1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Rewrite our gametest system

This is a noisier diff than I'd like as this is just a direct copy from
the multi-loader branch.

 - Rename "ingame" package to "gametest"

 - Don't chain GameTestSequence methods - it's actually much cleaner if
   we just use Kotlin's implicit this syntax.

 - Use our work in 71f81e1201 to write
   computer tests using Kotlin instead of Lua. This means all the logic
   is in one place, which is nice!

 - Add a couple more tests for some of the more error-prone bits of
   functionality.
This commit is contained in:
Jonathan Coates 2022-10-30 10:50:16 +00:00
parent b3702fed78
commit c4184a33bc
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
96 changed files with 2316 additions and 911 deletions

View File

@ -86,11 +86,17 @@ minecraft {
val testMod = configurations["testModRuntimeClasspath"].resolve()
val implementation = configurations.runtimeClasspath.get().resolve()
val new = (testMod - implementation)
.asSequence().filter { it.isFile }.map { it.absolutePath }
.asSequence()
.filter { it.isFile && !it.name.endsWith("-test-fixtures.jar") }
.map { it.absolutePath }
.joinToString(File.pathSeparator)
if (old == null) new else old.get() + File.pathSeparator + new
}
property("cctest.sources", file("src/testMod/resources/data/cctest").absolutePath)
arg("--mixin.config=computercraft-gametest.mixins.json")
mods.register("cctest") {
source(sourceSets["testMod"])
source(sourceSets["testFixtures"])
@ -114,7 +120,6 @@ minecraft {
mappings("parchment", "${libs.versions.parchmentMc.get()}-${libs.versions.parchment.get()}-$mcVersion")
accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
accessTransformer(file("src/testMod/resources/META-INF/accesstransformer.cfg"))
}
mixin {

View File

@ -78,7 +78,9 @@
<!-- Javadoc -->
<!-- TODO: Missing* checks for the dan200.computercraft.api package? -->
<module name="AtclauseOrder" />
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
</module>
<module name="InvalidJavadocPosition" />
<module name="JavadocBlockTagLocation" />
<module name="JavadocMethod"/>
@ -107,7 +109,9 @@
<module name="LocalFinalVariableName" />
<module name="LocalVariableName" />
<module name="MemberName" />
<module name="MethodName" />
<module name="MethodName">
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^dan200\.computercraft(\.[a-z][a-z0-9]*)*" />
@ -115,11 +119,6 @@
<module name="ParameterName" />
<module name="StaticVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
<property name="applyToPrivate" value="false" />
</module>
<module name="StaticVariableName">
<property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
<property name="applyToPrivate" value="true" />
</module>
<module name="TypeName" />

View File

@ -5,13 +5,17 @@
*/
package dan200.computercraft.shared.computer.core;
import com.google.common.annotations.VisibleForTesting;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.ComputerCraftAPIImpl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.core.ComputerContext;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.mainthread.MainThread;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
import dan200.computercraft.shared.util.IDAssigner;
@ -41,6 +45,9 @@ public final class ServerContext
{
private static final LevelResource FOLDER = new LevelResource( ComputerCraft.MOD_ID );
@VisibleForTesting
public static ILuaMachine.Factory luaMachine = CobaltLuaMachine::new;
private static @Nullable ServerContext instance;
private final MinecraftServer server;
@ -57,7 +64,11 @@ private ServerContext( MinecraftServer server )
this.server = server;
storageDir = server.getWorldPath( FOLDER );
mainThread = new MainThread();
context = new ComputerContext( new Environment( server ), ComputerCraft.computerThreads, mainThread );
context = new ComputerContext(
new Environment( server ),
new ComputerThread( ComputerCraft.computerThreads ),
mainThread, luaMachine
);
idAssigner = new IDAssigner( storageDir.resolve( "ids.json" ) );
}

View File

@ -10,8 +10,11 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.ingame.mod.TestMod;
import net.minecraft.client.Minecraft;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.TextComponent;
@ -19,10 +22,6 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.*;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ForgeRegistries;
import java.io.File;
@ -37,19 +36,24 @@
/**
* Provides a {@literal /ccexport <path>} command which exports icons and recipes for all ComputerCraft items.
*/
@Mod.EventBusSubscriber( modid = TestMod.MOD_ID, value = Dist.CLIENT )
public class Exporter
{
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
@SubscribeEvent
public static void onClientCommands( ClientChatEvent event )
public static <S> void register( CommandDispatcher<S> dispatcher )
{
String prefix = "/ccexport";
if( !event.getMessage().startsWith( prefix ) ) return;
event.setCanceled( true );
dispatcher.register(
LiteralArgumentBuilder.<S>literal( "ccexport" )
.then( RequiredArgumentBuilder.<S, String>argument( "path", StringArgumentType.string() )
.executes( c -> {
run( c.getArgument( "name", String.class ) );
return 0;
} ) ) );
}
Path output = new File( event.getMessage().substring( prefix.length() ).trim() ).getAbsoluteFile().toPath();
private static void run( String path )
{
Path output = new File( path ).getAbsoluteFile().toPath();
if( !Files.isDirectory( output ) )
{
Minecraft.getInstance().gui.getChat().addMessage( new TextComponent( "Output path does not exist" ) );

View File

@ -3,9 +3,9 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.api;
package dan200.computercraft.gametest.api;
import dan200.computercraft.ingame.mod.TestAPI;
import dan200.computercraft.gametest.core.TestAPI;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestSequence;

View File

@ -0,0 +1,24 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest.api;
import net.minecraft.gametest.framework.GameTest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks classes containing {@linkplain GameTest game tests}.
* <p>
* This is used by Forge to automatically load and test classes.
*/
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface GameTestHolder
{
}

View File

@ -3,17 +3,16 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
package dan200.computercraft.gametest.core;
import com.mojang.brigadier.CommandDispatcher;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.mixin.gametest.TestCommandAccessor;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.gametest.framework.StructureUtils;
import net.minecraft.gametest.framework.TestCommand;
import net.minecraft.gametest.framework.TestFunction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.TextComponent;
@ -24,10 +23,10 @@
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.level.block.entity.StructureBlockEntity;
import net.minecraft.world.level.storage.LevelResource;
import net.minecraftforge.fml.loading.FMLLoader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice;
import static net.minecraft.commands.Commands.literal;
@ -37,6 +36,8 @@
*/
class CCTestCommand
{
public static final LevelResource LOCATION = new LevelResource( ComputerCraft.MOD_ID );
public static void register( CommandDispatcher<CommandSourceStack> dispatcher )
{
dispatcher.register( choice( "cctest" )
@ -49,7 +50,7 @@ public static void register( CommandDispatcher<CommandSourceStack> dispatcher )
for( TestFunction function : GameTestRegistry.getAllTestFunctions() )
{
TestCommand.exportTestStructure( context.getSource(), function.getStructureName() );
TestCommandAccessor.callExportTestStructure( context.getSource(), function.getStructureName() );
}
return 0;
} ) )
@ -57,17 +58,11 @@ public static void register( CommandDispatcher<CommandSourceStack> dispatcher )
for( TestFunction function : GameTestRegistry.getAllTestFunctions() )
{
dispatcher.execute( "test import " + function.getTestName(), context.getSource() );
TestCommand.exportTestStructure( context.getSource(), function.getStructureName() );
TestCommandAccessor.callExportTestStructure( context.getSource(), function.getStructureName() );
}
return 0;
} ) )
.then( literal( "promote" ).executes( context -> {
if( !FMLLoader.getDist().isClient() ) return error( context.getSource(), "Cannot run on server" );
promote();
return 0;
} ) )
.then( literal( "marker" ).executes( context -> {
ServerPlayer player = context.getSource().getPlayerOrException();
BlockPos pos = StructureUtils.findNearestStructureBlock( player.blockPosition(), 15, player.getLevel() );
@ -99,7 +94,7 @@ public static void importFiles( MinecraftServer server )
{
try
{
Copier.replicate( TestMod.sourceDir.resolve( "computers" ), server.getWorldPath( new LevelResource( ComputerCraft.MOD_ID ) ) );
Copier.replicate( getSourceComputerPath(), getWorldComputerPath( server ) );
}
catch( IOException e )
{
@ -111,7 +106,7 @@ static void exportFiles( MinecraftServer server )
{
try
{
Copier.replicate( server.getWorldPath( new LevelResource( ComputerCraft.MOD_ID ) ), TestMod.sourceDir.resolve( "computers" ) );
Copier.replicate( getWorldComputerPath( server ), getSourceComputerPath() );
}
catch( IOException e )
{
@ -119,20 +114,14 @@ static void exportFiles( MinecraftServer server )
}
}
private static void promote()
private static Path getWorldComputerPath( MinecraftServer server )
{
try
{
Copier.replicate(
Minecraft.getInstance().gameDirectory.toPath().resolve( "screenshots" ),
TestMod.sourceDir.resolve( "screenshots" ),
x -> !x.toFile().getName().endsWith( ".diff.png" )
);
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
return server.getWorldPath( LOCATION ).resolve( "computer" ).resolve( "0" );
}
private static Path getSourceComputerPath()
{
return TestHooks.sourceDir.resolve( "computer" );
}
private static int error( CommandSourceStack source, String message )

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
package dan200.computercraft.gametest.core;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Minecraft;
@ -36,7 +36,7 @@
import static net.minecraft.world.level.levelgen.WorldGenSettings.withOverworld;
@Mod.EventBusSubscriber( modid = TestMod.MOD_ID, value = Dist.CLIENT )
@Mod.EventBusSubscriber( modid = "cctest", value = Dist.CLIENT )
public final class ClientHooks
{
private static final Logger LOG = LogManager.getLogger( TestHooks.class );

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
package dan200.computercraft.gametest.core;
import com.google.common.io.MoreFiles;
import com.google.common.io.RecursiveDeleteOption;

View File

@ -3,22 +3,22 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
package dan200.computercraft.gametest.core;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.IComputerSystem;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.ingame.api.ComputerState;
import dan200.computercraft.ingame.api.TestExtensionsKt;
import dan200.computercraft.gametest.api.ComputerState;
import dan200.computercraft.gametest.api.TestExtensionsKt;
import net.minecraft.gametest.framework.GameTestSequence;
import java.util.Optional;
/**
* API exposed to computers to help write tests.
*
* <p>
* Note, we extend this API within startup file of computers (see {@code cctest.lua}).
*
* @see TestExtensionsKt#thenComputerOk(GameTestSequence, String, String) To check tests on the computer have passed.

View File

@ -0,0 +1,60 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest.core;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.gametest.api.Times;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestRunner;
import net.minecraft.gametest.framework.GameTestTicker;
import net.minecraft.gametest.framework.StructureUtils;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestHooks
{
public static final Logger LOGGER = LoggerFactory.getLogger( TestHooks.class );
public static final Path sourceDir = Paths.get( System.getProperty( "cctest.sources" ) ).normalize().toAbsolutePath();
public static void init()
{
ServerContext.luaMachine = ManagedComputers.INSTANCE;
ComputerCraftAPI.registerAPIFactory( TestAPI::new );
StructureUtils.testStructuresDir = sourceDir.resolve( "structures" ).toString();
}
public static void onServerStarted( MinecraftServer server )
{
GameRules rules = server.getGameRules();
rules.getRule( GameRules.RULE_DAYLIGHT ).set( false, server );
ServerLevel world = server.getLevel( Level.OVERWORLD );
if( world != null ) world.setDayTime( Times.NOON );
LOGGER.info( "Cleaning up after last run" );
GameTestRunner.clearAllTests( server.overworld(), new BlockPos( 0, -60, 0 ), GameTestTicker.SINGLETON, 200 );
// Delete server context and add one with a mutable machine factory. This allows us to set the factory for
// specific test batches without having to reset all computers.
for( var computer : ServerContext.get( server ).registry().getComputers() )
{
var label = computer.getLabel() == null ? "#" + computer.getID() : computer.getLabel();
LOGGER.warn( "Unexpected computer {}", label );
}
LOGGER.info( "Importing files" );
CCTestCommand.importFiles( server );
}
}

View File

@ -0,0 +1,144 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest.core;
import dan200.computercraft.export.Exporter;
import dan200.computercraft.gametest.api.GameTestHolder;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.gametest.framework.StructureUtils;
import net.minecraft.gametest.framework.TestFunction;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.RegisterGameTestsEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.gametest.PrefixGameTestTemplate;
import org.objectweb.asm.Type;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Locale;
import java.util.function.Consumer;
@Mod( "cctest" )
public class TestMod
{
public TestMod()
{
TestHooks.init();
var bus = MinecraftForge.EVENT_BUS;
bus.addListener( EventPriority.LOW, ( ServerStartedEvent e ) -> TestHooks.onServerStarted( e.getServer() ) );
bus.addListener( ( RegisterCommandsEvent e ) -> CCTestCommand.register( e.getDispatcher() ) );
bus.addListener( ( RegisterClientCommandsEvent e ) -> Exporter.register( e.getDispatcher() ) );
var modBus = FMLJavaModLoadingContext.get().getModEventBus();
modBus.addListener( ( RegisterGameTestsEvent event ) -> {
var holder = Type.getType( GameTestHolder.class );
ModList.get().getAllScanData().stream()
.map( ModFileScanData::getAnnotations )
.flatMap( Collection::stream )
.filter( a -> holder.equals( a.annotationType() ) )
.forEach( x -> registerClass( x.clazz().getClassName(), event::register ) );
} );
}
private static Class<?> loadClass( String name )
{
try
{
return Class.forName( name, true, TestMod.class.getClassLoader() );
}
catch( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
}
private static void registerClass( String className, Consumer<Method> fallback )
{
var klass = loadClass( className );
for( var method : klass.getDeclaredMethods() )
{
var testInfo = method.getAnnotation( GameTest.class );
if( testInfo == null )
{
fallback.accept( method );
continue;
}
GameTestRegistry.getAllTestFunctions().add( turnMethodIntoTestFunction( method, testInfo ) );
GameTestRegistry.getAllTestClassNames().add( className );
}
}
/**
* Custom implementation of {@link GameTestRegistry#turnMethodIntoTestFunction(Method)} which makes
* {@link GameTest#template()} behave the same as Fabric, namely in that it points to a {@link ResourceLocation},
* rather than a test-class-specific structure.
* <p>
* This effectively acts as a global version of {@link PrefixGameTestTemplate}, just one which doesn't require Forge
* to be present.
*
* @param method The method to register.
* @param testInfo The test info.
* @return The constructed test function.
*/
private static TestFunction turnMethodIntoTestFunction( Method method, GameTest testInfo )
{
var className = method.getDeclaringClass().getSimpleName().toLowerCase( Locale.ROOT );
var testName = className + "." + method.getName().toLowerCase( Locale.ROOT );
return new TestFunction(
testInfo.batch(),
testName,
testInfo.template().isEmpty() ? testName : testInfo.template(),
StructureUtils.getRotationForRotationSteps( testInfo.rotationSteps() ), testInfo.timeoutTicks(), testInfo.setupTicks(),
testInfo.required(), testInfo.requiredSuccesses(), testInfo.attempts(),
turnMethodIntoConsumer( method )
);
}
private static <T> Consumer<T> turnMethodIntoConsumer( Method method )
{
return value -> {
try
{
Object instance = null;
if( !Modifier.isStatic( method.getModifiers() ) )
{
instance = method.getDeclaringClass().getConstructor().newInstance();
}
method.invoke( instance, value );
}
catch( InvocationTargetException e )
{
if( e.getCause() instanceof RuntimeException )
{
throw (RuntimeException) e.getCause();
}
else
{
throw new RuntimeException( e.getCause() );
}
}
catch( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
};
}
}

View File

@ -1,38 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.modifyBlock
import dan200.computercraft.ingame.api.sequence
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.level.block.LeverBlock
import net.minecraft.world.level.block.RedstoneLampBlock
import net.minecraftforge.gametest.GameTestHolder
@GameTestHolder(ComputerCraft.MOD_ID)
class Computer_Test {
/**
* Ensures redstone signals do not travel through computers.
*
* @see [#548](https://github.com/cc-tweaked/CC-Tweaked/issues/548)
*/
@GameTest
fun No_through_signal(context: GameTestHelper) = context.sequence {
val lamp = BlockPos(2, 2, 4)
val lever = BlockPos(2, 2, 0)
this
.thenExecute {
context.assertBlockState(lamp, { !it.getValue(RedstoneLampBlock.LIT) }, { "Lamp should not be lit" })
context.modifyBlock(lever) { x -> x.setValue(LeverBlock.POWERED, true) }
}
.thenIdle(3)
.thenExecute {
context.assertBlockState(
lamp,
{ !it.getValue(RedstoneLampBlock.LIT) },
{ "Lamp should still not be lit" },
)
}
}
}

View File

@ -1,17 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenComputerOk
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraftforge.gametest.GameTestHolder
@GameTestHolder(ComputerCraft.MOD_ID)
class CraftOs_Test {
/**
* Sends a rednet message to another a computer and back again.
*/
@GameTest(timeoutTicks = 200)
fun Sends_basic_rednet_messages(context: GameTestHelper) = context.sequence { thenComputerOk("main") }
}

View File

@ -1,29 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenComputerOk
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.Items
import net.minecraftforge.gametest.GameTestHolder
@GameTestHolder(ComputerCraft.MOD_ID)
class Disk_Drive_Test {
/**
* Ensure audio disks exist and we can play them.
*
* @see [#688](https://github.com/cc-tweaked/CC-Tweaked/issues/688)
*/
@GameTest(timeoutTicks = Modem_Test.TIMEOUT)
fun Audio_disk(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
@GameTest(timeoutTicks = Modem_Test.TIMEOUT)
fun Ejects_disk(helper: GameTestHelper) = helper.sequence {
val stackAt = BlockPos(2, 2, 2)
this
.thenComputerOk()
.thenWaitUntil { helper.assertItemEntityPresent(Items.MUSIC_DISC_13, stackAt, 0.0) }
}
}

View File

@ -1,45 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenComputerOk
import dan200.computercraft.shared.Registry
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraftforge.gametest.GameTestHolder
@GameTestHolder(ComputerCraft.MOD_ID)
class Modem_Test {
@GameTest(timeoutTicks = TIMEOUT)
fun Have_peripherals(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
@GameTest(timeoutTicks = TIMEOUT)
fun Gains_peripherals(helper: GameTestHelper) = helper.sequence {
val position = BlockPos(2, 2, 2)
this
.thenComputerOk(marker = "initial")
.thenExecute {
helper.setBlock(
position,
BlockCable.correctConnections(
helper.level,
helper.absolutePos(position),
Registry.ModBlocks.CABLE.get().defaultBlockState().setValue(BlockCable.CABLE, true),
),
)
}
.thenComputerOk()
}
/**
* Sends a modem message to another computer on the same network
*/
@GameTest(timeoutTicks = TIMEOUT)
fun Transmits_messages(context: GameTestHelper) = context.sequence { thenComputerOk("receive") }
companion object {
const val TIMEOUT = 200
}
}

View File

@ -1,87 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.*
import dan200.computercraft.shared.Capabilities
import dan200.computercraft.shared.Registry
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer
import dan200.computercraft.shared.peripheral.monitor.TileMonitor
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.level.block.Blocks
import net.minecraftforge.gametest.GameTestHolder
import java.util.*
@GameTestHolder(ComputerCraft.MOD_ID)
class Monitor_Test {
@GameTest
fun Ensures_valid_on_place(context: GameTestHelper) = context.sequence {
val pos = BlockPos(2, 2, 2)
val tag = CompoundTag()
tag.putInt("Width", 2)
tag.putInt("Height", 2)
val toSet = BlockInput(
Registry.ModBlocks.MONITOR_ADVANCED.get().defaultBlockState(),
Collections.emptySet(),
tag,
)
context.setBlock(pos, Blocks.AIR.defaultBlockState())
context.setBlock(pos, toSet)
this
.thenIdle(2)
.thenExecute {
val tile = context.getBlockEntity(pos)
if (tile !is TileMonitor) {
context.fail("Expected tile to be monitor, is $tile", pos)
return@thenExecute
}
if (tile.width != 1 || tile.height != 1) {
context.fail("Tile has width and height of ${tile.width}x${tile.height}, but should be 1x1", pos)
}
}
}
private fun looksAcceptable(helper: GameTestHelper, renderer: MonitorRenderer) = helper.sequence {
this
.thenExecute {
ComputerCraft.monitorRenderer = renderer
helper.positionAtArmorStand()
// Get the monitor and peripheral. This forces us to create a server monitor at this location.
val monitor = helper.getBlockEntity(BlockPos(2, 2, 3), Registry.ModBlockEntities.MONITOR_ADVANCED.get())
monitor.getCapability(Capabilities.CAPABILITY_PERIPHERAL)
val terminal = monitor.cachedServerMonitor!!.terminal!!
terminal.write("Hello, world!")
terminal.setCursorPos(1, 2)
terminal.textColour = 2
terminal.backgroundColour = 3
terminal.write("Some coloured text")
}
.thenScreenshot()
}
// @GameTest(batch = "Monitor_Test.Looks_acceptable", template = LOOKS_ACCEPTABLE)
fun Looks_acceptable(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.TBO)
// @GameTest(batch = "Monitor_Test.Looks_acceptable_dark", template = LOOKS_ACCEPTABLE_DARK)
fun Looks_acceptable_dark(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.TBO)
// @GameTest(batch = "Monitor_Test.Looks_acceptable_vbo", template = LOOKS_ACCEPTABLE)
fun Looks_acceptable_vbo(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.VBO)
// @GameTest(batch = "Monitor_Test.Looks_acceptable_dark_vbo", template = LOOKS_ACCEPTABLE_DARK)
fun Looks_acceptable_dark_vbo(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.VBO)
private companion object {
const val LOOKS_ACCEPTABLE = "looks_acceptable"
const val LOOKS_ACCEPTABLE_DARK = "looks_acceptable_dark"
}
}

View File

@ -1,15 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ingame.api.positionAtArmorStand
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenScreenshot
import net.minecraft.gametest.framework.GameTestHelper
class Printout_Test {
// @GameTest(batch = "Printout_Test.In_frame_at_night", timeoutTicks = Timeouts.CLIENT_TIMEOUT)
fun In_frame_at_night(helper: GameTestHelper) = helper.sequence {
this
.thenExecute { helper.positionAtArmorStand() }
.thenScreenshot()
}
}

View File

@ -1,101 +0,0 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.api.detail.DetailRegistries
import dan200.computercraft.ingame.api.*
import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT
import dan200.computercraft.shared.media.items.ItemPrintout
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.ItemStack
import net.minecraftforge.gametest.GameTestHolder
@GameTestHolder(ComputerCraft.MOD_ID)
class Turtle_Test {
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Unequip_refreshes_peripheral(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can sheer sheep (and drop items)
*
* @see [#537](https://github.com/cc-tweaked/CC-Tweaked/issues/537)
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Shears_sheep(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can place lava.
*
* @see [#518](https://github.com/cc-tweaked/CC-Tweaked/issues/518)
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Place_lava(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can place when waterlogged.
*
* @see [#385](https://github.com/cc-tweaked/CC-Tweaked/issues/385)
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Place_waterlogged(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can pick up lava
*
* @see [#297](https://github.com/cc-tweaked/CC-Tweaked/issues/297)
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Gather_lava(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can hoe dirt.
*
* @see [#258](https://github.com/cc-tweaked/CC-Tweaked/issues/258)
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Hoe_dirt(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can place monitors
*
* @see [#691](https://github.com/cc-tweaked/CC-Tweaked/issues/691)
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Place_monitor(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can place into compostors. These are non-typical inventories, so
* worth testing.
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Use_compostors(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can be cleaned in cauldrons.
*
* Currently not required as turtles can no longer right-click cauldrons.
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT, required = false)
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can use IDetailProviders by getting details for a printed page.
*/
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Item_detail_provider(helper: GameTestHelper) = helper.sequence {
this
.thenComputerOk(marker = "initial")
.thenExecute {
// Register a dummy provider for printout items
DetailRegistries.ITEM_STACK.addProvider(
object : BasicItemDetailProvider<ItemPrintout>("printout", ItemPrintout::class.java) {
override fun provideDetails(data: MutableMap<in String, Any>, stack: ItemStack, item: ItemPrintout) {
data["type"] = item.type.toString()
}
},
)
}
.thenComputerOk()
}
}

View File

@ -1,179 +0,0 @@
package dan200.computercraft.ingame.api
import dan200.computercraft.ingame.mod.ImageUtils
import dan200.computercraft.ingame.mod.TestMod
import net.minecraft.client.Minecraft
import net.minecraft.client.Screenshot
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestAssertPosException
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.GameTestSequence
import net.minecraft.world.entity.decoration.ArmorStand
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 java.nio.file.Files
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Supplier
import javax.imageio.ImageIO
object Times {
const val NOON: Long = 6000
}
/**
* Custom timeouts for various test types.
*/
object Timeouts {
private const val SECOND: Int = 20
const val COMPUTER_TIMEOUT: Int = SECOND * 15
const val CLIENT_TIMEOUT: Int = SECOND * 20
}
/**
* Wait until a computer has finished running and check it is OK.
*/
fun GameTestSequence.thenComputerOk(name: String? = null, marker: String = ComputerState.DONE): GameTestSequence {
val label = parent.testName + (if (name == null) "" else ".$name")
return this.thenWaitUntil {
val computer = ComputerState.get(label)
if (computer == null || !computer.isDone(marker)) throw GameTestAssertException("Computer '$label' has not reached $marker yet.")
}.thenExecute {
ComputerState.get(label).check(marker)
}
}
/**
* Run a task on the client
*/
fun GameTestSequence.thenOnClient(task: ClientTestHelper.() -> Unit): GameTestSequence {
var future: CompletableFuture<Unit>? = null
return this
.thenExecute { future = Minecraft.getInstance().submit(Supplier { task(ClientTestHelper()) }) }
.thenWaitUntil { if (!future!!.isDone) throw GameTestAssertException("Not done task yet") }
.thenExecute {
try {
future!!.get()
} catch (e: ExecutionException) {
throw e.cause ?: e
}
}
}
/**
* Idle for one tick to allow the client to catch up, then take a screenshot.
*/
fun GameTestSequence.thenScreenshot(name: String? = null): GameTestSequence {
val suffix = if (name == null) "" else "-$name"
val fullName = "${parent.testName}$suffix"
var counter = 0
val hasScreenshot = AtomicBoolean()
return this
// Wait until all chunks have been rendered and we're idle for an extended period.
.thenExecute { counter = 0 }
.thenWaitUntil {
val renderer = Minecraft.getInstance().levelRenderer
if (renderer.chunkRenderDispatcher != null && renderer.hasRenderedAllChunks()) {
val idleFor = ++counter
if (idleFor <= 20) throw GameTestAssertException("Only idle for $idleFor ticks")
} else {
counter = 0
throw GameTestAssertException("Waiting for client to finish rendering")
}
}
// Now disable the GUI, take a screenshot and reenable it. We sleep either side to give the client time to do
// its thing.
.thenExecute {
Minecraft.getInstance().options.hideGui = true
hasScreenshot.set(false)
}
.thenIdle(5) // Some delay before/after to ensure the render thread has caught up.
.thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } }
.thenWaitUntil { if (!hasScreenshot.get()) throw GameTestAssertException("Screenshot does not exist") }
.thenExecute {
Minecraft.getInstance().options.hideGui = false
val screenshotsPath = Minecraft.getInstance().gameDirectory.toPath().resolve("screenshots")
val screenshotPath = screenshotsPath.resolve("$fullName.png")
val originalPath = TestMod.sourceDir.resolve("screenshots").resolve("$fullName.png")
if (!Files.exists(originalPath)) throw GameTestAssertException("$fullName does not exist. Use `/cctest promote' to create it.")
val screenshot = ImageIO.read(screenshotPath.toFile())
?: throw GameTestAssertException("Error reading screenshot from $screenshotPath")
val original = ImageIO.read(originalPath.toFile())
if (screenshot.width != original.width || screenshot.height != original.height) {
throw GameTestAssertException("$fullName screenshot is ${screenshot.width}x${screenshot.height} but original is ${original.width}x${original.height}")
}
ImageUtils.writeDifference(screenshotsPath.resolve("$fullName.diff.png"), screenshot, original)
if (!ImageUtils.areSame(screenshot, original)) throw GameTestAssertException("Images are different.")
}
}
val GameTestHelper.testName: String get() = testInfo.testName
val GameTestHelper.structureName: String get() = testInfo.structureName
/**
* Modify a block state within the test.
*/
fun GameTestHelper.modifyBlock(pos: BlockPos, modify: (BlockState) -> BlockState) {
setBlock(pos, modify(getBlockState(pos)))
}
fun GameTestHelper.sequence(run: GameTestSequence.() -> GameTestSequence) {
run(startSequence()).thenSucceed()
}
fun <T : BlockEntity> GameTestHelper.getBlockEntity(pos: BlockPos, type: BlockEntityType<T>): T {
val tile = getBlockEntity(pos)
@Suppress("UNCHECKED_CAST")
return when {
tile == null -> throw GameTestAssertPosException("Expected ${type.registryName}, but no tile was there", absolutePos(pos), pos, 0)
tile.type != type -> throw GameTestAssertPosException("Expected ${type.registryName} but got ${tile.type.registryName}", absolutePos(pos), pos, 0)
else -> tile as T
}
}
/**
* Set a block within the test structure.
*/
fun GameTestHelper.setBlock(pos: BlockPos, state: BlockInput) = state.place(level, absolutePos(pos), 3)
/**
* Position the player at an armor stand.
*/
fun GameTestHelper.positionAtArmorStand() {
val entities = level.getEntities(null, bounds) { it.name.string == structureName }
if (entities.size <= 0 || entities[0] !is ArmorStand) throw GameTestAssertException("Cannot find armor stand")
val stand = entities[0] as ArmorStand
val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist")
player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot)
}
class ClientTestHelper {
val minecraft: Minecraft = Minecraft.getInstance()
fun screenshot(name: String, callback: () -> Unit = {}) {
Screenshot.grab(
minecraft.gameDirectory,
name,
minecraft.mainRenderTarget,
) {
TestMod.log.info(it.string)
callback()
}
}
}

View File

@ -1,82 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
public final class ImageUtils
{
private static final Logger LOG = LogManager.getLogger( ImageUtils.class );
/**
* Allow 0.3% of pixels to fail. This allows for slight differences at the edges.
*/
private static final double PIXEL_THRESHOLD = 0.003;
/**
* Maximum possible distance between two colours. Floating point differences means we need some fuzziness here.
*/
public static final int DISTANCE_THRESHOLD = 5;
private ImageUtils()
{
}
public static boolean areSame( BufferedImage left, BufferedImage right )
{
int width = left.getWidth(), height = left.getHeight();
if( width != right.getWidth() || height != right.getHeight() ) return false;
int failed = 0, threshold = (int) (width * height * PIXEL_THRESHOLD);
for( int x = 0; x < width; x++ )
{
for( int y = 0; y < height; y++ )
{
int l = left.getRGB( x, y ), r = right.getRGB( x, y );
if( (l & 0xFFFFFF) != (r & 0xFFFFFF) && distance( l, r, 0 ) + distance( l, r, 8 ) + distance( l, r, 16 ) >= DISTANCE_THRESHOLD )
{
failed++;
}
}
}
if( failed > 0 ) LOG.warn( "{} pixels failed comparing (threshold is {})", failed, threshold );
return failed <= threshold;
}
public static void writeDifference( Path path, BufferedImage left, BufferedImage right ) throws IOException
{
int width = left.getWidth(), height = left.getHeight();
BufferedImage copy = new BufferedImage( width, height, left.getType() );
for( int x = 0; x < width; x++ )
{
for( int y = 0; y < height; y++ )
{
int l = left.getRGB( x, y ), r = right.getRGB( x, y );
copy.setRGB( x, y, difference( l, r, 0 ) | difference( l, r, 8 ) | difference( l, r, 16 ) | 0xFF000000 );
}
}
ImageIO.write( copy, "png", path.toFile() );
}
private static int difference( int l, int r, int shift )
{
return Math.abs( ((l >> shift) & 0xFF) - ((r >> shift) & 0xFF) ) << shift;
}
private static int distance( int l, int r, int shift )
{
return Math.abs( ((l >> shift) & 0xFF) - ((r >> shift) & 0xFF) );
}
}

View File

@ -1,49 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
import dan200.computercraft.ingame.api.Times;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod.EventBusSubscriber( modid = TestMod.MOD_ID )
public class TestHooks
{
private static final Logger LOG = LogManager.getLogger( TestHooks.class );
@SubscribeEvent
public static void onRegisterCommands( RegisterCommandsEvent event )
{
LOG.info( "Starting server, registering command helpers." );
CCTestCommand.register( event.getDispatcher() );
}
@SubscribeEvent
public static void onServerStarted( ServerStartedEvent event )
{
MinecraftServer server = event.getServer();
GameRules rules = server.getGameRules();
rules.getRule( GameRules.RULE_DAYLIGHT ).set( false, server );
ServerLevel world = event.getServer().getLevel( Level.OVERWORLD );
if( world != null ) world.setDayTime( Times.NOON );
// LOG.info( "Cleaning up after last run" );
// CommandSourceStack source = server.createCommandSourceStack();
// GameTestRunner.clearAllTests( source.getLevel(), getStart( source ), GameTestTicker.SINGLETON, 200 );
LOG.info( "Importing files" );
CCTestCommand.importFiles( server );
}
}

View File

@ -1,33 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.ingame.mod;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.gametest.framework.StructureUtils;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
@Mod( TestMod.MOD_ID )
public class TestMod
{
public static final Path sourceDir = Paths.get( "../../src/testMod/server-files" ).normalize().toAbsolutePath();
public static final String MOD_ID = "cctest";
public static final Logger log = LogManager.getLogger( MOD_ID );
public TestMod()
{
log.info( "CC: Test initialised" );
ComputerCraftAPI.registerAPIFactory( TestAPI::new );
StructureUtils.testStructuresDir = sourceDir.resolve( "structures" ).toString();
}
}

View File

@ -0,0 +1,23 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin.gametest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.gametest.framework.GameTestInfo;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin( GameTestHelper.class )
public interface GameTestHelperAccessor
{
@Invoker
AABB callGetBounds();
@Accessor
GameTestInfo getTestInfo();
}

View File

@ -0,0 +1,17 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin.gametest;
import net.minecraft.gametest.framework.GameTestInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin( GameTestInfo.class )
public interface GameTestInfoAccessor
{
@Invoker( "getTick" )
long computercraft$getTick();
}

View File

@ -0,0 +1,18 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin.gametest;
import net.minecraft.gametest.framework.GameTestInfo;
import net.minecraft.gametest.framework.GameTestSequence;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin( GameTestSequence.class )
public interface GameTestSequenceAccessor
{
@Accessor
GameTestInfo getParent();
}

View File

@ -0,0 +1,58 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin.gametest;
import dan200.computercraft.gametest.core.TestHooks;
import net.minecraft.gametest.framework.GameTestAssertException;
import net.minecraft.gametest.framework.GameTestInfo;
import net.minecraft.gametest.framework.GameTestSequence;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin( GameTestSequence.class )
class GameTestSequenceMixin
{
@Shadow
@Final
GameTestInfo parent;
/**
* Override {@link GameTestSequence#tickAndContinue(long)} to catch non-{@link GameTestAssertException} failures.
*
* @param ticks The current tick.
* @author Jonathan Coates
* @reason There's no sense doing this in a more compatible way for game tests.
*/
@Overwrite
public void tickAndContinue( long ticks )
{
try
{
tick( ticks );
}
catch( GameTestAssertException ignored )
{
// Mimic the original behaviour.
}
catch( AssertionError e )
{
parent.fail( e );
}
catch( Exception e )
{
// Fail the test, rather than crashing the server.
TestHooks.LOGGER.error( "{} threw unexpected exception", parent.getTestName(), e );
parent.fail( e );
}
}
@Shadow
private void tick( long tick )
{
}
}

View File

@ -0,0 +1,21 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin.gametest;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.gametest.framework.TestCommand;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin( TestCommand.class )
public interface TestCommandAccessor
{
@Invoker
static int callExportTestStructure( CommandSourceStack source, String structure )
{
return 0;
}
}

View File

@ -0,0 +1,67 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.core.apis.RedstoneAPI
import dan200.computercraft.core.computer.ComputerSide
import dan200.computercraft.gametest.api.*
import dan200.computercraft.test.core.computer.getApi
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.LeverBlock
import net.minecraft.world.level.block.RedstoneLampBlock
@GameTestHolder
class Computer_Test {
/**
* Ensures redstone signals do not travel through computers.
*
* @see [#548](https://github.com/cc-tweaked/CC-Tweaked/issues/548)
*/
@GameTest
fun No_through_signal(context: GameTestHelper) = context.sequence {
val lamp = BlockPos(2, 2, 4)
val lever = BlockPos(2, 2, 0)
thenExecute {
context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should not be lit")
context.modifyBlock(lever) { x -> x.setValue(LeverBlock.POWERED, true) }
}
thenIdle(3)
thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should still not be lit") }
}
/**
* Similar to the above, but with a repeater before the computer
*/
@GameTest
fun No_through_signal_reverse(context: GameTestHelper) = context.sequence {
val lamp = BlockPos(2, 2, 4)
val lever = BlockPos(2, 2, 0)
thenExecute {
context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should not be lit")
context.modifyBlock(lever) { x -> x.setValue(LeverBlock.POWERED, true) }
}
thenIdle(3)
thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should still not be lit") }
}
@GameTest
fun Set_and_destroy(context: GameTestHelper) = context.sequence {
val lamp = BlockPos(2, 2, 3)
thenOnComputer { getApi<RedstoneAPI>().setOutput(ComputerSide.BACK, true) }
thenIdle(3)
thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, true, "Lamp should be lit") }
thenExecute { context.setBlock(BlockPos(2, 2, 2), Blocks.AIR) }
thenIdle(4)
thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should not be lit") }
}
// TODO: More redstone connectivity tests!
// TODO: Computer peripherals (including command computers)
}

View File

@ -0,0 +1,22 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.Timeouts
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.gametest.api.thenComputerOk
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
@GameTestHolder
class CraftOs_Test {
/**
* Sends a rednet message to another a computer and back again.
*/
@GameTest(timeoutTicks = Timeouts.COMPUTER_TIMEOUT)
fun Sends_basic_rednet_messages(context: GameTestHelper) = context.sequence { thenComputerOk("main") }
}

View File

@ -0,0 +1,43 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.test.core.assertArrayEquals
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.Items
@GameTestHolder
class Disk_Drive_Test {
/**
* Ensure audio disks exist and we can play them.
*
* @see [#688](https://github.com/cc-tweaked/CC-Tweaked/issues/688)
*/
@GameTest
fun Audio_disk(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
callPeripheral("right", "hasAudio")
.assertArrayEquals(true, message = "Disk has audio")
callPeripheral("right", "getAudioTitle")
.assertArrayEquals("C418 - 13", message = "Correct audio title")
}
}
@GameTest
fun Ejects_disk(helper: GameTestHelper) = helper.sequence {
val stackAt = BlockPos(2, 2, 2)
thenOnComputer { callPeripheral("right", "ejectDisk") }
thenWaitUntil { helper.assertItemEntityPresent(Items.MUSIC_DISC_13, stackAt, 0.0) }
}
// TODO: Ejecting disk unmounts and closes files
}

View File

@ -0,0 +1,37 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.Structures
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.shared.Registry
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.ChestBlockEntity
import net.minecraft.world.level.storage.loot.BuiltInLootTables
@GameTestHolder
class Loot_Test {
/**
* Test that the loot tables will spawn in treasure disks.
*/
@GameTest(template = Structures.DEFAULT)
fun Chest_contains_disk(context: GameTestHelper) = context.sequence {
thenExecute {
val pos = BlockPos(2, 2, 2)
context.setBlock(pos, Blocks.CHEST)
val chest = context.getBlockEntity(pos) as ChestBlockEntity
chest.setLootTable(BuiltInLootTables.SIMPLE_DUNGEON, 123)
chest.unpackLootTable(null)
context.assertContainerContains(pos, Registry.ModItems.TREASURE_DISK.get())
}
}
}

View File

@ -0,0 +1,103 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.api.lua.ObjectArguments
import dan200.computercraft.core.apis.PeripheralAPI
import dan200.computercraft.core.computer.ComputerSide
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable
import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.LuaTaskContext
import dan200.computercraft.test.core.computer.getApi
import kotlinx.coroutines.delay
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.time.Duration.Companion.milliseconds
@GameTestHolder
class Modem_Test {
@GameTest
fun Have_peripherals(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
assertEquals(listOf("monitor_0", "printer_0", "right"), getPeripheralNames(), "Starts with peripherals")
}
}
@GameTest
fun Gains_peripherals(helper: GameTestHelper) = helper.sequence {
val position = BlockPos(2, 2, 2)
thenOnComputer {
assertEquals(listOf("back"), getPeripheralNames(), "Starts with peripherals")
}
thenExecute {
helper.setBlock(
position,
BlockCable.correctConnections(
helper.level,
helper.absolutePos(position),
dan200.computercraft.shared.Registry.ModBlocks.CABLE.get().defaultBlockState().setValue(BlockCable.CABLE, true),
),
)
}
thenIdle(1)
thenOnComputer {
assertEquals(listOf("back", "monitor_1", "printer_1"), getPeripheralNames(), "Gains new peripherals")
}
}
/**
* Sends a modem message to another computer on the same network
*/
@GameTest
fun Transmits_messages(context: GameTestHelper) = context.sequence {
thenStartComputer("send") {
val modem = findPeripheral("modem") ?: throw IllegalStateException("Cannot find modem")
while (true) {
callPeripheral(modem, "transmit", 12, 34, "Hello")
delay(50.milliseconds)
}
}
thenOnComputer("receive") {
val modem = findPeripheral("modem") ?: throw IllegalStateException("Cannot find modem")
callPeripheral(modem, "open", 12)
pullEvent("modem_message")
.assertArrayEquals("modem_message", "left", 12, 34, "Hello", 4, message = "Modem message")
}
}
}
private fun LuaTaskContext.findPeripheral(type: String): String? {
val peripheral = getApi<PeripheralAPI>()
for (side in ComputerSide.NAMES) {
val hasType = peripheral.hasType(side, type)
if (hasType != null && hasType[0] == true) return side
}
return null
}
private suspend fun LuaTaskContext.getPeripheralNames(): List<String> {
val peripheral = getApi<PeripheralAPI>()
val peripherals = mutableListOf<String>()
for (side in ComputerSide.NAMES) {
if (!peripheral.isPresent(side)) continue
peripherals.add(side)
val hasType = peripheral.hasType(side, "modem")
if (hasType == null || hasType[0] != true) continue
val names = peripheral.call(context, ObjectArguments(side, "getNamesRemote")).await() ?: continue
@Suppress("UNCHECKED_CAST")
peripherals.addAll(names[0] as Collection<String>)
}
peripherals.sort()
return peripherals
}

View File

@ -0,0 +1,50 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.getBlockEntity
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.gametest.api.setBlock
import dan200.computercraft.shared.Registry
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.level.block.Blocks
import java.util.*
@GameTestHolder
class Monitor_Test {
@GameTest
fun Ensures_valid_on_place(context: GameTestHelper) = context.sequence {
val pos = BlockPos(2, 2, 2)
thenExecute {
val tag = CompoundTag()
tag.putInt("Width", 2)
tag.putInt("Height", 2)
val toSet = BlockInput(
Registry.ModBlocks.MONITOR_ADVANCED.get().defaultBlockState(),
Collections.emptySet(),
tag,
)
context.setBlock(pos, Blocks.AIR.defaultBlockState())
context.setBlock(pos, toSet)
}
thenIdle(2)
thenExecute {
val tile = context.getBlockEntity(pos, Registry.ModBlockEntities.MONITOR_ADVANCED.get())
if (tile.width != 1 || tile.height != 1) {
context.fail("Tile has width and height of ${tile.width}x${tile.height}, but should be 1x1", pos)
}
}
}
}

View File

@ -0,0 +1,61 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.Structures
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.shared.Registry
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.CraftingRecipe
import net.minecraft.world.item.crafting.RecipeType
import org.junit.jupiter.api.Assertions.assertEquals
import java.util.*
@GameTestHolder
class Recipe_Test {
/**
* Test that crafting results contain NBT data.
*
* Mostly useful for Fabric, where we need a mixin for this.
*/
@GameTest(template = Structures.DEFAULT)
fun Craft_result_has_nbt(context: GameTestHelper) = context.sequence {
thenExecute {
val container = CraftingContainer(DummyMenu, 3, 3)
container.setItem(0, ItemStack(Items.SKELETON_SKULL))
container.setItem(1, ItemStack(Registry.ModItems.COMPUTER_ADVANCED.get()))
val recipe: Optional<CraftingRecipe> = context.level.server.recipeManager
.getRecipeFor(RecipeType.CRAFTING, container, context.level)
if (!recipe.isPresent) throw GameTestAssertException("No recipe matches")
val result = recipe.get().assemble(container)
val owner = CompoundTag()
owner.putString("Name", "dan200")
owner.putString("Id", "f3c8d69b-0776-4512-8434-d1b2165909eb")
val tag = CompoundTag()
tag.put("SkullOwner", owner)
assertEquals(tag, result.tag, "Expected NBT tags to be the same")
}
}
object DummyMenu : AbstractContainerMenu(MenuType.GENERIC_9x1, 0) {
override fun quickMoveStack(player: Player, slot: Int): ItemStack = ItemStack.EMPTY
override fun stillValid(p0: Player): Boolean = true
}
}

View File

@ -0,0 +1,252 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest
import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.api.detail.DetailRegistries
import dan200.computercraft.api.lua.ObjectArguments
import dan200.computercraft.core.apis.PeripheralAPI
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.Registry
import dan200.computercraft.shared.media.items.ItemPrintout
import dan200.computercraft.shared.peripheral.monitor.BlockMonitor
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState
import dan200.computercraft.shared.turtle.apis.TurtleAPI
import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.LuaTaskContext
import dan200.computercraft.test.core.computer.getApi
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.item.PrimedTnt
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FenceBlock
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.array
import org.hamcrest.Matchers.instanceOf
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import java.util.*
@GameTestHolder
class Turtle_Test {
@GameTest
fun Unequip_refreshes_peripheral(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<PeripheralAPI>().getType("right").assertArrayEquals("modem", message = "Starts with a modem")
getApi<TurtleAPI>().equipRight().await()
getApi<PeripheralAPI>().getType("right").assertArrayEquals("drive", message = "Unequipping gives a drive")
}
}
/**
* Checks turtles can sheer sheep (and drop items)
*
* @see [#537](https://github.com/cc-tweaked/CC-Tweaked/issues/537)
*/
@GameTest
fun Shears_sheep(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().placeDown(ObjectArguments()).await()
.assertArrayEquals(true, message = "Shears the sheep")
assertEquals("minecraft:white_wool", getTurtleItemDetail(2)["name"])
}
}
/**
* Checks turtles can place lava.
*
* @see [#518](https://github.com/cc-tweaked/CC-Tweaked/issues/518)
*/
@GameTest
fun Place_lava(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().placeDown(ObjectArguments()).await()
.assertArrayEquals(true, message = "Placed lava")
}
thenExecute { helper.assertBlockPresent(Blocks.LAVA, BlockPos(2, 2, 2)) }
}
/**
* Checks turtles can place when waterlogged.
*
* @see [#385](https://github.com/cc-tweaked/CC-Tweaked/issues/385)
*/
@GameTest
fun Place_waterlogged(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().place(ObjectArguments()).await()
.assertArrayEquals(true, message = "Placed oak fence")
}
thenExecute {
helper.assertBlockIs(BlockPos(2, 2, 2), { it.block == Blocks.OAK_FENCE && it.getValue(FenceBlock.WATERLOGGED) })
}
}
/**
* Checks turtles can pick up lava
*
* @see [#297](https://github.com/cc-tweaked/CC-Tweaked/issues/297)
*/
@GameTest
fun Gather_lava(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().placeDown(ObjectArguments()).await()
.assertArrayEquals(true, message = "Picked up lava")
assertEquals("minecraft:lava_bucket", getTurtleItemDetail()["name"])
}
thenExecute { helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 2)) }
}
/**
* Checks turtles can hoe dirt.
*
* @see [#258](https://github.com/cc-tweaked/CC-Tweaked/issues/258)
*/
@GameTest
fun Hoe_dirt(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().dig(Optional.empty()).await()
.assertArrayEquals(true, message = "Dug with hoe")
}
thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 2, 1)) }
}
/**
* Checks turtles can place monitors
*
* @see [#691](https://github.com/cc-tweaked/CC-Tweaked/issues/691)
*/
@GameTest
fun Place_monitor(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().place(ObjectArguments()).await()
.assertArrayEquals(true, message = "Block was placed")
}
thenIdle(1)
thenExecute { helper.assertBlockHas(BlockPos(1, 2, 3), BlockMonitor.STATE, MonitorEdgeState.LR) }
}
/**
* Checks turtles can place into compostors. These are non-typical inventories, so
* worth testing.
*/
@GameTest
fun Use_compostors(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
getApi<TurtleAPI>().dropDown(Optional.empty()).await()
.assertArrayEquals(true, message = "Item was dropped")
assertEquals(63, getApi<TurtleAPI>().getItemCount(Optional.of(1)), "Only dropped one item")
}
}
/**
* Checks turtles can be cleaned in cauldrons.
*
* Currently not required as turtles can no longer right-click cauldrons.
*/
@GameTest(required = false)
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
val details = getTurtleItemDetail(1, true)
getApi<TurtleAPI>().place(ObjectArguments()).await()
.assertArrayEquals(true, message = "Used item on cauldron")
val newDetails = getTurtleItemDetail(1, true)
assertEquals("computercraft:turtle_normal", newDetails["name"], "Still a turtle")
assertNotEquals(details["nbt"], newDetails["nbt"], "Colour should have changed")
}
}
/**
* Checks turtles can use IDetailProviders by getting details for a printed page.
*/
@GameTest
fun Item_detail_provider(helper: GameTestHelper) = helper.sequence {
// Register a dummy provider for printout items
thenExecute {
DetailRegistries.ITEM_STACK.addProvider(
object :
BasicItemDetailProvider<ItemPrintout>("printout", ItemPrintout::class.java) {
override fun provideDetails(data: MutableMap<in String, Any>, stack: ItemStack, item: ItemPrintout) {
data["type"] = item.type.toString().lowercase()
}
},
)
}
thenOnComputer {
val details = getTurtleItemDetail(detailed = true)
assertEquals(mapOf("type" to "page"), details["printout"]) {
"Printout information is returned (whole map is $details)"
}
}
}
/**
* Advanced turtles resist all explosions but normal ones don't.
*/
@GameTest
fun Resists_explosions(helper: GameTestHelper) = helper.sequence {
thenExecute {
val pos = helper.absolutePos(BlockPos(2, 2, 2))
val tnt = PrimedTnt(helper.level, pos.x + 0.5, pos.y + 1.0, pos.z + 0.5, null)
tnt.fuse = 1
helper.level.addFreshEntity(tnt)
}
thenWaitUntil { helper.assertEntityNotPresent(EntityType.TNT) }
thenExecute {
helper.assertBlockPresent(Registry.ModBlocks.TURTLE_ADVANCED.get(), BlockPos(2, 2, 2))
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 1))
}
}
/**
* Turtles resist mob explosions
*/
@GameTest
fun Resists_entity_explosions(helper: GameTestHelper) = helper.sequence {
thenExecute { helper.getEntity(EntityType.CREEPER).ignite() }
thenWaitUntil { helper.assertEntityNotPresent(EntityType.CREEPER) }
thenExecute {
helper.assertBlockPresent(Registry.ModBlocks.TURTLE_ADVANCED.get(), BlockPos(2, 2, 2))
helper.assertBlockPresent(Registry.ModBlocks.TURTLE_NORMAL.get(), BlockPos(2, 2, 1))
}
}
/**
* Test calling `turtle.drop` into an inventory.
*/
@GameTest
fun Drop_to_chest(helper: GameTestHelper) = helper.sequence {
val turtle = BlockPos(2, 2, 2)
val chest = BlockPos(2, 2, 3)
thenOnComputer {
getApi<TurtleAPI>().drop(Optional.of(32)).await()
.assertArrayEquals(true, message = "Could not drop items")
}
thenExecute {
helper.assertContainerExactly(turtle, listOf(ItemStack(Blocks.DIRT, 32), ItemStack.EMPTY, ItemStack(Blocks.DIRT, 32)))
helper.assertContainerExactly(chest, listOf(ItemStack(Blocks.DIRT, 48)))
}
}
// TODO: Ghost peripherals?
// TODO: Dropping into minecarts
// TODO: Turtle sucking from items
}
private suspend fun LuaTaskContext.getTurtleItemDetail(slot: Int = 1, detailed: Boolean = false): Map<String, *> {
val item = getApi<TurtleAPI>().getItemDetail(context, Optional.of(slot), Optional.of(detailed)).await()
assertThat("Returns details", item, array(instanceOf(Map::class.java)))
@Suppress("UNCHECKED_CAST")
return item!![0] as Map<String, *>
}

View File

@ -0,0 +1,222 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest.api
import dan200.computercraft.gametest.core.ManagedComputers
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
import dan200.computercraft.mixin.gametest.GameTestSequenceAccessor
import dan200.computercraft.test.core.computer.LuaTaskContext
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.*
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType
import net.minecraft.world.item.ItemStack
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.Property
import net.minecraftforge.registries.ForgeRegistries
/**
* Globally usable structures.
*
* @see GameTest.template
*/
object Structures {
/** The "default" structure, a 5x5 area with a polished Andesite floor */
const val DEFAULT = "default"
}
/** Pre-set in-game times */
object Times {
const val NOON: Long = 6000
}
/**
* Custom timeouts for various test types.
*
* @see GameTest.timeoutTicks
*/
object Timeouts {
private const val SECOND: Int = 20
const val COMPUTER_TIMEOUT: Int = SECOND * 15
}
/**
* Equivalent to [GameTestSequence.thenExecute], but which won't run the next steps if the parent fails.
*/
fun GameTestSequence.thenExecuteFailFast(task: Runnable): GameTestSequence =
thenExecute(task).thenWaitUntil {
val failure = (this as GameTestSequenceAccessor).parent.error
if (failure != null) throw failure
}
/**
* Wait until a computer has finished running and check it is OK.
*/
fun GameTestSequence.thenComputerOk(name: String? = null, marker: String = ComputerState.DONE): GameTestSequence {
val label = (this as GameTestSequenceAccessor).parent.testName + (if (name == null) "" else ".$name")
thenWaitUntil {
val computer = ComputerState.get(label)
if (computer == null || !computer.isDone(marker)) throw GameTestAssertException("Computer '$label' has not reached $marker yet.")
}
thenExecuteFailFast { ComputerState.get(label)!!.check(marker) }
return this
}
/**
* Run a task on a computer but don't wait for it to finish.
*/
fun GameTestSequence.thenStartComputer(name: String? = null, action: suspend LuaTaskContext.() -> Unit): GameTestSequence {
val test = (this as GameTestSequenceAccessor).parent
val label = test.testName + (if (name == null) "" else ".$name")
return thenExecuteFailFast { ManagedComputers.enqueue(test, label, action) }
}
/**
* Run a task on a computer and wait for it to finish.
*/
fun GameTestSequence.thenOnComputer(name: String? = null, action: suspend LuaTaskContext.() -> Unit): GameTestSequence {
val test = (this as GameTestSequenceAccessor).parent
val label = test.testName + (if (name == null) "" else ".$name")
var monitor: ManagedComputers.Monitor? = null
thenExecuteFailFast { monitor = ManagedComputers.enqueue(test, label, action) }
thenWaitUntil { if (!monitor!!.isFinished) throw GameTestAssertException("Computer '$label' has not finished yet.") }
thenExecuteFailFast { monitor!!.check() }
return this
}
/**
* Create a new game test sequence
*/
fun GameTestHelper.sequence(run: GameTestSequence.() -> Unit) {
val sequence = startSequence()
run(sequence)
sequence.thenSucceed()
}
/**
* A custom instance of [GameTestAssertPosException] which allows for longer error messages.
*/
private class VerboseGameTestAssertPosException(message: String, absolutePos: BlockPos, relativePos: BlockPos, tick: Long) :
GameTestAssertPosException(message, absolutePos, relativePos, tick) {
override fun getMessageToShowAtBlock(): String = message!!.lineSequence().first()
}
/**
* Fail this test. Unlike [GameTestHelper.fail], this trims the in-game error message to the first line.
*/
private fun GameTestHelper.failVerbose(message: String, pos: BlockPos): Nothing {
throw VerboseGameTestAssertPosException(message, absolutePos(pos), pos, tick)
}
/** Fail with an optional context message. */
private fun GameTestHelper.fail(message: String?, detail: String, pos: BlockPos): Nothing {
failVerbose(if (message.isNullOrEmpty()) detail else "$message: $detail", pos)
}
/**
* A version of [GameTestHelper.assertBlockState] which also includes the current block state.
*/
fun GameTestHelper.assertBlockIs(pos: BlockPos, predicate: (BlockState) -> Boolean, message: String = "") {
val state = getBlockState(pos)
if (!predicate(state)) fail(message, state.toString(), pos)
}
/**
* A version of [GameTestHelper.assertBlockProperty] which includes the current block state in the error message.
*/
fun <T : Comparable<T>> GameTestHelper.assertBlockHas(pos: BlockPos, property: Property<T>, value: T, message: String = "") {
val state = getBlockState(pos)
if (!state.hasProperty(property)) {
val id = ForgeRegistries.BLOCKS.getKey(state.block)
fail(message, "block $id does not have property ${property.name}", pos)
} else if (state.getValue(property) != value) {
fail(message, "${property.name} is ${state.getValue(property)}, expected $value", pos)
}
}
/**
* Assert a container contains exactly these items and no more.
*
* @param pos The position of the container.
* @param items The list of items this container must contain. This should be equal to the expected contents of the
* first `n` slots - the remaining are required to be empty.
*/
fun GameTestHelper.assertContainerExactly(pos: BlockPos, items: List<ItemStack>) {
val container = getBlockEntity(pos) ?: failVerbose("Expected a container at $pos, found nothing", pos)
if (container !is Container) {
failVerbose("Expected a container at $pos, found ${getName(container.type)}", pos)
}
val slot = (0 until container.containerSize).indexOfFirst { slot ->
val expected = if (slot >= items.size) ItemStack.EMPTY else items[slot]
!ItemStack.matches(container.getItem(slot), expected)
}
if (slot >= 0) {
failVerbose(
"""
Items do not match (first mismatch at slot $slot).
Expected: $items
Container: ${(0 until container.containerSize).map { container.getItem(it) }.dropLastWhile { it.isEmpty }}
""".trimIndent(),
pos,
)
}
}
private fun getName(type: BlockEntityType<*>): ResourceLocation = ForgeRegistries.BLOCK_ENTITIES.getKey(type)!!
/**
* Get a [BlockEntity] of a specific type.
*/
fun <T : BlockEntity> GameTestHelper.getBlockEntity(pos: BlockPos, type: BlockEntityType<T>): T {
val tile = getBlockEntity(pos)
@Suppress("UNCHECKED_CAST")
return when {
tile == null -> failVerbose("Expected ${getName(type)}, but no tile was there", pos)
tile.type != type -> failVerbose("Expected ${getName(type)} but got ${getName(tile.type)}", pos)
else -> tile as T
}
}
/**
* Get all entities of a specific type within the test structure.
*/
fun <T : Entity> GameTestHelper.getEntities(type: EntityType<T>): List<T> {
val info = (this as GameTestHelperAccessor).testInfo
return level.getEntities(type, info.structureBounds!!) { it.isAlive }
}
/**
* Get an [Entity] inside the game structure, requiring there to be a single one.
*/
fun <T : Entity> GameTestHelper.getEntity(type: EntityType<T>): T {
val entities = getEntities(type)
when (entities.size) {
0 -> throw GameTestAssertException("No $type entities")
1 -> return entities[0]
else -> throw GameTestAssertException("Multiple $type entities (${entities.size} in bounding box)")
}
}
/**
* Set a block within the test structure.
*/
fun GameTestHelper.setBlock(pos: BlockPos, state: BlockInput) = state.place(level, absolutePos(pos), 3)
/**
* Modify a block state within the test.
*/
fun GameTestHelper.modifyBlock(pos: BlockPos, modify: (BlockState) -> BlockState) {
setBlock(pos, modify(getBlockState(pos)))
}

View File

@ -0,0 +1,137 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.gametest.core
import dan200.computercraft.api.lua.ILuaAPI
import dan200.computercraft.core.apis.OSAPI
import dan200.computercraft.core.lua.CobaltLuaMachine
import dan200.computercraft.core.lua.ILuaMachine
import dan200.computercraft.core.lua.MachineEnvironment
import dan200.computercraft.core.lua.MachineResult
import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
import dan200.computercraft.shared.computer.core.ServerContext
import dan200.computercraft.test.core.computer.KotlinLuaMachine
import dan200.computercraft.test.core.computer.LuaTaskContext
import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestAssertPosException
import net.minecraft.gametest.framework.GameTestInfo
import net.minecraft.gametest.framework.GameTestSequence
import org.apache.logging.log4j.LogManager
import java.io.InputStream
import java.util.*
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.atomic.AtomicReference
/**
* Provides a custom [ILuaMachine] which allows computers to run Kotlin or Lua code, depending on their ID.
*
* This allows writing game tests which consume Lua APIs, without having the overhead of starting a new computer for
* each test.
*
* @see GameTestSequence.thenOnComputer
*/
object ManagedComputers : ILuaMachine.Factory {
private val LOGGER = LogManager.getLogger(ManagedComputers::class.java)
private val computers: MutableMap<String, Queue<suspend LuaTaskContext.() -> Unit>> = mutableMapOf()
internal fun enqueue(test: GameTestInfo, label: String, task: suspend LuaTaskContext.() -> Unit): Monitor {
val monitor = Monitor(test, label)
computers.computeIfAbsent(label) { ConcurrentLinkedDeque() }.add {
try {
LOGGER.info("Running $label")
task()
monitor.result.set(Result.success(Unit))
} catch (e: Throwable) {
if (e !is AssertionError) LOGGER.error("Computer $label failed", e)
monitor.result.set(Result.failure(e))
throw e
} finally {
LOGGER.info("Finished $label")
}
}
ServerContext.get(test.level.server).registry().computers
.firstOrNull { it.label == label }?.queueEvent("test_wakeup")
return monitor
}
override fun create(environment: MachineEnvironment): ILuaMachine = DelegateMachine(environment)
private class DelegateMachine(private val environment: MachineEnvironment) : ILuaMachine {
private val apis = mutableListOf<ILuaAPI>()
private var delegate: ILuaMachine? = null
override fun addAPI(api: ILuaAPI) {
val delegate = this.delegate
if (delegate != null) return delegate.addAPI(api)
apis.add(api)
if (api is OSAPI) {
val newMachine = if (api.computerID != 1) {
CobaltLuaMachine(environment)
} else if (api.computerLabel != null) {
KotlinMachine(environment, api.computerLabel[0] as String)
} else {
LOGGER.error("Kotlin Lua machine must have a label")
CobaltLuaMachine(environment)
}
this.delegate = newMachine
for (api in apis) newMachine.addAPI(api)
}
}
override fun loadBios(bios: InputStream): MachineResult {
val delegate = this.delegate ?: return MachineResult.error("Computer not created")
return delegate.loadBios(bios)
}
override fun handleEvent(eventName: String?, arguments: Array<out Any>?): MachineResult {
val delegate = this.delegate ?: return MachineResult.error("Computer not created")
return delegate.handleEvent(eventName, arguments)
}
override fun printExecutionState(out: StringBuilder) {
delegate?.printExecutionState(out)
}
override fun close() {
delegate?.close()
}
}
private class KotlinMachine(environment: MachineEnvironment, private val label: String) : KotlinLuaMachine(environment) {
override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? = computers[label]?.poll()
}
class Monitor(private val test: GameTestInfo, private val label: String) {
internal val result = AtomicReference<Result<Unit>>()
val isFinished
get() = result.get() != null
fun check() {
val result = result.get() ?: fail("Computer $label did not finish")
val error = result.exceptionOrNull()
if (error != null) fail(error.message ?: error.toString())
}
private fun fail(message: String): Nothing {
val computer =
ServerContext.get(test.level.server).registry().computers.firstOrNull { it.label == label }
if (computer == null) {
throw GameTestAssertException(message)
} else {
val pos = computer.position
val relativePos = pos.subtract(test.structureBlockPos)
throw GameTestAssertPosException(message, pos, relativePos, (test as GameTestInfoAccessor).`computercraft$getTick`())
}
}
}
}

View File

@ -1,6 +0,0 @@
public net.minecraft.gametest.framework.TestCommand m_128010_(Lnet/minecraft/commands/CommandSourceStack;Ljava/lang/String;)I # exportTestStructure
public net.minecraft.gametest.framework.GameTestHelper m_177448_()Lnet/minecraft/world/phys/AABB; # getBounds
public net.minecraft.gametest.framework.GameTestHelper f_127595_ # testInfo
public net.minecraft.gametest.framework.GameTestSequence f_127774_ # parent

View File

@ -16,3 +16,10 @@ displayName="CC: Tweaked test framework"
description='''
A test framework for ensuring CC: Tweaked works correctly.
'''
[[dependencies.cctest]]
modId="computercraft"
mandatory=true
versionRange="[1.0,)"
ordering="AFTER"
side="BOTH"

View File

@ -0,0 +1,16 @@
{
"required": true,
"package": "dan200.computercraft.mixin.gametest",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_17",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"GameTestHelperAccessor",
"GameTestInfoAccessor",
"GameTestSequenceAccessor",
"GameTestSequenceMixin",
"TestCommandAccessor"
]
}

View File

@ -0,0 +1,141 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:lever{face:floor,facing:south,powered:false}"},
{pos: [2, 1, 1], state: "minecraft:repeater{delay:1,facing:north,locked:false,powered:false}"},
{pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:north,state:off}", nbt: {ComputerId: 0, Label: "computer_test.no_through_signal_rev", On: 0b, id: "computercraft:computer_advanced"}},
{pos: [2, 1, 3], state: "minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}"},
{pos: [2, 1, 4], state: "minecraft:redstone_lamp{lit:false}"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:redstone_lamp{lit:false}",
"minecraft:air",
"minecraft:lever{face:floor,facing:south,powered:false}",
"minecraft:repeater{delay:1,facing:north,locked:false,powered:false}",
"minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}",
"computercraft:computer_advanced{facing:north,state:off}"
]
}

View File

@ -0,0 +1,138 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:computer_advanced{facing:north,state:on}", nbt: {ComputerId: 1, Label: "computer_test.set_and_destroy", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [2, 1, 3], state: "minecraft:redstone_lamp{lit:false}"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:redstone_lamp{lit:false}",
"minecraft:air",
"computercraft:computer_advanced{facing:north,state:on}"
]
}

View File

@ -0,0 +1,136 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air"
]
}

View File

@ -15,7 +15,7 @@
{pos: [0, 1, 1], state: "computercraft:disk_drive{facing:north,state:full}", nbt: {Item: {Count: 1b, id: "minecraft:music_disc_13"}, id: "computercraft:disk_drive"}},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "disk_drive_test.audio_disk", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [1, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "disk_drive_test.audio_disk", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},

View File

@ -43,7 +43,7 @@
{pos: [2, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "disk_drive_test.ejects_disk", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [3, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "disk_drive_test.ejects_disk", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [3, 1, 2], state: "minecraft:white_stained_glass"},
{pos: [3, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [3, 1, 4], state: "minecraft:air"},

View File

@ -48,7 +48,7 @@
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "modem_test.gains_peripherals", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [4, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "modem_test.gains_peripherals", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [4, 1, 2], state: "computercraft:cable{cable:true,down:false,east:false,modem:north_off,north:true,south:false,up:false,waterlogged:false,west:true}", nbt: {PeirpheralAccess: 0b, id: "computercraft:cable"}},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},

View File

@ -49,7 +49,7 @@
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "modem_test.have_peripherals", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [4, 1, 2], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "modem_test.have_peripherals", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},

View File

@ -35,7 +35,7 @@
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "modem_test.transmits_messages.receive", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [1, 1, 3], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "modem_test.transmits_messages.receive", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "computercraft:cable{cable:true,down:false,east:true,modem:east_off,north:false,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
@ -43,7 +43,7 @@
{pos: [2, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:west_off,north:true,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 0, Label: "modem_test.transmits_messages.send", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [3, 1, 1], state: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "modem_test.transmits_messages.send", On: 1b, id: "computercraft:computer_advanced"}},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},

View File

@ -14,7 +14,7 @@
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {Color: 13388876, ComputerId: 0, display: {Name: '{"text":"Clean turtle"}'}}}], Label: "turtle_test.cleaned_with_cauldrons", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {Color: 13388876, ComputerId: 0, display: {Name: '{"text":"Clean turtle"}'}}}], Label: "turtle_test.cleaned_with_cauldrons", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 1], state: "minecraft:cauldron{level:3}"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},

View File

@ -0,0 +1,138 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 64b, Slot: 0b, id: "minecraft:dirt"}, {Count: 32b, Slot: 2b, id: "minecraft:dirt"}], Label: "turtle_test.drop_to_chest", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:chest{facing:west,type:single,waterlogged:false}", nbt: {Items: [{Count: 16b, Slot: 0b, id: "minecraft:dirt"}], id: "minecraft:chest"}},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}",
"minecraft:chest{facing:west,type:single,waterlogged:false}"
]
}

View File

@ -64,7 +64,7 @@
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:bucket"}], Label: "turtle_test.gather_lava", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:bucket"}], Label: "turtle_test.gather_lava", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},

View File

@ -14,7 +14,7 @@
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [], Label: "turtle_test.hoe_dirt", LeftUpgrade: "minecraft:diamond_hoe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.hoe_dirt", LeftUpgrade: "minecraft:diamond_hoe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 1], state: "minecraft:dirt"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},

View File

@ -14,7 +14,7 @@
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: "Example ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: "Example page"}}], Label: "turtle_test.item_detail_provider", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: "Example ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: "Example page"}}], Label: "turtle_test.item_detail_provider", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},

View File

@ -64,7 +64,7 @@
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:lava_bucket"}], Label: "turtle_test.place_lava", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:lava_bucket"}], Label: "turtle_test.place_lava", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},

View File

@ -34,7 +34,7 @@
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:monitor_advanced"}], Label: "turtle_test.place_monitor", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:monitor_advanced"}], Label: "turtle_test.place_monitor", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:dark_oak_planks"},
{pos: [2, 1, 0], state: "minecraft:air"},

View File

@ -38,7 +38,7 @@
{pos: [1, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:white_stained_glass"},
{pos: [2, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:true}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:oak_fence"}], Label: "turtle_test.place_waterlogged", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:true}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:oak_fence"}], Label: "turtle_test.place_waterlogged", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 2], state: "minecraft:water{level:0}"},
{pos: [2, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [2, 1, 4], state: "minecraft:air"},

View File

@ -0,0 +1,140 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {Fuel: 0, Items: [], On: 0b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 2], state: "computercraft:turtle_advanced{facing:south,waterlogged:false}", nbt: {Fuel: 0, Items: [], On: 0b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_advanced"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [
{blockPos: [1, 1, 2], pos: [1.5d, 1.0d, 2.5d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorDropChances: [0.085f, 0.085f, 0.085f, 0.085f], ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.0d, Name: "forge:step_height_addition"}, {Base: 0.25d, Name: "minecraft:generic.movement_speed"}, {Base: 0.08d, Name: "forge:entity_gravity"}, {Base: 16.0d, Modifiers: [{Amount: -0.0871987524284032d, Name: "Random spawn bonus", Operation: 1, UUID: [I; -1592956383, -599506679, -1812844190, 1076877318]}], Name: "minecraft:generic.follow_range"}], Brain: {memories: {}}, CanPickUpLoot: 0b, CanUpdate: 1b, DeathTime: 0s, ExplosionRadius: 3b, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, Fuse: 30s, HandDropChances: [0.085f, 0.085f], HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invulnerable: 0b, LeftHanded: 0b, Motion: [0.0d, -0.0784000015258789d, 0.0d], OnGround: 1b, PersistenceRequired: 0b, PortalCooldown: 0, Pos: [2.5d, -58.0d, 3.5d], Rotation: [143.85756f, 0.0f], UUID: [I; 1463798444, -662876850, -1423329658, 948503391], id: "minecraft:creeper", ignited: 0b}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}",
"computercraft:turtle_advanced{facing:south,waterlogged:false}"
]
}

View File

@ -0,0 +1,139 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:barrier"},
{pos: [0, 1, 1], state: "minecraft:barrier"},
{pos: [0, 1, 2], state: "minecraft:barrier"},
{pos: [0, 1, 3], state: "minecraft:barrier"},
{pos: [0, 1, 4], state: "minecraft:barrier"},
{pos: [1, 1, 0], state: "minecraft:barrier"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:barrier"},
{pos: [2, 1, 0], state: "minecraft:barrier"},
{pos: [2, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {Fuel: 0, Items: [], On: 0b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 2], state: "computercraft:turtle_advanced{facing:south,waterlogged:false}", nbt: {Fuel: 0, Items: [], On: 0b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_advanced"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:barrier"},
{pos: [3, 1, 0], state: "minecraft:barrier"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:barrier"},
{pos: [4, 1, 0], state: "minecraft:barrier"},
{pos: [4, 1, 1], state: "minecraft:barrier"},
{pos: [4, 1, 2], state: "minecraft:barrier"},
{pos: [4, 1, 3], state: "minecraft:barrier"},
{pos: [4, 1, 4], state: "minecraft:barrier"},
{pos: [0, 2, 0], state: "minecraft:barrier"},
{pos: [0, 2, 1], state: "minecraft:barrier"},
{pos: [0, 2, 2], state: "minecraft:barrier"},
{pos: [0, 2, 3], state: "minecraft:barrier"},
{pos: [0, 2, 4], state: "minecraft:barrier"},
{pos: [1, 2, 0], state: "minecraft:barrier"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:barrier"},
{pos: [2, 2, 0], state: "minecraft:barrier"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:barrier"},
{pos: [3, 2, 0], state: "minecraft:barrier"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:barrier"},
{pos: [4, 2, 0], state: "minecraft:barrier"},
{pos: [4, 2, 1], state: "minecraft:barrier"},
{pos: [4, 2, 2], state: "minecraft:barrier"},
{pos: [4, 2, 3], state: "minecraft:barrier"},
{pos: [4, 2, 4], state: "minecraft:barrier"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:barrier",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}",
"computercraft:turtle_advanced{facing:south,waterlogged:false}"
]
}

View File

@ -89,7 +89,7 @@
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:shears", tag: {Damage: 0}}], Label: "turtle_test.shears_sheep", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 3, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:shears", tag: {Damage: 0}}], Label: "turtle_test.shears_sheep", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},

View File

@ -39,7 +39,7 @@
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [], Label: "turtle_test.unequip_refreshes_peripheral", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, RightUpgrade: "computercraft:wireless_modem_normal", RightUpgradeNbt: {active: 0b}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.unequip_refreshes_peripheral", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, RightUpgrade: "computercraft:wireless_modem_normal", RightUpgradeNbt: {active: 0b}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},

View File

@ -64,7 +64,7 @@
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 0, Fuel: 0, Items: [{Count: 64b, Slot: 0b, id: "minecraft:spruce_sapling"}], Label: "turtle_test.use_compostors", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 2, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 64b, Slot: 0b, id: "minecraft:spruce_sapling"}], Label: "turtle_test.use_compostors", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},

View File

@ -0,0 +1,20 @@
{
"pools": [
{
"name": "main",
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "computercraft:treasure_disk",
"functions": [
{
"function": "minecraft:set_nbt",
"tag": "{\"Title\": \"Demo disk\", \"SubPath\": \"demo\", \"Colour\": 15905331}"
}
]
}
]
}
]
}

View File

@ -1,2 +0,0 @@
test.eq(true, disk.hasAudio("right"), "Has audio")
test.eq("C418 - 13", disk.getAudioTitle("right"), "Audio title")

View File

@ -1,18 +0,0 @@
local function check_peripherals(expected, msg)
local peripherals = peripheral.getNames()
table.sort(peripherals)
test.eq(table.concat(expected, ", "), table.concat(peripherals, ", "), msg)
end
check_peripherals({"back"}, "Has no peripherals on startup")
test.ok("initial")
os.pullEvent("peripheral")
sleep(0)
check_peripherals({
"back",
"monitor_1",
"printer_1",
}, "Gains new peripherals")

View File

@ -1,12 +0,0 @@
local function check_peripherals(expected, msg)
local peripherals = peripheral.getNames()
table.sort(peripherals)
test.eq(table.concat(expected, ", "), table.concat(peripherals, ", "), msg)
end
check_peripherals({
"monitor_0",
"printer_0",
"right",
}, "Starts with peripherals")

View File

@ -1,10 +0,0 @@
local modem = peripheral.find("modem")
modem.open(12)
local _, name, chan, reply, payload, distance = os.pullEvent("modem_message")
test.eq("left", name, "Modem name")
test.eq(12, chan, "Channel")
test.eq(34, reply, "Reply channel")
test.eq("Hello!", payload, "Payload")
test.eq(4, distance, "Distance") -- Why 4?!

View File

@ -1,5 +0,0 @@
local modem = peripheral.find("modem")
while true do
modem.transmit(12, 34, "Hello!")
sleep(1)
end

View File

@ -1,7 +0,0 @@
local old_details = turtle.getItemDetail(1, true)
test.assert(turtle.place(), "Dyed turtle")
local new_details = turtle.getItemDetail(1, true)
test.eq("computercraft:turtle_normal", new_details.name, "Still a turtle")
test.neq(old_details.nbt, new_details.nbt, "Colour has changed")

View File

@ -1,7 +0,0 @@
turtle.placeDown()
local item = turtle.getItemDetail()
test.eq("minecraft:lava_bucket", item.name)
local has_down, down = turtle.inspectDown()
test.eq(false, has_down, "Air below")

View File

@ -1,5 +0,0 @@
test.assert(turtle.dig())
local has_block, block = turtle.inspect()
test.assert(has_block, "Has block")
test.eq("minecraft:farmland", block.name)

View File

@ -1,7 +0,0 @@
test.ok("initial")
local details = turtle.getItemDetail(1, true)
test.assert(details, "Has details")
test.assert(details.printout, "Has printout meta")
test.eq("PAGE", details.printout.type)

View File

@ -1,5 +0,0 @@
test.assert(turtle.placeDown())
local ok, down = turtle.inspectDown()
test.assert(ok, "Has below")
test.eq("minecraft:lava", down.name, "Is lava")

View File

@ -1,6 +0,0 @@
test.assert(turtle.place())
local has_block, block = turtle.inspect()
test.assert(has_block, "Has block")
test.eq("computercraft:monitor_advanced", block.name)
test.eq("lr", block.state.state)

View File

@ -1,6 +0,0 @@
test.assert(turtle.place())
local has_block, block = turtle.inspect()
test.eq(true, has_block, "Has block")
test.eq("minecraft:oak_fence", block.name)
test.eq(true, block.state.waterlogged)

View File

@ -1,9 +0,0 @@
-- TurtleTest.`Shears sheep`
turtle.placeDown()
local item = turtle.getItemDetail(2)
if item == nil then test.fail("Got no item") end
test.eq("minecraft:white_wool", item.name)
test.ok()

View File

@ -1,3 +0,0 @@
test.eq("modem", peripheral.getType("right"), "Starts with a modem")
turtle.equipRight()
test.eq("drive", peripheral.getType("right"), "Unequipping gives a drive")

View File

@ -1 +0,0 @@
test.eq(true, turtle.dropDown(), "Drop items into compostor")

View File

@ -1,5 +0,0 @@
{
"computer": 0,
"peripheral.monitor": 1,
"peripheral.printer": 1
}

View File

@ -1,2 +0,0 @@
# Automatically generated EULA. Please don't use this for a real server.
eula=true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,45 +0,0 @@
# Minecraft server properties
allow-flight=false
allow-nether=true
broadcast-console-to-ops=true
broadcast-rcon-to-ops=true
difficulty=easy
enable-command-block=true
enable-query=false
enable-rcon=false
enforce-whitelist=false
force-gamemode=false
function-permission-level=2
gamemode=creative
generate-structures=false
generator-settings=
hardcore=false
level-name=world
level-seed=
level-type=flat
max-build-height=256
max-players=20
max-tick-time=60000
max-world-size=29999984
motd=A testing server
network-compression-threshold=256
online-mode=false
op-permission-level=4
player-idle-timeout=0
prevent-proxy-connections=false
pvp=true
query.port=25565
rcon.password=
rcon.port=25575
resource-pack=
resource-pack-sha1=
server-ip=
server-port=25565
snooper-enabled=true
spawn-animals=true
spawn-monsters=true
spawn-npcs=true
spawn-protection=16
use-native-transport=true
view-distance=10
white-list=false