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

Allow other mods to provide extra item/block details (#1101)

This commit is contained in:
Drew Edwards 2022-05-24 22:21:18 +01:00 committed by GitHub
parent 36635662f1
commit 8b89d88d04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 496 additions and 11 deletions

View File

@ -6,6 +6,7 @@
package dan200.computercraft; package dan200.computercraft;
import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI;
import dan200.computercraft.api.detail.IDetailProvider;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource; 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.core.filesystem.ResourceMount;
import dan200.computercraft.shared.*; import dan200.computercraft.shared.*;
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; 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.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.IDAssigner; import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.wired.WiredNode; import dan200.computercraft.shared.wired.WiredNode;
@ -168,6 +170,12 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
ApiFactories.register( factory ); ApiFactories.register( factory );
} }
@Override
public <T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider )
{
DetailProviders.registerProvider( type, provider );
}
@Nonnull @Nonnull
@Override @Override
public IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ) public IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )

View File

@ -5,6 +5,8 @@
*/ */
package dan200.computercraft.api; 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.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource; 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.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider; import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction; import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader; import net.minecraft.world.IBlockReader;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -221,6 +225,20 @@ public final class ComputerCraftAPI
getInstance().registerAPIFactory( factory ); 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. * Construct a new wired node for a given wired element.
* *
@ -301,6 +319,8 @@ public final class ComputerCraftAPI
void registerAPIFactory( @Nonnull ILuaAPIFactory factory ); void registerAPIFactory( @Nonnull ILuaAPIFactory factory );
<T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider );
@Nonnull @Nonnull
IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ); 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.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 <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,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;
}
}

View File

@ -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 <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

@ -8,11 +8,11 @@ package dan200.computercraft.shared.computer.apis;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer; import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.peripheral.generic.data.BlockData; import dan200.computercraft.shared.peripheral.generic.data.BlockData;
import dan200.computercraft.shared.util.NBTUtil; import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.block.BlockState;
import net.minecraft.command.CommandSource; import net.minecraft.command.CommandSource;
import net.minecraft.command.Commands; import net.minecraft.command.Commands;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
@ -77,10 +77,10 @@ public class CommandAPI implements ILuaAPI
private static Map<?, ?> getBlockInfo( World world, BlockPos pos ) private static Map<?, ?> getBlockInfo( World world, BlockPos pos )
{ {
// Get the details of the block // Get the details of the block
BlockState state = world.getBlockState( pos ); BlockReference block = new BlockReference( world, pos );
Map<String, Object> table = BlockData.fill( new HashMap<>(), state ); Map<String, Object> 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() ) ) ); if( tile != null ) table.put( "nbt", NBTUtil.toLua( tile.save( new CompoundNBT() ) ) );
return table; return table;

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.shared.peripheral.generic.data; package dan200.computercraft.shared.peripheral.generic.data;
import dan200.computercraft.api.detail.BlockReference;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.state.Property; import net.minecraft.state.Property;
@ -15,8 +16,10 @@ import java.util.Map;
public class BlockData public class BlockData
{ {
@Nonnull @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.getState();
data.put( "name", DataHelpers.getId( state.getBlock() ) ); data.put( "name", DataHelpers.getId( state.getBlock() ) );
Map<Object, Object> stateTable = new HashMap<>(); Map<Object, Object> stateTable = new HashMap<>();
@ -28,6 +31,8 @@ public class BlockData
data.put( "state", stateTable ); data.put( "state", stateTable );
data.put( "tags", DataHelpers.getTags( state.getBlock().getTags() ) ); data.put( "tags", DataHelpers.getTags( state.getBlock().getTags() ) );
DetailProviders.fillData( BlockReference.class, data, block );
return data; 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.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

@ -25,6 +25,9 @@ public class FluidData
{ {
fillBasic( data, stack ); fillBasic( data, stack );
data.put( "tags", DataHelpers.getTags( stack.getFluid().getTags() ) ); data.put( "tags", DataHelpers.getTags( stack.getFluid().getTags() ) );
DetailProviders.fillData( FluidStack.class, data, stack );
return data; return data;
} }
} }

View File

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

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.shared.turtle.core; package dan200.computercraft.shared.turtle.core;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleCommand; import dan200.computercraft.api.turtle.ITurtleCommand;
import dan200.computercraft.api.turtle.TurtleCommandResult; import dan200.computercraft.api.turtle.TurtleCommandResult;
@ -41,13 +42,14 @@ public class TurtleInspectCommand implements ITurtleCommand
BlockPos oldPosition = turtle.getPosition(); BlockPos oldPosition = turtle.getPosition();
BlockPos newPosition = oldPosition.relative( direction ); 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 ) ) if( state.getBlock().isAir( state, world, newPosition ) )
{ {
return TurtleCommandResult.failure( "No block to inspect" ); 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 );
// Fire the event, exiting if it is cancelled // Fire the event, exiting if it is cancelled
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction ); TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );

View File

@ -1,10 +1,11 @@
package dan200.computercraft.ingame package dan200.computercraft.ingame
import dan200.computercraft.ingame.api.GameTest import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.ingame.api.GameTestHelper import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.ingame.api.*
import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT
import dan200.computercraft.ingame.api.sequence import dan200.computercraft.shared.media.items.ItemPrintout
import dan200.computercraft.ingame.api.thenComputerOk import net.minecraft.item.ItemStack
class Turtle_Test { class Turtle_Test {
@GameTest(timeoutTicks = COMPUTER_TIMEOUT) @GameTest(timeoutTicks = COMPUTER_TIMEOUT)
@ -72,4 +73,25 @@ class Turtle_Test {
*/ */
@GameTest @GameTest
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { thenComputerOk() } 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

@ -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,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
}