1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 12:10:30 +00:00

Merge branch 'mc-1.16.x' into mc-1.18.x

This commit is contained in:
Jonathan Coates 2022-05-27 18:55:50 +01:00
commit 83a1af6526
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
26 changed files with 368 additions and 29 deletions

View File

@ -88,6 +88,23 @@ minecraft {
args '--mod', 'computercraft', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
}
testClient {
workingDirectory project.file('test-files/client')
parent runs.client
mods {
cctest {
source sourceSets.testMod
}
}
lazyToken('minecraft_classpath') {
(configurations.shade.copyRecursive().resolve() + configurations.testModExtra.copyRecursive().resolve())
.collect { it.absolutePath }
.join(File.pathSeparator)
}
}
gameTestServer {
workingDirectory project.file('test-files/server')

View File

@ -6,6 +6,7 @@
package dan200.computercraft;
import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI;
import dan200.computercraft.api.detail.IDetailProvider;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource;
@ -24,6 +25,7 @@ import dan200.computercraft.shared.BundledRedstone;
import dan200.computercraft.shared.MediaProviders;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
import dan200.computercraft.shared.peripheral.generic.data.DetailProviders;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.wired.WiredNode;
@ -155,6 +157,12 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
ApiFactories.register( factory );
}
@Override
public <T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider )
{
DetailProviders.registerProvider( type, provider );
}
@Nonnull
@Override
public IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )

View File

@ -5,6 +5,8 @@
*/
package dan200.computercraft.api;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.IDetailProvider;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource;
@ -20,10 +22,12 @@ import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -196,6 +200,20 @@ public final class ComputerCraftAPI
getInstance().registerAPIFactory( factory );
}
/**
* Registers a detail provider to provide additional details for blocks, fluids and items when inspected by methods
* such as {@code turtle.getItemDetail()} or {@code turtle.inspect()}.
*
* @param type The type of object that this provider can provide details for. Should be {@link BlockReference},
* {@link FluidStack} or {@link ItemStack}.
* @param provider The detail provider to register.
* @param <T> The type of object that this provider can provide details for.
*/
public static <T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider )
{
getInstance().registerDetailProvider( type, provider );
}
/**
* Construct a new wired node for a given wired element.
*
@ -272,6 +290,8 @@ public final class ComputerCraftAPI
void registerAPIFactory( @Nonnull ILuaAPIFactory factory );
<T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider );
@Nonnull
IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element );

View File

@ -0,0 +1,79 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.detail;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
*
* @param <T> The type the stack's item must have.
*/
public abstract class BasicItemDetailProvider<T> implements IDetailProvider<ItemStack>
{
private final Class<T> itemType;
private final String namespace;
/**
* Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
*
* @param itemType The type the stack's item must have.
* @param namespace The namespace to use for this provider.
*/
public BasicItemDetailProvider( String namespace, @Nonnull Class<T> itemType )
{
Objects.requireNonNull( itemType );
this.itemType = itemType;
this.namespace = namespace;
}
/**
* Create a new item detail provider. Meta will be inserted directly into the results.
*
* @param itemType The type the stack's item must have.
*/
public BasicItemDetailProvider( @Nonnull Class<T> itemType )
{
this( null, itemType );
}
/**
* Provide additional details for the given {@link Item} and {@link ItemStack}. This method is called by
* {@code turtle.getItemDetail()}. New properties should be added to the given {@link Map}, {@code data}.
*
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param stack The item stack to provide details for.
* @param item The item to provide details for.
*/
public abstract void provideDetails( @Nonnull Map<? super String, Object> data, @Nonnull ItemStack stack,
@Nonnull T item );
@Override
public void provideDetails( @Nonnull Map<? super String, Object> data, @Nonnull ItemStack stack )
{
Item item = stack.getItem();
if( !itemType.isInstance( item ) ) return;
// If `namespace` is specified, insert into a new data map instead of the existing one.
Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
provideDetails( child, stack, itemType.cast( item ) );
if( namespace != null )
{
data.put( namespace, child );
}
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A reference to a block in the world, used by block detail providers.
*
* @param level The level the block exists in.
* @param pos The position of the block.
* @param state The block state at this position.
* @param blockEntity The block entity at this position, if it exists.
*/
public record BlockReference(
@Nonnull Level level,
@Nonnull BlockPos pos,
@Nonnull BlockState state,
@Nullable BlockEntity blockEntity
)
{
public BlockReference( Level level, BlockPos pos )
{
this( level, pos, level.getBlockState( pos ), level.getBlockEntity( pos ) );
}
}

View File

@ -0,0 +1,32 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.detail;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* This interface is used to provide details about a block, fluid, or item.
*
* @param <T> The type of object that this provider can provide details for.
* @see dan200.computercraft.api.ComputerCraftAPI#registerDetailProvider(Class, IDetailProvider)
*/
@FunctionalInterface
public interface IDetailProvider<T>
{
/**
* Provide additional details for the given object. This method is called by functions such as
* {@code turtle.getItemDetail()} and {@code turtle.inspect()}. New properties should be added to the given
* {@link Map}, {@code data}.
*
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned. New properties should be added to this map.
* @param object The object to provide details for.
*/
void provideDetails( @Nonnull Map<? super String, Object> data, @Nonnull T object );
}

View File

@ -58,7 +58,7 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
protected abstract WidgetTerminal createTerminal();
@Override
protected final void init()
protected void init()
{
super.init();
minecraft.keyboardHandler.setSendRepeatsToGui( true );
@ -69,21 +69,21 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
}
@Override
public final void removed()
public void removed()
{
super.removed();
minecraft.keyboardHandler.setSendRepeatsToGui( false );
}
@Override
public final void containerTick()
public void containerTick()
{
super.containerTick();
terminal.update();
}
@Override
public final boolean keyPressed( int key, int scancode, int modifiers )
public boolean keyPressed( int key, int scancode, int modifiers )
{
// Forward the tab key to the terminal, rather than moving between controls.
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal )
@ -96,7 +96,7 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
@Override
public final void render( @Nonnull PoseStack stack, int mouseX, int mouseY, float partialTicks )
public void render( @Nonnull PoseStack stack, int mouseX, int mouseY, float partialTicks )
{
renderBackground( stack );
super.render( stack, mouseX, mouseY, partialTicks );
@ -114,7 +114,7 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
}
@Override
public final boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );

View File

@ -6,7 +6,10 @@
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaFunction;
import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.ObjectSource;
import dan200.computercraft.core.computer.Computer;
@ -14,7 +17,6 @@ import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.util.ThreadUtils;
import org.squiddev.cobalt.LuaTable;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LoadState;

View File

@ -6,7 +6,10 @@
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.asm.LuaMethod;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.debug.DebugFrame;

View File

@ -8,6 +8,7 @@ package dan200.computercraft.shared.computer.apis;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.peripheral.generic.data.BlockData;
@ -21,7 +22,6 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nonnull;
import java.util.*;
@ -76,10 +76,10 @@ public class CommandAPI implements ILuaAPI
private static Map<?, ?> getBlockInfo( Level world, BlockPos pos )
{
// Get the details of the block
BlockState state = world.getBlockState( pos );
Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
BlockReference block = new BlockReference( world, pos );
Map<String, Object> table = BlockData.fill( new HashMap<>(), block );
BlockEntity tile = world.getBlockEntity( pos );
BlockEntity tile = block.blockEntity();
if( tile != null ) table.put( "nbt", NBTUtil.toLua( tile.saveWithFullMetadata() ) );
return table;

View File

@ -5,6 +5,7 @@
*/
package dan200.computercraft.shared.peripheral.generic.data;
import dan200.computercraft.api.detail.BlockReference;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
@ -15,8 +16,10 @@ import java.util.Map;
public class BlockData
{
@Nonnull
public static <T extends Map<? super String, Object>> T fill( @Nonnull T data, @Nonnull BlockState state )
public static <T extends Map<? super String, Object>> T fill( @Nonnull T data, @Nonnull BlockReference block )
{
BlockState state = block.state();
data.put( "name", DataHelpers.getId( state.getBlock() ) );
Map<Object, Object> stateTable = new HashMap<>();
@ -28,6 +31,8 @@ public class BlockData
data.put( "state", stateTable );
data.put( "tags", DataHelpers.getTags( state.getTags() ) );
DetailProviders.fillData( BlockReference.class, data, block );
return data;
}

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.shared.peripheral.generic.data;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.IDetailProvider;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.fluids.FluidStack;
import java.util.*;
public final class DetailProviders
{
private static final Map<Class<?>, Collection<IDetailProvider<?>>> allProviders = new HashMap<>();
public static synchronized <T> void registerProvider( Class<T> type, IDetailProvider<T> provider )
{
Objects.requireNonNull( type, "type cannot be null" );
Objects.requireNonNull( provider, "provider cannot be null" );
if( type != BlockReference.class && type != ItemStack.class && type != FluidStack.class )
{
throw new IllegalArgumentException( "type must be assignable from BlockReference, ItemStack or FluidStack" );
}
allProviders.computeIfAbsent( type, k -> new LinkedHashSet<>() ).add( provider );
}
@SuppressWarnings( "unchecked" )
public static <T> void fillData( Class<T> type, Map<? super String, Object> data, T value )
{
Collection<IDetailProvider<T>> providers = (Collection<IDetailProvider<T>>) (Object) allProviders.get( type );
if( providers == null ) return;
for( IDetailProvider<T> provider : providers )
{
provider.provideDetails( data, value );
}
}
}

View File

@ -24,10 +24,13 @@ public class FluidData
public static <T extends Map<? super String, Object>> T fill( @Nonnull T data, @Nonnull FluidStack stack )
{
fillBasic( data, stack );
// FluidStack doesn't have a getTags method, so we need to use the deprecated builtInRegistryHolder.
@SuppressWarnings( "deprecation" )
var holder = stack.getFluid().builtInRegistryHolder();
data.put( "tags", DataHelpers.getTags( holder ) );
DetailProviders.fillData( FluidStack.class, data, stack );
return data;
}
}

View File

@ -97,6 +97,8 @@ public class ItemData
data.put( "unbreakable", true );
}
DetailProviders.fillData( ItemStack.class, data, stack );
return data;
}

View File

@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.printer;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.util.SingleIntArray;
import dan200.computercraft.shared.util.ValidatingSlot;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
@ -33,13 +34,16 @@ public class ContainerPrinter extends AbstractContainerMenu
addDataSlots( properties );
// Ink slot
addSlot( new Slot( inventory, 0, 13, 35 ) );
addSlot( new ValidatingSlot( inventory, 0, 13, 35, TilePrinter::isInk ) );
// In-tray
for( int x = 0; x < 6; x++ ) addSlot( new Slot( inventory, x + 1, 61 + x * 18, 22 ) );
for( int x = 0; x < 6; x++ )
{
addSlot( new ValidatingSlot( inventory, x + 1, 61 + x * 18, 22, TilePrinter::isPaper ) );
}
// Out-tray
for( int x = 0; x < 6; x++ ) addSlot( new Slot( inventory, x + 7, 61 + x * 18, 49 ) );
for( int x = 0; x < 6; x++ ) addSlot( new ValidatingSlot( inventory, x + 7, 61 + x * 18, 49, o -> false ) );
// Player inv
for( int y = 0; y < 3; y++ )

View File

@ -308,7 +308,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
return ColourUtils.getStackColour( stack ) != null;
}
private static boolean isPaper( @Nonnull ItemStack stack )
static boolean isPaper( @Nonnull ItemStack stack )
{
Item item = stack.getItem();
return item == Items.PAPER

View File

@ -5,6 +5,7 @@
*/
package dan200.computercraft.shared.turtle.core;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleCommand;
import dan200.computercraft.api.turtle.TurtleCommandResult;
@ -12,7 +13,6 @@ import dan200.computercraft.shared.peripheral.generic.data.BlockData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nonnull;
import java.util.HashMap;
@ -39,10 +39,10 @@ public class TurtleInspectCommand implements ITurtleCommand
BlockPos oldPosition = turtle.getPosition();
BlockPos newPosition = oldPosition.relative( direction );
BlockState state = world.getBlockState( newPosition );
if( state.isAir() ) return TurtleCommandResult.failure( "No block to inspect" );
BlockReference block = new BlockReference( world, newPosition );
if( block.state().isAir() ) return TurtleCommandResult.failure( "No block to inspect" );
Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
Map<String, Object> table = BlockData.fill( new HashMap<>(), block );
return TurtleCommandResult.success( new Object[] { table } );

View File

@ -10,17 +10,21 @@ import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nonnull;
import java.util.function.Predicate;
public class ValidatingSlot extends Slot
{
public ValidatingSlot( Container inventoryIn, int index, int xPosition, int yPosition )
private final Predicate<ItemStack> predicate;
public ValidatingSlot( Container inventoryIn, int index, int xPosition, int yPosition, Predicate<ItemStack> predicate )
{
super( inventoryIn, index, xPosition, yPosition );
this.predicate = predicate;
}
@Override
public boolean mayPlace( @Nonnull ItemStack stack )
{
return true; // inventory.isItemValidForSlot( slotNumber, stack );
return predicate.test( stack );
}
}

View File

@ -107,7 +107,7 @@ class AsyncRunner : NullApiEnvironment() {
private val empty: Array<Any?> = arrayOf()
@OptIn(ExperimentalTime::class)
fun runTest(timeout: Duration = Duration.seconds(5), fn: suspend AsyncRunner.() -> Unit) {
fun runTest(timeout: Duration = 5.seconds, fn: suspend AsyncRunner.() -> Unit) {
runBlocking {
val runner = AsyncRunner()
try {

View File

@ -85,7 +85,7 @@ public class ComputerThreadTest
assertEquals( budget, TimeUnit.MILLISECONDS.toNanos( 25 ), "Budget should be 25ms" );
long delay = ConcurrentHelpers.waitUntil( timeout::isPaused );
assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.03, 0.015 ) );
assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.03, 0.02 ) );
computer.shutdown();
return MachineResult.OK;

View File

@ -1,11 +1,15 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenComputerOk
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)
@ -75,4 +79,25 @@ class Turtle_Test {
*/
@GameTest(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
ComputerCraftAPI.registerDetailProvider(
ItemStack::class.java,
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

@ -29,9 +29,11 @@ object Times {
* Custom timeouts for various test types.
*/
object Timeouts {
const val COMPUTER_TIMEOUT: Int = 200
private const val SECOND: Int = 20
const val CLIENT_TIMEOUT: Int = 400
const val COMPUTER_TIMEOUT: Int = SECOND * 15
const val CLIENT_TIMEOUT: Int = SECOND * 20
}
/**

View File

@ -6,6 +6,7 @@
package dan200.computercraft.ingame.mod;
import com.google.common.io.MoreFiles;
import com.google.common.io.RecursiveDeleteOption;
import java.io.IOException;
import java.nio.file.FileVisitResult;
@ -55,7 +56,7 @@ final class Copier extends SimpleFileVisitor<Path>
public static void replicate( Path from, Path to, Predicate<Path> check ) throws IOException
{
if( Files.exists( to ) ) MoreFiles.deleteRecursively( to );
if( Files.exists( to ) ) MoreFiles.deleteRecursively( to, RecursiveDeleteOption.ALLOW_INSECURE );
Files.walkFileTree( from, new Copier( from, to, check ) );
}
}

View File

@ -4,6 +4,14 @@ if label == nil then return test.fail("Label a computer to use it.") end
local fn, err = loadfile("tests/" .. label .. ".lua", nil, _ENV)
if not fn then return test.fail(err) end
local source = "@" .. label .. ".lua"
debug.sethook(function()
local i = debug.getinfo(2, "lS")
if i.source == source and i.currentline then
test.log("At line " .. i.currentline)
end
end, "l")
local ok, err = pcall(fn)
if not ok then return test.fail(err) end

View File

@ -0,0 +1,7 @@
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

@ -0,0 +1,39 @@
{
DataVersion: 2975,
size: [3, 3, 3],
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: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], 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: [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, 1], state: "minecraft:air"},
{pos: [1, 1, 2], 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: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], 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: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}"
]
}