diff --git a/build.gradle b/build.gradle index b4be8f8f7..025b6ed72 100644 --- a/build.gradle +++ b/build.gradle @@ -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') diff --git a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java index 75d67f048..8862fd377 100644 --- a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java +++ b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java @@ -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 void registerDetailProvider( @Nonnull Class type, @Nonnull IDetailProvider provider ) + { + DetailProviders.registerProvider( type, provider ); + } + @Nonnull @Override public IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ) diff --git a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java index 75d2a65e7..69b9de806 100644 --- a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java +++ b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java @@ -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 The type of object that this provider can provide details for. + */ + public static void registerDetailProvider( @Nonnull Class type, @Nonnull IDetailProvider 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 ); + void registerDetailProvider( @Nonnull Class type, @Nonnull IDetailProvider provider ); + @Nonnull IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ); diff --git a/src/main/java/dan200/computercraft/api/detail/BasicItemDetailProvider.java b/src/main/java/dan200/computercraft/api/detail/BasicItemDetailProvider.java new file mode 100644 index 000000000..a3974b6b4 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/BasicItemDetailProvider.java @@ -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 The type the stack's item must have. + */ +public abstract class BasicItemDetailProvider implements IDetailProvider +{ + private final Class 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 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 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 data, @Nonnull ItemStack stack, + @Nonnull T item ); + + @Override + public void provideDetails( @Nonnull Map 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 child = namespace == null ? data : new HashMap<>(); + + provideDetails( child, stack, itemType.cast( item ) ); + + if( namespace != null ) + { + data.put( namespace, child ); + } + } +} diff --git a/src/main/java/dan200/computercraft/api/detail/BlockReference.java b/src/main/java/dan200/computercraft/api/detail/BlockReference.java new file mode 100644 index 000000000..bcb90a21d --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/BlockReference.java @@ -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 ) ); + } +} diff --git a/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java b/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java new file mode 100644 index 000000000..c98efb3b5 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java @@ -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 The type of object that this provider can provide details for. + * @see dan200.computercraft.api.ComputerCraftAPI#registerDetailProvider(Class, IDetailProvider) + */ +@FunctionalInterface +public interface IDetailProvider +{ + /** + * 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 data, @Nonnull T object ); +} diff --git a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java index 5fdb743cd..62c0671da 100644 --- a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java +++ b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java @@ -58,7 +58,7 @@ public abstract class ComputerScreenBase 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 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 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 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 ); diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index dc2e1c23b..47718354b 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -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; diff --git a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java index 3590947ad..6bb9527a1 100644 --- a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java @@ -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; diff --git a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index de846075b..997763180 100644 --- a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -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 table = BlockData.fill( new HashMap<>(), state ); + BlockReference block = new BlockReference( world, pos ); + Map 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; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java index ee8389c58..90aef64f1 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java @@ -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 fill( @Nonnull T data, @Nonnull BlockState state ) + public static > T fill( @Nonnull T data, @Nonnull BlockReference block ) { + BlockState state = block.state(); + data.put( "name", DataHelpers.getId( state.getBlock() ) ); Map 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; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DetailProviders.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DetailProviders.java new file mode 100644 index 000000000..1628ab989 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DetailProviders.java @@ -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, Collection>> allProviders = new HashMap<>(); + + public static synchronized void registerProvider( Class type, IDetailProvider 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 void fillData( Class type, Map data, T value ) + { + Collection> providers = (Collection>) (Object) allProviders.get( type ); + if( providers == null ) return; + + for( IDetailProvider provider : providers ) + { + provider.provideDetails( data, value ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java index f2e4a473b..e04b774ee 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java @@ -24,10 +24,13 @@ public class FluidData public static > 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; } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java index 85ae8fade..2f8aa0c3c 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java @@ -97,6 +97,8 @@ public class ItemData data.put( "unbreakable", true ); } + DetailProviders.fillData( ItemStack.class, data, stack ); + return data; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java index 39ffd9fb4..ec082f987 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java @@ -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++ ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java index 98106b557..b289ef99a 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java @@ -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 diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java index 7c5c9f9bb..9c8e317c8 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java @@ -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 table = BlockData.fill( new HashMap<>(), state ); + Map table = BlockData.fill( new HashMap<>(), block ); return TurtleCommandResult.success( new Object[] { table } ); diff --git a/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java b/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java index ffc80678f..ab5309460 100644 --- a/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java +++ b/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java @@ -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 predicate; + + public ValidatingSlot( Container inventoryIn, int index, int xPosition, int yPosition, Predicate 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 ); } } diff --git a/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt b/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt index fd976eedb..efe522171 100644 --- a/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt +++ b/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt @@ -107,7 +107,7 @@ class AsyncRunner : NullApiEnvironment() { private val empty: Array = 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 { diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java index 70fcc3422..c6cb4060d 100644 --- a/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java +++ b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java @@ -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; diff --git a/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt b/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt index e4e05eb79..47f27b495 100644 --- a/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt +++ b/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt @@ -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("printout", ItemPrintout::class.java) { + override fun provideDetails(data: MutableMap, stack: ItemStack, item: ItemPrintout) { + data["type"] = item.type.toString(); + } + } + ) + } + .thenComputerOk() + } } diff --git a/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt b/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt index 919fb730e..fc494ba86 100644 --- a/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt +++ b/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt @@ -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 } /** diff --git a/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java b/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java index c4d544ef1..439ba152e 100644 --- a/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java +++ b/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java @@ -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 public static void replicate( Path from, Path to, Predicate 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 ) ); } } diff --git a/src/testMod/server-files/computers/computer/0/startup.lua b/src/testMod/server-files/computers/computer/0/startup.lua index b0b815745..b67ad6af7 100644 --- a/src/testMod/server-files/computers/computer/0/startup.lua +++ b/src/testMod/server-files/computers/computer/0/startup.lua @@ -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 diff --git a/src/testMod/server-files/computers/computer/0/tests/turtle_test.item_detail_provider.lua b/src/testMod/server-files/computers/computer/0/tests/turtle_test.item_detail_provider.lua new file mode 100644 index 000000000..76f4749f1 --- /dev/null +++ b/src/testMod/server-files/computers/computer/0/tests/turtle_test.item_detail_provider.lua @@ -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) diff --git a/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt b/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt new file mode 100644 index 000000000..0c8647206 --- /dev/null +++ b/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt @@ -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}" + ] +}