diff --git a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java index 08432f0ff..dfbb26ed5 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.core.filesystem.FileMount; import dan200.computercraft.core.filesystem.ResourceMount; import dan200.computercraft.shared.*; 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; @@ -168,6 +170,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 7daa7e6bf..dc00bb641 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,12 +22,14 @@ import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.redstone.IBundledRedstoneProvider; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; 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; @@ -221,6 +225,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. * @@ -301,6 +319,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..10398d8bd --- /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.item.Item; +import net.minecraft.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..03aa443cb --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/BlockReference.java @@ -0,0 +1,65 @@ +/* + * 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.block.BlockState; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A reference to a block in the world, used by block detail providers. + */ +public class BlockReference +{ + private final World world; + private final BlockPos pos; + private final BlockState state; + private final TileEntity blockEntity; + + public BlockReference( World world, BlockPos pos ) + { + this.world = world; + this.pos = pos; + this.state = world.getBlockState( pos ); + this.blockEntity = world.getBlockEntity( pos ); + } + + public BlockReference( World world, BlockPos pos, BlockState state, TileEntity blockEntity ) + { + this.world = world; + this.pos = pos; + this.state = state; + this.blockEntity = blockEntity; + } + + @Nonnull + public World getWorld() + { + return world; + } + + @Nonnull + public BlockPos getPos() + { + return pos; + } + + @Nonnull + public BlockState getState() + { + return state; + } + + @Nullable + public TileEntity getBlockEntity() + { + return blockEntity; + } +} 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..74841bc8c --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java @@ -0,0 +1,33 @@ +/* + * 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/shared/computer/apis/CommandAPI.java b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index af891135a..0c3e19697 100644 --- a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -8,11 +8,11 @@ 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; import dan200.computercraft.shared.util.NBTUtil; -import net.minecraft.block.BlockState; import net.minecraft.command.CommandSource; import net.minecraft.command.Commands; import net.minecraft.nbt.CompoundNBT; @@ -77,10 +77,10 @@ public class CommandAPI implements ILuaAPI private static Map getBlockInfo( World 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 ); - TileEntity tile = world.getBlockEntity( pos ); + TileEntity tile = block.getBlockEntity(); if( tile != null ) table.put( "nbt", NBTUtil.toLua( tile.save( new CompoundNBT() ) ) ); 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 8f6e3fba1..dde7ecbb1 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.block.BlockState; import net.minecraft.state.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.getState(); + 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.getBlock().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..313eecc18 --- /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.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 6c18aa6e3..0bc067179 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 @@ -25,6 +25,9 @@ public class FluidData { fillBasic( data, stack ); data.put( "tags", DataHelpers.getTags( stack.getFluid().getTags() ) ); + + 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 fbf723c7c..d39deec4a 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 @@ -98,6 +98,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/turtle/core/TurtleInspectCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java index 70bf8b15d..98cec50fd 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; @@ -41,13 +42,14 @@ public class TurtleInspectCommand implements ITurtleCommand BlockPos oldPosition = turtle.getPosition(); BlockPos newPosition = oldPosition.relative( direction ); - BlockState state = world.getBlockState( newPosition ); + BlockReference block = new BlockReference( world, newPosition ); + BlockState state = block.getState(); if( state.getBlock().isAir( state, world, newPosition ) ) { return TurtleCommandResult.failure( "No block to inspect" ); } - Map table = BlockData.fill( new HashMap<>(), state ); + Map table = BlockData.fill( new HashMap<>(), block ); // Fire the event, exiting if it is cancelled TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction ); diff --git a/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt b/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt index f37337b25..4f59fcaae 100644 --- a/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt +++ b/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt @@ -1,10 +1,11 @@ package dan200.computercraft.ingame -import dan200.computercraft.ingame.api.GameTest -import dan200.computercraft.ingame.api.GameTestHelper +import dan200.computercraft.api.ComputerCraftAPI +import dan200.computercraft.api.detail.BasicItemDetailProvider +import dan200.computercraft.ingame.api.* 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.item.ItemStack class Turtle_Test { @GameTest(timeoutTicks = COMPUTER_TIMEOUT) @@ -72,4 +73,25 @@ class Turtle_Test { */ @GameTest 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/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..986ef80ac --- /dev/null +++ b/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt @@ -0,0 +1,196 @@ +{ + size: [3, 3, 3], + entities: [], + blocks: [ + { + pos: [0, 0, 0], + state: 0 + }, + { + pos: [0, 0, 1], + state: 0 + }, + { + pos: [0, 0, 2], + state: 0 + }, + { + pos: [1, 0, 0], + state: 0 + }, + { + pos: [1, 0, 1], + state: 0 + }, + { + pos: [1, 0, 2], + state: 0 + }, + { + pos: [2, 0, 0], + state: 0 + }, + { + pos: [2, 0, 1], + state: 0 + }, + { + pos: [2, 0, 2], + state: 0 + }, + { + pos: [0, 1, 0], + state: 1 + }, + { + pos: [0, 1, 1], + state: 1 + }, + { + pos: [0, 1, 2], + state: 1 + }, + { + pos: [1, 1, 1], + state: 1 + }, + { + pos: [1, 1, 2], + state: 1 + }, + { + pos: [2, 1, 0], + state: 1 + }, + { + pos: [2, 1, 1], + state: 1 + }, + { + pos: [2, 1, 2], + state: 1 + }, + { + pos: [0, 2, 0], + state: 1 + }, + { + pos: [0, 2, 1], + state: 1 + }, + { + pos: [0, 2, 2], + state: 1 + }, + { + pos: [1, 2, 0], + state: 1 + }, + { + pos: [1, 2, 1], + state: 1 + }, + { + pos: [1, 2, 2], + state: 1 + }, + { + pos: [2, 2, 0], + state: 1 + }, + { + pos: [2, 2, 1], + state: 1 + }, + { + pos: [2, 2, 2], + state: 1 + }, + { + nbt: { + Owner: { + UpperId: 4039158846114182220L, + LowerId: -6876936588741668278L, + Name: "Dev" + }, + Fuel: 0, + Label: "turtle_test.item_detail_provider", + Slot: 0, + Items: [ + { + Slot: 0b, + id: "computercraft:printed_page", + Count: 1b, + tag: { + Text9: " ", + Text8: " ", + Text7: " ", + Text6: " ", + Text5: " ", + Text4: " ", + Text3: " ", + Text2: " ", + Text1: " ", + Text0: "Example ", + Color20: "fffffffffffffffffffffffff", + Text20: " ", + Title: "Example page", + Text18: " ", + Text19: " ", + Text16: " ", + Color2: "fffffffffffffffffffffffff", + Text17: " ", + Color1: "fffffffffffffffffffffffff", + Text14: " ", + Color0: "fffffffffffffffffffffffff", + Text15: " ", + Pages: 1, + Text12: " ", + Color6: "fffffffffffffffffffffffff", + Text13: " ", + Color5: "fffffffffffffffffffffffff", + Text10: " ", + Color4: "fffffffffffffffffffffffff", + Color11: "fffffffffffffffffffffffff", + Text11: " ", + Color3: "fffffffffffffffffffffffff", + Color10: "fffffffffffffffffffffffff", + Color13: "fffffffffffffffffffffffff", + Color9: "fffffffffffffffffffffffff", + Color12: "fffffffffffffffffffffffff", + Color8: "fffffffffffffffffffffffff", + Color15: "fffffffffffffffffffffffff", + Color7: "fffffffffffffffffffffffff", + Color14: "fffffffffffffffffffffffff", + Color17: "fffffffffffffffffffffffff", + Color16: "fffffffffffffffffffffffff", + Color19: "fffffffffffffffffffffffff", + Color18: "fffffffffffffffffffffffff" + } + } + ], + id: "computercraft:turtle_normal", + ComputerId: 0, + On: 1b + }, + pos: [1, 1, 0], + state: 2 + } + ], + palette: [ + { + Name: "minecraft:polished_andesite" + }, + { + Name: "minecraft:air" + }, + { + Properties: { + waterlogged: "false", + facing: "south" + }, + Name: "computercraft:turtle_normal" + } + ], + DataVersion: 2586 +}