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

Post multi-loader cleanup

This commit got away from me, okay? No, I'm not proud of it either.

 - Remove our overrides of handleUpdate tag: we now try to detect
   whether we're on the client or server inside BlockEntity.load. Alas,
   this is needed for Fabric.

 - Remove BlockGeneric/TileGeneric entirely: we've slowly whittled this
   down over the years, and nowadays we can get away with putting most
   of its functionality into subclasses.

   This allows us to do some nice things with overriding HorizontalBlock
   (or our new HorizontalContainerBlock class), rather than
   reimplementing functionality in each class. Though it would be nice
   if Java had some sort of trait system :D:

 - Simplify a lot of our container class so it's just defined in terms
   of a NonNullList<ItemStack>. This also includes a total rewrite of
   the disk drive which I'm not ... thrilled about. It ended up being
   easier to copy the code from the mc-next branch :D:.

 - Try to test some of the gnarly bits of this. Still a /lot/ more to be
   done with testing this.

Closes #658
This commit is contained in:
Jonathan Coates 2022-11-10 15:48:26 +00:00
parent 77624fc6fd
commit 8360e8234d
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
68 changed files with 2070 additions and 1153 deletions

View File

@ -117,6 +117,7 @@ fun appendUnique(x: String) {
if (newClasses.any { it.startsWith("cctest%%") }) {
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testMod"))
}

View File

@ -32,5 +32,6 @@ dependencies {
testRuntimeOnly(libs.bundles.testRuntime)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)
}

View File

@ -27,11 +27,8 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
private static final int TEX_WIDTH = 254;
private static final int TEX_HEIGHT = 217;
private final ComputerFamily family;
public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
super(container, player, title, BORDER);
family = container.getFamily();
imageWidth = TEX_WIDTH + AbstractComputerMenu.SIDEBAR_WIDTH;
imageHeight = TEX_HEIGHT;

View File

@ -107,7 +107,7 @@ public static void drawBorder(PoseStack transform, MultiBufferSource bufferSourc
double thisWidth = Math.min(right - borderX, X_SIZE);
drawTexture(matrix, buffer, borderX, y - 8, z - 0.02f, 0, COVER_Y, (float) thisWidth, COVER_SIZE, light);
drawTexture(matrix, buffer, borderX, y + Y_SIZE - 4, z - 0.02f, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE, light);
borderX += thisWidth;
borderX = (float) (borderX + thisWidth);
}
}

View File

@ -229,6 +229,7 @@ private static void renderTerminal(
RenderSystem.setInverseViewRotationMatrix(oldInverseRotation);
}
case BEST -> throw new IllegalStateException("Impossible: Should never use BEST renderer");
}
}

View File

@ -108,6 +108,7 @@ public String toString() {
return "WiredNode{@" + element.getPosition() + " (" + element.getClass().getSimpleName() + ")}";
}
@SuppressWarnings("LockNotBeforeTry")
private void acquireReadLock() {
var currentNetwork = network;
while (true) {

View File

@ -0,0 +1,35 @@
/*
* 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.common;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
/**
* A {@link BlockEntity} which exposes an inventory.
*/
public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEntity implements BasicContainer {
protected AbstractContainerBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
@Override
protected final Component getDefaultName() {
return Component.translatable(getBlockState().getBlock().getDescriptionId());
}
@Override
public boolean stillValid(Player player) {
return BlockEntityHelpers.isUsable(this, player, BlockEntityHelpers.DEFAULT_INTERACT_RANGE);
}
}

View File

@ -1,85 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.common;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
public abstract class GenericBlock extends BaseEntityBlock {
private final RegistryEntry<? extends BlockEntityType<? extends GenericTile>> type;
public GenericBlock(Properties settings, RegistryEntry<? extends BlockEntityType<? extends GenericTile>> type) {
super(settings);
this.type = type;
}
@Override
@Deprecated
public final void onRemove(BlockState block, Level world, BlockPos pos, BlockState replace, boolean bool) {
if (block.getBlock() == replace.getBlock()) return;
var tile = world.getBlockEntity(pos);
super.onRemove(block, world, pos, replace, bool);
world.removeBlockEntity(pos);
if (tile instanceof GenericTile generic) generic.destroy();
}
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
var tile = world.getBlockEntity(pos);
return tile instanceof GenericTile generic ? generic.onActivate(player, hand, hit) : InteractionResult.PASS;
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
var tile = world.getBlockEntity(pos);
if (tile instanceof GenericTile generic) generic.onNeighbourChange(neighbourPos);
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) {
var tile = world.getBlockEntity(pos);
if (tile instanceof GenericTile generic) generic.onNeighbourTileEntityChange(neighbour);
}
@Override
@Deprecated
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) {
var te = world.getBlockEntity(pos);
if (te instanceof GenericTile generic) generic.blockTick();
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return type.get().create(pos, state);
}
@Override
@Deprecated
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
}

View File

@ -1,72 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.common;
import dan200.computercraft.annotations.ForgeOverride;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
public abstract class GenericTile extends BlockEntity {
public GenericTile(BlockEntityType<? extends GenericTile> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
public void destroy() {
}
public final void updateBlock() {
setChanged();
var pos = getBlockPos();
var state = getBlockState();
getLevel().sendBlockUpdated(pos, state, state, Block.UPDATE_ALL);
}
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
return InteractionResult.PASS;
}
public void onNeighbourChange(BlockPos neighbour) {
}
public void onNeighbourTileEntityChange(BlockPos neighbour) {
}
protected void blockTick() {
}
protected double getInteractRange(Player player) {
return 8.0;
}
public boolean isUsable(Player player) {
if (player == null || !player.isAlive() || getLevel().getBlockEntity(getBlockPos()) != this) return false;
var range = getInteractRange(player);
var pos = getBlockPos();
return player.getCommandSenderWorld() == getLevel() &&
player.distanceToSqr(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5) <= range * range;
}
@ForgeOverride // FIXME: Implement this: I'd forgotten about this
public final void onDataPacket(Connection net, ClientboundBlockEntityDataPacket packet) {
var tag = packet.getTag();
if (tag != null) handleUpdateTag(tag);
}
@ForgeOverride
public void handleUpdateTag(CompoundTag tag) {
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.common;
import net.minecraft.core.BlockPos;
import net.minecraft.world.Containers;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
/**
* A block which has a container and can be placed in a horizontal direction.
*
* @see AbstractContainerBlockEntity The container class which should be used on the block entity.
*/
public abstract class HorizontalContainerBlock extends BaseEntityBlock {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public HorizontalContainerBlock(Properties properties) {
super(properties);
}
@Override
public final BlockState getStateForPlacement(BlockPlaceContext placement) {
return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite());
}
@Override
@Deprecated
public final BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public final BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Override
@Deprecated
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (level.isClientSide) return InteractionResult.SUCCESS;
if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
player.openMenu(container);
}
return InteractionResult.CONSUME;
}
@Override
@Deprecated
public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
if (state.is(newState.getBlock())) return;
if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
Containers.dropContents(level, pos, container);
level.updateNeighbourForOutputSignal(pos, this);
}
super.onRemove(state, level, pos, newState, isMoving);
}
@Override
public final void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
if (stack.hasCustomHoverName() && world.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
container.setCustomName(stack.getHoverName());
}
}
@Override
@Deprecated
public final boolean hasAnalogOutputSignal(BlockState pState) {
return true;
}
@Override
@Deprecated
public final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) {
return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(pLevel.getBlockEntity(pPos));
}
@Override
@Deprecated
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
}

View File

@ -147,7 +147,7 @@ public final List<String> list(IArguments args) throws LuaException {
for (CommandNode<?> child : node.getChildren()) {
if (child instanceof LiteralCommandNode<?>) result.add(child.getName());
}
return result;
return Collections.unmodifiableList(result);
}
/**

View File

@ -5,34 +5,42 @@
*/
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends GenericBlock implements IBundledRedstoneBlock {
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
private final ComputerFamily family;
@ -40,7 +48,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
private final BlockEntityTicker<T> serverTicker = (level, pos, state, computer) -> computer.serverTick();
protected AbstractComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
super(settings, type);
super(settings);
this.family = family;
this.type = type;
}
@ -160,9 +168,41 @@ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable L
}
}
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
return world.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer.use(player, hand) : InteractionResult.PASS;
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
var be = world.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbourPos);
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) {
var be = world.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbour);
}
@Nullable
@Override
@Deprecated
public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
return level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer : null;
}
@Override
@Nullable
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
return level.isClientSide ? null : BaseEntityBlock.createTickerHelper(type, this.type.get(), serverTicker);
return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, this.type.get(), serverTicker);
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return type.get().create(pos, state);
}
}

View File

@ -10,7 +10,6 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.impl.BundledRedstone;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
@ -18,6 +17,7 @@
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.RedstoneUtil;
@ -32,14 +32,14 @@
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
import java.util.Objects;
public abstract class AbstractComputerBlockEntity extends GenericTile implements IComputerBlockEntity, Nameable, MenuProvider {
public abstract class AbstractComputerBlockEntity extends BlockEntity implements IComputerBlockEntity, Nameable, MenuProvider {
private static final String NBT_ID = "ComputerId";
private static final String NBT_LABEL = "Label";
private static final String NBT_ON = "On";
@ -58,7 +58,7 @@ public abstract class AbstractComputerBlockEntity extends GenericTile implements
private final ComputerFamily family;
public AbstractComputerBlockEntity(BlockEntityType<? extends GenericTile> type, BlockPos pos, BlockState state, ComputerFamily family) {
public AbstractComputerBlockEntity(BlockEntityType<? extends AbstractComputerBlockEntity> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state);
this.family = family;
}
@ -71,31 +71,26 @@ protected void unload() {
instanceID = -1;
}
@Override
public void destroy() {
unload();
for (var dir : DirectionUtil.FACINGS) {
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
}
}
@Override
public void setRemoved() {
unload();
super.setRemoved();
unload();
}
protected boolean canNameWithTag(Player player) {
return false;
}
@Override
public boolean isUsable(Player player) {
return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName());
protected double getInteractRange() {
return BlockEntityHelpers.DEFAULT_INTERACT_RANGE;
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
public boolean isUsable(Player player) {
return BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName())
&& BlockEntityHelpers.isUsable(this, player, getInteractRange());
}
public InteractionResult use(Player player, InteractionHand hand) {
var currentItem = player.getItemInHand(hand);
if (!currentItem.isEmpty() && currentItem.getItem() == Items.NAME_TAG && canNameWithTag(player) && currentItem.hasCustomHoverName()) {
// Label to rename computer
@ -120,13 +115,7 @@ public InteractionResult onActivate(Player player, InteractionHand hand, BlockHi
return InteractionResult.PASS;
}
@Override
public void onNeighbourChange(BlockPos neighbour) {
updateInputAt(neighbour);
}
@Override
public void onNeighbourTileEntityChange(BlockPos neighbour) {
public void neighborChanged(BlockPos neighbour) {
updateInputAt(neighbour);
}
@ -179,9 +168,16 @@ public void saveAdditional(CompoundTag nbt) {
}
@Override
public void load(CompoundTag nbt) {
public final void load(CompoundTag nbt) {
super.load(nbt);
if (level != null && level.isClientSide) {
loadClient(nbt);
} else {
loadServer(nbt);
}
}
protected void loadServer(CompoundTag nbt) {
// Load ID, label and power state
computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null;
@ -268,7 +264,7 @@ private void updateInputAt(BlockPos neighbour) {
* Update the block's state and propagate redstone output.
*/
public void updateOutput() {
updateBlock();
BlockEntityHelpers.updateBlock(this);
for (var dir : DirectionUtil.FACINGS) {
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
}
@ -319,7 +315,7 @@ public final ServerComputer createServerComputer() {
if (computer == null) {
if (computerID < 0) {
computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level, IDAssigner.COMPUTER);
updateBlock();
BlockEntityHelpers.updateBlock(this);
}
computer = createComputer(computerID);
@ -353,8 +349,7 @@ public CompoundTag getUpdateTag() {
return nbt;
}
@Override
public void handleUpdateTag(CompoundTag nbt) {
protected void loadClient(CompoundTag nbt) {
label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null;
computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
}
@ -368,7 +363,7 @@ protected void transferStateFrom(AbstractComputerBlockEntity copy) {
on = copy.on;
startOn = copy.startOn;
lockCode = copy.lockCode;
updateBlock();
BlockEntityHelpers.updateBlock(this);
}
copy.instanceID = -1;
}

View File

@ -13,8 +13,6 @@
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
@ -47,18 +45,6 @@ public BlockState getStateForPlacement(BlockPlaceContext placement) {
return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite());
}
@Override
@Deprecated
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Override
protected ItemStack getItem(AbstractComputerBlockEntity tile) {
return tile instanceof ComputerBlockEntity ? ComputerItemFactory.create((ComputerBlockEntity) tile) : ItemStack.EMPTY;

View File

@ -14,7 +14,7 @@
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.util.SingleIntArray;
import dan200.computercraft.shared.container.SingleContainerData;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ContainerData;
@ -46,7 +46,7 @@ public AbstractComputerMenu(
super(type, id);
this.canUse = canUse;
this.family = family;
data = computer == null ? new SimpleContainerData(1) : (SingleIntArray) () -> computer.isOn() ? 1 : 0;
data = computer == null ? new SimpleContainerData(1) : (SingleContainerData) () -> computer.isOn() ? 1 : 0;
addDataSlots(data);
this.computer = computer;

View File

@ -8,7 +8,7 @@
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.util.InvisibleSlot;
import dan200.computercraft.shared.container.InvisibleSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.MenuType;

View File

@ -0,0 +1,77 @@
/*
* 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.container;
import net.minecraft.core.NonNullList;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
/**
* A basic implementation of {@link Container} which operates on a {@linkplain #getContents() stack of items}.
*/
public interface BasicContainer extends Container {
NonNullList<ItemStack> getContents();
@Override
default int getMaxStackSize() {
return 64;
}
@Override
default void startOpen(Player player) {
}
@Override
default void stopOpen(Player player) {
}
@Override
default boolean canPlaceItem(int slot, ItemStack stack) {
return true;
}
@Override
default int getContainerSize() {
return getContents().size();
}
@Override
default boolean isEmpty() {
for (var stack : getContents()) {
if (!stack.isEmpty()) return false;
}
return true;
}
@Override
default ItemStack getItem(int slot) {
var contents = getContents();
return slot >= 0 && slot < contents.size() ? contents.get(slot) : ItemStack.EMPTY;
}
@Override
default ItemStack removeItemNoUpdate(int slot) {
return ContainerHelper.takeItem(getContents(), slot);
}
@Override
default ItemStack removeItem(int slot, int count) {
return ContainerHelper.removeItem(getContents(), slot, count);
}
@Override
default void setItem(int slot, ItemStack itemStack) {
getContents().set(slot, itemStack);
}
@Override
default void clearContent() {
getContents().clear();
}
}

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
package dan200.computercraft.shared.container;
import net.minecraft.core.Direction;
import net.minecraft.world.WorldlyContainer;
@ -11,7 +11,10 @@
import javax.annotation.Nullable;
public interface DefaultSidedInventory extends DefaultInventory, WorldlyContainer {
/**
* A basic implementation of {@link WorldlyContainer} which operates on a {@linkplain #getContents() stack of items}.
*/
public interface BasicWorldlyContainer extends BasicContainer, WorldlyContainer {
@Override
default boolean canPlaceItemThroughFace(int slot, ItemStack stack, @Nullable Direction side) {
return canPlaceItem(slot, stack);

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
package dan200.computercraft.shared.container;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
@ -12,6 +12,7 @@
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.Set;
import java.util.function.Predicate;
/**
* Provides a delegate over inventories.
@ -97,4 +98,9 @@ default int countItem(Item stack) {
default boolean hasAnyOf(Set<Item> set) {
return getInventory().hasAnyOf(set);
}
@Override
default boolean hasAnyMatching(Predicate<ItemStack> predicate) {
return getInventory().hasAnyMatching(predicate);
}
}

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
package dan200.computercraft.shared.container;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;

View File

@ -3,12 +3,15 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
package dan200.computercraft.shared.container;
import net.minecraft.world.inventory.ContainerData;
/**
* A basic {@link ContainerData} implementation which provides a single value.
*/
@FunctionalInterface
public interface SingleIntArray extends ContainerData {
public interface SingleContainerData extends ContainerData {
int get();
@Override

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
package dan200.computercraft.shared.container;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.Slot;

View File

@ -5,40 +5,34 @@
*/
package dan200.computercraft.shared.peripheral.diskdrive;
import dan200.computercraft.impl.MediaProviders;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.common.HorizontalContainerBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.stats.Stats;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
public class DiskDriveBlock extends GenericBlock {
static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public class DiskDriveBlock extends HorizontalContainerBlock {
public static final EnumProperty<DiskDriveState> STATE = EnumProperty.create("state", DiskDriveState.class);
private static final BlockEntityTicker<DiskDriveBlockEntity> serverTicker = (level, pos, state, drive) -> drive.serverTick();
public DiskDriveBlock(Properties settings) {
super(settings, ModRegistry.BlockEntities.DISK_DRIVE);
super(settings);
registerDefaultState(getStateDefinition().any()
.setValue(FACING, Direction.NORTH)
.setValue(STATE, DiskDriveState.EMPTY));
@ -52,41 +46,25 @@ protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockSt
@Override
@Deprecated
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching() && level.getBlockEntity(pos) instanceof DiskDriveBlockEntity drive) {
// Try to put a disk into the drive
var disk = player.getItemInHand(hand);
if (disk.isEmpty()) return InteractionResult.PASS;
@Override
@Deprecated
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
if (!level.isClientSide && drive.getDiskStack().isEmpty() && MediaProviders.get(disk) != null) {
drive.setDiskStack(disk.split(1));
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
return super.use(state, level, pos, player, hand, hit);
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext placement) {
return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite());
}
@Override
public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack) {
if (te instanceof Nameable nameable && nameable.hasCustomName()) {
player.awardStat(Stats.BLOCK_MINED.get(this));
player.causeFoodExhaustion(0.005F);
var result = new ItemStack(this);
result.setHoverName(nameable.getCustomName());
popResource(world, pos, result);
} else {
super.playerDestroy(world, player, pos, state, te, stack);
}
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
if (stack.hasCustomHoverName() && world.getBlockEntity(pos) instanceof DiskDriveBlockEntity drive) {
drive.customName = stack.getHoverName();
}
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return ModRegistry.BlockEntities.DISK_DRIVE.get().create(pos, state);
}
@Override

View File

@ -5,42 +5,34 @@
*/
package dan200.computercraft.shared.peripheral.diskdrive;
import dan200.computercraft.api.filesystem.IMount;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.impl.MediaProviders;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.common.AbstractContainerBlockEntity;
import dan200.computercraft.shared.network.client.PlayRecordClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DefaultInventory;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.*;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.LevelEvent;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
public final class DiskDriveBlockEntity extends GenericTile implements DefaultInventory, Nameable, MenuProvider {
private static final String NBT_NAME = "CustomName";
public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
private static final String NBT_ITEM = "Item";
private static class MountInfo {
@ -48,54 +40,37 @@ private static class MountInfo {
String mountPath;
}
@Nullable
Component customName;
private LockCode lockCode = LockCode.NO_LOCK;
private final DiskDrivePeripheral peripheral = new DiskDrivePeripheral(this);
private final Map<IComputerAccess, MountInfo> computers = new HashMap<>();
private final @GuardedBy("this") Map<IComputerAccess, MountInfo> computers = new HashMap<>();
private ItemStack diskStack = ItemStack.EMPTY;
private @Nullable IPeripheral peripheral;
private @Nullable IMount diskMount = null;
private final NonNullList<ItemStack> inventory = NonNullList.withSize(1, ItemStack.EMPTY);
private boolean recordQueued = false;
private MediaStack media = MediaStack.EMPTY;
private boolean recordPlaying = false;
private boolean restartRecord = false;
private boolean ejectQueued;
// In order to avoid main-thread calls in the peripheral, we set flags to mark which operation should be performed,
// then read them when ticking.
private final AtomicReference<RecordCommand> recordQueued = new AtomicReference<>(null);
private final AtomicBoolean ejectQueued = new AtomicBoolean(false);
public DiskDriveBlockEntity(BlockEntityType<DiskDriveBlockEntity> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
public IPeripheral peripheral() {
return peripheral;
}
@Override
public void destroy() {
ejectContents(true);
public void clearRemoved() {
updateItem();
}
@Override
public void setRemoved() {
if (recordPlaying) stopRecord();
}
@Override
public boolean isUsable(Player player) {
return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName());
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching()) {
// Try to put a disk into the drive
var disk = player.getItemInHand(hand);
if (disk.isEmpty()) return InteractionResult.PASS;
if (!getLevel().isClientSide && getItem(0).isEmpty() && MediaProviders.get(disk) != null) {
setDiskStack(disk);
player.setItemInHand(hand, ItemStack.EMPTY);
}
return InteractionResult.SUCCESS;
} else {
// Open the GUI
if (!getLevel().isClientSide && isUsable(player)) player.openMenu(this);
return InteractionResult.SUCCESS;
}
}
public Direction getDirection() {
return getBlockState().getValue(DiskDriveBlock.FACING);
}
@ -103,52 +78,32 @@ public Direction getDirection() {
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
customName = nbt.contains(NBT_NAME) ? Component.Serializer.fromJson(nbt.getString(NBT_NAME)) : null;
if (nbt.contains(NBT_ITEM)) {
var item = nbt.getCompound(NBT_ITEM);
diskStack = ItemStack.of(item);
diskMount = null;
}
lockCode = LockCode.fromTag(nbt);
setDiskStack(nbt.contains(NBT_ITEM) ? ItemStack.of(nbt.getCompound(NBT_ITEM)) : ItemStack.EMPTY);
}
@Override
public void saveAdditional(CompoundTag nbt) {
if (customName != null) nbt.putString(NBT_NAME, Component.Serializer.toJson(customName));
public void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
if (!diskStack.isEmpty()) {
var item = new CompoundTag();
diskStack.save(item);
nbt.put(NBT_ITEM, item);
}
lockCode.addToTag(nbt);
super.saveAdditional(nbt);
var stack = getDiskStack();
if (!stack.isEmpty()) tag.put(NBT_ITEM, stack.save(new CompoundTag()));
}
void serverTick() {
// Ejection
if (ejectQueued) {
ejectContents(false);
ejectQueued = false;
}
if (ejectQueued.getAndSet(false)) ejectContents();
// Music
synchronized (this) {
if (recordPlaying != recordQueued || restartRecord) {
restartRecord = false;
if (recordQueued) {
var contents = getDiskMedia();
var record = contents != null ? contents.getAudio(diskStack) : null;
var recordQueued = this.recordQueued.getAndSet(null);
if (recordQueued != null) {
switch (recordQueued) {
case PLAY -> {
var record = media.getAudio();
if (record != null) {
recordPlaying = true;
playRecord();
} else {
recordQueued = false;
var title = media.getAudioTitle();
sendMessage(new PlayRecordClientMessage(getBlockPos(), record, title));
}
} else {
}
case STOP -> {
stopRecord();
recordPlaying = false;
}
@ -156,115 +111,61 @@ var record = contents != null ? contents.getAudio(diskStack) : null;
}
}
// IInventory implementation
@Override
public int getContainerSize() {
return 1;
public NonNullList<ItemStack> getContents() {
return inventory;
}
@Override
public boolean isEmpty() {
return diskStack.isEmpty();
public void setChanged() {
if (level != null && !level.isClientSide) updateItem();
super.setChanged();
}
@Override
public ItemStack getItem(int slot) {
return diskStack;
}
private void updateItem() {
var newDisk = getDiskStack();
if (ItemStack.isSame(newDisk, media.stack)) return;
@Override
public ItemStack removeItemNoUpdate(int slot) {
var result = diskStack;
diskStack = ItemStack.EMPTY;
diskMount = null;
var media = new MediaStack(newDisk.copy());
return result;
}
@Override
public ItemStack removeItem(int slot, int count) {
if (diskStack.isEmpty()) return ItemStack.EMPTY;
if (diskStack.getCount() <= count) {
var disk = diskStack;
setItem(slot, ItemStack.EMPTY);
return disk;
}
var part = diskStack.split(count);
setItem(slot, diskStack.isEmpty() ? ItemStack.EMPTY : diskStack);
return part;
}
@Override
public void setItem(int slot, ItemStack stack) {
if (getLevel().isClientSide) {
diskStack = stack;
diskMount = null;
setChanged();
return;
if (newDisk.isEmpty()) {
updateBlockState(DiskDriveState.EMPTY);
} else {
updateBlockState(media.media != null ? DiskDriveState.FULL : DiskDriveState.INVALID);
}
synchronized (this) {
if (ItemStack.isSameItemSameTags(stack, diskStack)) {
diskStack = stack;
return;
}
// Unmount old disk
if (!diskStack.isEmpty()) {
// TODO: Is this iteration thread safe?
var computers = this.computers.keySet();
for (var computer : computers) unmountDisk(computer);
if (!this.media.stack.isEmpty()) {
for (var computer : computers.entrySet()) unmountDisk(computer.getKey(), computer.getValue());
}
// Stop music
if (recordPlaying) {
stopRecord();
recordPlaying = false;
recordQueued = false;
}
// Swap disk over
diskStack = stack;
diskMount = null;
setChanged();
this.media = media;
// Mount new disk
if (!diskStack.isEmpty()) {
var computers = this.computers.keySet();
for (var computer : computers) mountDisk(computer);
if (!this.media.stack.isEmpty()) {
for (var computer : computers.entrySet()) mountDisk(computer.getKey(), computer.getValue(), this.media);
}
}
}
@Override
public void setChanged() {
if (!level.isClientSide) updateBlockState();
super.setChanged();
}
@Override
public boolean stillValid(Player player) {
return isUsable(player);
}
@Override
public void clearContent() {
setItem(0, ItemStack.EMPTY);
}
ItemStack getDiskStack() {
return getItem(0);
}
void setDiskStack(ItemStack stack) {
setItem(0, stack);
MediaStack getMedia() {
return media;
}
private @Nullable IMedia getDiskMedia() {
return MediaProviders.get(getDiskStack());
void setDiskStack(ItemStack stack) {
setItem(0, stack);
setChanged();
}
@Nullable
@ -277,95 +178,62 @@ String getDiskMountPath(IComputerAccess computer) {
void mount(IComputerAccess computer) {
synchronized (this) {
computers.put(computer, new MountInfo());
mountDisk(computer);
var info = new MountInfo();
computers.put(computer, info);
mountDisk(computer, info, media);
}
}
void unmount(IComputerAccess computer) {
synchronized (this) {
unmountDisk(computer);
computers.remove(computer);
unmountDisk(computer, computers.remove(computer));
}
}
void playDiskAudio() {
synchronized (this) {
var media = getDiskMedia();
if (media != null && media.getAudioTitle(diskStack) != null) {
recordQueued = true;
restartRecord = recordPlaying;
}
}
recordQueued.set(RecordCommand.PLAY);
}
void stopDiskAudio() {
synchronized (this) {
recordQueued = false;
restartRecord = false;
}
recordQueued.set(RecordCommand.STOP);
}
void ejectDisk() {
synchronized (this) {
ejectQueued = true;
}
ejectQueued.set(true);
}
// private methods
private synchronized void mountDisk(IComputerAccess computer) {
if (!diskStack.isEmpty()) {
var info = assertNonNull(computers.get(computer));
var contents = getDiskMedia();
if (contents != null) {
if (diskMount == null) {
diskMount = contents.createDataMount(diskStack, getLevel());
private void mountDisk(IComputerAccess computer, MountInfo info, MediaStack disk) {
var mount = disk.getMount((ServerLevel) getLevel());
if (mount != null) {
if (mount instanceof IWritableMount writable) {
// Try mounting at the lowest numbered "disk" name we can
var n = 1;
while (info.mountPath == null) {
info.mountPath = computer.mountWritable(n == 1 ? "disk" : "disk" + n, writable);
n++;
}
if (diskMount != null) {
if (diskMount instanceof IWritableMount) {
// Try mounting at the lowest numbered "disk" name we can
var n = 1;
while (info.mountPath == null) {
info.mountPath = computer.mountWritable(n == 1 ? "disk" : "disk" + n, (IWritableMount) diskMount);
n++;
}
} else {
// Try mounting at the lowest numbered "disk" name we can
var n = 1;
while (info.mountPath == null) {
info.mountPath = computer.mount(n == 1 ? "disk" : "disk" + n, diskMount);
n++;
}
}
} else {
info.mountPath = null;
} else {
// Try mounting at the lowest numbered "disk" name we can
var n = 1;
while (info.mountPath == null) {
info.mountPath = computer.mount(n == 1 ? "disk" : "disk" + n, mount);
n++;
}
}
computer.queueEvent("disk", computer.getAttachmentName());
}
}
private synchronized void unmountDisk(IComputerAccess computer) {
if (!diskStack.isEmpty()) {
var info = Objects.requireNonNull(computers.get(computer), "No mount info");
if (info.mountPath != null) {
computer.unmount(info.mountPath);
info.mountPath = null;
}
computer.queueEvent("disk_eject", computer.getAttachmentName());
}
}
private void updateBlockState() {
if (remove || level == null) return;
if (!diskStack.isEmpty()) {
var contents = getDiskMedia();
updateBlockState(contents != null ? DiskDriveState.FULL : DiskDriveState.INVALID);
} else {
updateBlockState(DiskDriveState.EMPTY);
info.mountPath = null;
}
computer.queueEvent("disk", computer.getAttachmentName());
}
private static void unmountDisk(IComputerAccess computer, MountInfo info) {
if (info.mountPath != null) {
computer.unmount(info.mountPath);
info.mountPath = null;
}
computer.queueEvent("disk_eject", computer.getAttachmentName());
}
private void updateBlockState(DiskDriveState state) {
@ -375,81 +243,32 @@ private void updateBlockState(DiskDriveState state) {
getLevel().setBlockAndUpdate(getBlockPos(), blockState.setValue(DiskDriveBlock.STATE, state));
}
private synchronized void ejectContents(boolean destroyed) {
if (getLevel().isClientSide || diskStack.isEmpty()) return;
private void ejectContents() {
if (getLevel().isClientSide) return;
// Remove the disks from the inventory
var disks = diskStack;
var stack = getDiskStack();
if (stack.isEmpty()) return;
setDiskStack(ItemStack.EMPTY);
// Spawn the item in the world
var xOff = 0;
var zOff = 0;
if (!destroyed) {
var dir = getDirection();
xOff = dir.getStepX();
zOff = dir.getStepZ();
}
var pos = getBlockPos();
var x = pos.getX() + 0.5 + xOff * 0.5;
var y = pos.getY() + 0.75;
var z = pos.getZ() + 0.5 + zOff * 0.5;
var entityitem = new ItemEntity(getLevel(), x, y, z, disks);
entityitem.setDeltaMovement(xOff * 0.15, 0, zOff * 0.15);
getLevel().addFreshEntity(entityitem);
if (!destroyed) getLevel().globalLevelEvent(1000, getBlockPos(), 0);
}
// Private methods
private void playRecord() {
var contents = getDiskMedia();
var record = contents != null ? contents.getAudio(diskStack) : null;
if (record != null) {
playRecord(new PlayRecordClientMessage(getBlockPos(), record, assertNonNull(contents).getAudioTitle(diskStack)));
} else {
stopRecord();
}
WorldUtil.dropItemStack(stack, getLevel(), getBlockPos(), getDirection());
getLevel().levelEvent(LevelEvent.SOUND_DISPENSER_DISPENSE, getBlockPos(), 0);
}
private void stopRecord() {
playRecord(new PlayRecordClientMessage(getBlockPos()));
sendMessage(new PlayRecordClientMessage(getBlockPos()));
}
private void playRecord(PlayRecordClientMessage message) {
private void sendMessage(PlayRecordClientMessage message) {
PlatformHelper.get().sendToAllAround(message, (ServerLevel) getLevel(), Vec3.atCenterOf(getBlockPos()), 64);
}
@Override
public boolean hasCustomName() {
return customName != null;
}
@Nullable
@Override
public Component getCustomName() {
return customName;
}
@Override
public Component getName() {
return customName != null ? customName : Component.translatable(getBlockState().getBlock().getDescriptionId());
}
@Override
public Component getDisplayName() {
return Nameable.super.getDisplayName();
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
protected AbstractContainerMenu createMenu(int id, Inventory inventory) {
return new DiskDriveMenu(id, inventory, this);
}
public IPeripheral peripheral() {
if (peripheral != null) return peripheral;
return peripheral = new DiskDrivePeripheral(this);
private enum RecordCommand {
PLAY,
STOP,
}
}

View File

@ -10,7 +10,6 @@
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.StringUtil;
import dan200.computercraft.impl.MediaProviders;
import dan200.computercraft.shared.media.items.DiskItem;
import javax.annotation.Nullable;
@ -53,20 +52,20 @@ public String getType() {
*/
@LuaFunction
public final boolean isDiskPresent() {
return !diskDrive.getDiskStack().isEmpty();
return !diskDrive.getMedia().stack.isEmpty();
}
/**
* Returns the label of the disk in the drive if available.
*
* @return The label of the disk, or {@code nil} if either no disk is inserted or the disk doesn't have a label.
* @cc.treturn string The label of the disk, or {@code nil} if either no disk is inserted or the disk doesn't have a label.
* @cc.treturn string|nil The label of the disk, or {@code nil} if either no disk is inserted or the disk doesn't have a label.
*/
@Nullable
@LuaFunction
public final @Nullable Object[] getDiskLabel() {
var stack = diskDrive.getDiskStack();
var media = MediaProviders.get(stack);
return media == null ? null : new Object[]{ media.getLabel(stack) };
public final Object[] getDiskLabel() {
var media = diskDrive.getMedia();
return media.media == null ? null : new Object[]{ media.media.getLabel(media.stack) };
}
/**
@ -82,11 +81,11 @@ public final boolean isDiskPresent() {
*/
@LuaFunction(mainThread = true)
public final void setDiskLabel(Optional<String> label) throws LuaException {
var stack = diskDrive.getDiskStack();
var media = MediaProviders.get(stack);
if (media == null) return;
var media = diskDrive.getMedia();
if (media.media == null) return;
if (!media.setLabel(stack, label.map(StringUtil::normaliseLabel).orElse(null))) {
var stack = media.stack.copy();
if (!media.media.setLabel(stack, label.map(StringUtil::normaliseLabel).orElse(null))) {
throw new LuaException("Disk label cannot be changed");
}
diskDrive.setDiskStack(stack);
@ -122,9 +121,7 @@ public final String getMountPath(IComputerAccess computer) {
*/
@LuaFunction
public final boolean hasAudio() {
var stack = diskDrive.getDiskStack();
var media = MediaProviders.get(stack);
return media != null && media.getAudio(stack) != null;
return diskDrive.getMedia().getAudio() != null;
}
/**
@ -136,9 +133,7 @@ public final boolean hasAudio() {
@LuaFunction
@Nullable
public final Object getAudioTitle() {
var stack = diskDrive.getDiskStack();
var media = MediaProviders.get(stack);
return media != null ? media.getAudioTitle(stack) : false;
return diskDrive.getMedia().getAudioTitle();
}
/**
@ -171,12 +166,13 @@ public final void ejectDisk() {
* Returns the ID of the disk inserted in the drive.
*
* @return The ID of the disk in the drive, or {@code nil} if no disk with an ID is inserted.
* @cc.treturn number The The ID of the disk in the drive, or {@code nil} if no disk with an ID is inserted.
* @cc.treturn number|nil The ID of the disk in the drive, or {@code nil} if no disk with an ID is inserted.
* @cc.since 1.4
*/
@Nullable
@LuaFunction
public final @Nullable Object[] getDiskID() {
var disk = diskDrive.getDiskStack();
public final Object[] getDiskID() {
var disk = diskDrive.getMedia().stack;
return disk.getItem() instanceof DiskItem ? new Object[]{ DiskItem.getDiskID(disk) } : null;
}

View File

@ -0,0 +1,51 @@
/*
* 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.diskdrive;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.impl.MediaProviders;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
/**
* An immutable snapshot of the current disk. This allows us to read the stack in a thread-safe manner.
*/
class MediaStack {
static final MediaStack EMPTY = new MediaStack(ItemStack.EMPTY);
final ItemStack stack;
final @Nullable IMedia media;
@Nullable
private IMount mount;
MediaStack(ItemStack stack) {
this.stack = stack;
media = MediaProviders.get(stack);
}
@Nullable
SoundEvent getAudio() {
return media != null ? media.getAudio(stack) : null;
}
@Nullable
String getAudioTitle() {
return media != null ? media.getAudioTitle(stack) : null;
}
@Nullable
public IMount getMount(ServerLevel level) {
if (media == null) return null;
if (mount == null) mount = media.createDataMount(stack, level);
return mount;
}
}

View File

@ -109,7 +109,7 @@ private synchronized void setNetwork(@Nullable IPacketNetwork network) {
if (this.network != null) this.network.addReceiver(this);
}
public void destroy() {
public void removed() {
setNetwork(null);
}

View File

@ -8,24 +8,30 @@
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.WaterloggableHelpers;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
@ -36,7 +42,7 @@
import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED;
import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStateForPlacement;
public class CableBlock extends GenericBlock implements SimpleWaterloggedBlock {
public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityBlock {
public static final EnumProperty<CableModemVariant> MODEM = EnumProperty.create("modem", CableModemVariant.class);
public static final BooleanProperty CABLE = BooleanProperty.create("cable");
@ -55,7 +61,7 @@ public class CableBlock extends GenericBlock implements SimpleWaterloggedBlock {
.build());
public CableBlock(Properties settings) {
super(settings, ModRegistry.BlockEntities.CABLE);
super(settings);
registerDefaultState(getStateDefinition().any()
.setValue(MODEM, CableModemVariant.None)
@ -218,4 +224,33 @@ public static BlockState correctConnections(Level world, BlockPos pos, BlockStat
.setValue(WEST, false).setValue(UP, false).setValue(DOWN, false);
}
}
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
return world.getBlockEntity(pos) instanceof CableBlockEntity modem ? modem.use(player) : InteractionResult.PASS;
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbourPos);
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) {
if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbour);
}
@Override
@Deprecated
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) {
if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.blockTick();
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return ModRegistry.BlockEntities.CABLE.get().create(pos, state);
}
}

View File

@ -11,7 +11,6 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
@ -22,21 +21,20 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.Collections;
public class CableBlockEntity extends GenericTile {
public class CableBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeirpheralAccess";
private class CableElement extends WiredModemElement {
@ -66,8 +64,6 @@ protected void detachPeripheral(String name) {
private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral(this::queueRefreshPeripheral);
private @Nullable Runnable modemChanged;
private boolean destroyed = false;
private boolean connectionsFormed = false;
private final WiredModemElement cable = new CableElement();
@ -106,24 +102,16 @@ private void onRemove() {
}
}
@Override
public void destroy() {
if (!destroyed) {
destroyed = true;
modem.destroy();
onRemove();
}
}
@Override
public void setRemoved() {
super.setRemoved();
modem.removed();
onRemove();
}
@Override
public void clearRemoved() {
super.clearRemoved(); // TODO: Replace with onLoad
super.clearRemoved();
TickScheduler.schedule(tickToken);
}
@ -147,8 +135,7 @@ private Direction getDirection() {
return direction == null ? Direction.NORTH : direction;
}
@Override
public void onNeighbourChange(BlockPos neighbour) {
void neighborChanged(BlockPos neighbour) {
var dir = getDirection();
if (neighbour.equals(getBlockPos().relative(dir)) && hasModem() && !getBlockState().canSurvive(getLevel(), getBlockPos())) {
if (hasCable()) {
@ -167,12 +154,6 @@ public void onNeighbourChange(BlockPos neighbour) {
return;
}
onNeighbourTileEntityChange(neighbour);
}
@Override
public void onNeighbourTileEntityChange(BlockPos neighbour) {
super.onNeighbourTileEntityChange(neighbour);
if (!level.isClientSide && peripheralAccessAllowed) {
var facing = getDirection();
if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral();
@ -192,8 +173,7 @@ private void refreshPeripheral() {
}
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
InteractionResult use(Player player) {
if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS;
if (!canAttachPeripheral()) return InteractionResult.FAIL;
@ -241,8 +221,7 @@ private void updateBlockState() {
}
}
@Override
public void blockTick() {
void blockTick() {
if (getLevel().isClientSide) return;
if (invalidPeripheral) refreshPeripheral();
@ -331,13 +310,11 @@ private void updateConnectedPeripherals() {
@Nullable
public IWiredElement getWiredElement(@Nullable Direction direction) {
if (destroyed) return null;
return direction == null || CableBlock.canConnectIn(getBlockState(), direction) ? cable : null;
}
@Nullable
public IPeripheral getPeripheral(@Nullable Direction direction) {
if (destroyed) return null;
return direction == null || getMaybeDirection() == direction ? modem : null;
}

View File

@ -5,19 +5,32 @@
*/
package dan200.computercraft.shared.peripheral.modem.wired;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.GenericBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
public class WiredModemFullBlock extends GenericBlock {
import javax.annotation.Nullable;
public class WiredModemFullBlock extends Block implements EntityBlock {
public static final BooleanProperty MODEM_ON = BooleanProperty.create("modem");
public static final BooleanProperty PERIPHERAL_ON = BooleanProperty.create("peripheral");
public WiredModemFullBlock(Properties settings) {
super(settings, ModRegistry.BlockEntities.WIRED_MODEM_FULL);
super(settings);
registerDefaultState(getStateDefinition().any()
.setValue(MODEM_ON, false)
.setValue(PERIPHERAL_ON, false)
@ -28,4 +41,33 @@ public WiredModemFullBlock(Properties settings) {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(MODEM_ON, PERIPHERAL_ON);
}
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
return world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem ? modem.use(player) : InteractionResult.PASS;
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.neighborChanged(neighbourPos);
}
@ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) {
if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.neighborChanged(neighbour);
}
@Override
@Deprecated
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) {
if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.blockTick();
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return ModRegistry.BlockEntities.WIRED_MODEM_FULL.get().create(pos, state);
}
}

View File

@ -10,7 +10,6 @@
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
@ -21,13 +20,12 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
@ -36,7 +34,7 @@
import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.MODEM_ON;
import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.PERIPHERAL_ON;
public class WiredModemFullBlockEntity extends GenericTile {
public class WiredModemFullBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
private static final class FullElement extends WiredModemElement {
@ -78,7 +76,6 @@ public Vec3 getPosition() {
private boolean peripheralAccessAllowed = false;
private final WiredModemLocalPeripheral[] peripherals = new WiredModemLocalPeripheral[6];
private boolean destroyed = false;
private boolean connectionsFormed = false;
private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
@ -98,35 +95,16 @@ public WiredModemFullBlockEntity(BlockEntityType<WiredModemFullBlockEntity> type
}
}
private void doRemove() {
@Override
public void setRemoved() {
super.setRemoved();
if (level == null || !level.isClientSide) {
node.remove();
connectionsFormed = false;
}
}
@Override
public void destroy() {
if (!destroyed) {
destroyed = true;
doRemove();
}
super.destroy();
}
@Override
public void setRemoved() {
super.setRemoved();
doRemove();
}
@Override
public void onNeighbourChange(BlockPos neighbour) {
onNeighbourTileEntityChange(neighbour);
}
@Override
public void onNeighbourTileEntityChange(BlockPos neighbour) {
void neighborChanged(BlockPos neighbour) {
if (!level.isClientSide && peripheralAccessAllowed) {
for (var facing : DirectionUtil.FACINGS) {
if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral(facing);
@ -147,8 +125,7 @@ private void refreshPeripheral(Direction facing) {
}
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
public InteractionResult use(Player player) {
if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS;
if (getLevel().isClientSide) return InteractionResult.SUCCESS;
@ -204,12 +181,11 @@ private void updateBlockState() {
@Override
public void clearRemoved() {
super.clearRemoved(); // TODO: Replace with onLoad
super.clearRemoved();
TickScheduler.schedule(tickToken);
}
@Override
public void blockTick() {
void blockTick() {
if (getLevel().isClientSide) return;
if (invalidSides != 0) {
@ -288,7 +264,7 @@ private Map<String, IPeripheral> getConnectedPeripherals() {
Map<String, IPeripheral> peripherals = new HashMap<>(6);
for (var peripheral : this.peripherals) peripheral.extendMap(peripherals);
return peripherals;
return Collections.unmodifiableMap(peripherals);
}
private void updateConnectedPeripherals() {

View File

@ -219,6 +219,7 @@ public final MethodResult callRemote(IComputerAccess computer, ILuaContext conte
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public void attach(IComputerAccess computer) {
super.attach(computer);
@ -236,6 +237,7 @@ public void attach(IComputerAccess computer) {
}
@Override
@SuppressWarnings("UnsynchronizedOverridesSynchronized")
public void detach(IComputerAccess computer) {
Map<String, RemotePeripheralWrapper> wrappers;
synchronized (peripheralWrappers) {

View File

@ -5,26 +5,23 @@
*/
package dan200.computercraft.shared.peripheral.modem.wireless;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.peripheral.modem.ModemShapes;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.WaterloggableHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
@ -34,12 +31,15 @@
import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED;
import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStateForPlacement;
public class WirelessModemBlock extends GenericBlock implements SimpleWaterloggedBlock {
public static final DirectionProperty FACING = BlockStateProperties.FACING;
public class WirelessModemBlock extends DirectionalBlock implements SimpleWaterloggedBlock, EntityBlock {
public static final BooleanProperty ON = BooleanProperty.create("on");
private final RegistryEntry<? extends BlockEntityType<? extends WirelessModemBlockEntity>> type;
public WirelessModemBlock(Properties settings, RegistryEntry<? extends BlockEntityType<? extends WirelessModemBlockEntity>> type) {
super(settings, type);
super(settings);
this.type = type;
registerDefaultState(getStateDefinition().any()
.setValue(FACING, Direction.NORTH)
.setValue(ON, false)
@ -98,4 +98,17 @@ public BlockState mirror(BlockState state, Mirror mirrorIn) {
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Override
@Deprecated
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) {
var te = level.getBlockEntity(pos);
if (te instanceof WirelessModemBlockEntity modem) modem.blockTick();
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return type.get().create(blockPos, blockState);
}
}

View File

@ -6,20 +6,20 @@
package dan200.computercraft.shared.peripheral.modem.wireless;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
public class WirelessModemBlockEntity extends GenericTile {
public class WirelessModemBlockEntity extends BlockEntity {
private static class Peripheral extends WirelessModemPeripheral {
private final WirelessModemBlockEntity entity;
@ -52,7 +52,6 @@ public Object getTarget() {
private final boolean advanced;
private final ModemPeripheral modem;
private boolean destroyed = false;
private @Nullable Runnable modemChanged;
private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
@ -63,17 +62,15 @@ public WirelessModemBlockEntity(BlockEntityType<? extends WirelessModemBlockEnti
}
@Override
public void clearRemoved() {
super.clearRemoved(); // TODO: Replace with onLoad
TickScheduler.schedule(tickToken);
public void setRemoved() {
super.setRemoved();
modem.removed();
}
@Override
public void destroy() {
if (!destroyed) {
modem.destroy();
destroyed = true;
}
public void clearRemoved() {
super.clearRemoved();
TickScheduler.schedule(tickToken);
}
@Override
@ -84,8 +81,7 @@ public void setBlockState(BlockState state) {
if (getDirection() != direction && modemChanged != null) modemChanged.run();
}
@Override
public void blockTick() {
void blockTick() {
if (modem.getModemState().pollChanged()) updateBlockState();
}
@ -103,7 +99,6 @@ private void updateBlockState() {
@Nullable
public IPeripheral getPeripheral(@Nullable Direction direction) {
if (destroyed) return null;
return direction == null || getDirection() == direction ? modem : null;
}

View File

@ -5,38 +5,47 @@
*/
package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
public class MonitorBlock extends GenericBlock {
public class MonitorBlock extends HorizontalDirectionalBlock implements EntityBlock {
public static final DirectionProperty ORIENTATION = DirectionProperty.create("orientation",
Direction.UP, Direction.DOWN, Direction.NORTH);
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final EnumProperty<MonitorEdgeState> STATE = EnumProperty.create("state", MonitorEdgeState.class);
private final RegistryEntry<? extends BlockEntityType<? extends MonitorBlockEntity>> type;
public MonitorBlock(Properties settings, RegistryEntry<? extends BlockEntityType<? extends MonitorBlockEntity>> type) {
super(settings, type);
super(settings);
this.type = type;
// TODO: Test underwater - do we need isSolid at all?
registerDefaultState(getStateDefinition().any()
.setValue(ORIENTATION, Direction.NORTH)
@ -49,18 +58,6 @@ protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockSt
builder.add(ORIENTATION, FACING, STATE);
}
@Override
@Deprecated
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context) {
@ -81,6 +78,41 @@ public BlockState getStateForPlacement(BlockPlaceContext context) {
.setValue(ORIENTATION, orientation);
}
@Override
@Deprecated
public final void onRemove(BlockState block, Level world, BlockPos pos, BlockState replace, boolean bool) {
if (block.getBlock() == replace.getBlock()) return;
var tile = world.getBlockEntity(pos);
super.onRemove(block, world, pos, replace, bool);
if (tile instanceof MonitorBlockEntity generic) generic.destroy();
}
@Override
@Deprecated
public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) {
var te = world.getBlockEntity(pos);
if (te instanceof MonitorBlockEntity monitor) monitor.blockTick();
}
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching() || !(level.getBlockEntity(pos) instanceof MonitorBlockEntity monitor) || monitor.getFront() != hit.getDirection()) {
return InteractionResult.PASS;
}
if (!level.isClientSide) {
monitor.monitorTouched(
(float) (hit.getLocation().x - hit.getBlockPos().getX()),
(float) (hit.getLocation().y - hit.getBlockPos().getY()),
(float) (hit.getLocation().z - hit.getBlockPos().getZ())
);
}
return InteractionResult.SUCCESS;
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState blockState, @Nullable LivingEntity livingEntity, ItemStack itemStack) {
super.setPlacedBy(world, pos, blockState, livingEntity, itemStack);
@ -96,4 +128,10 @@ public void setPlacedBy(Level world, BlockPos pos, BlockState blockState, @Nulla
monitor.expand();
}
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
return type.get().create(blockPos, blockState);
}
}

View File

@ -10,21 +10,18 @@
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -33,7 +30,7 @@
import java.util.Set;
import java.util.function.Consumer;
public class MonitorBlockEntity extends GenericTile {
public class MonitorBlockEntity extends BlockEntity {
private static final Logger LOG = LoggerFactory.getLogger(MonitorBlockEntity.class);
public static final double RENDER_BORDER = 2.0 / 16.0;
@ -79,17 +76,14 @@ public MonitorBlockEntity(BlockEntityType<? extends MonitorBlockEntity> type, Bl
}
@Override
public void clearRemoved() { // TODO: Switch back to onLood
public void clearRemoved() {
super.clearRemoved();
needsValidating = true; // Same, tbh
TickScheduler.schedule(tickToken);
}
@Override
public void destroy() {
void destroy() {
// TODO: Call this before using the block
if (destroyed) return;
destroyed = true;
if (!getLevel().isClientSide) contractNeighbours();
}
@ -99,22 +93,6 @@ public void setRemoved() {
if (clientMonitor != null && xIndex == 0 && yIndex == 0) clientMonitor.destroy();
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
if (!player.isCrouching() && getFront() == hit.getDirection()) {
if (!getLevel().isClientSide) {
monitorTouched(
(float) (hit.getLocation().x - hit.getBlockPos().getX()),
(float) (hit.getLocation().y - hit.getBlockPos().getY()),
(float) (hit.getLocation().z - hit.getBlockPos().getZ())
);
}
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
@Override
public void saveAdditional(CompoundTag tag) {
tag.putInt(NBT_X, xIndex);
@ -128,14 +106,18 @@ public void saveAdditional(CompoundTag tag) {
public void load(CompoundTag nbt) {
super.load(nbt);
var oldXIndex = xIndex;
var oldYIndex = yIndex;
xIndex = nbt.getInt(NBT_X);
yIndex = nbt.getInt(NBT_Y);
width = nbt.getInt(NBT_WIDTH);
height = nbt.getInt(NBT_HEIGHT);
if (level != null && level.isClientSide) onClientLoad(oldXIndex, oldYIndex);
}
@Override
public void blockTick() {
void blockTick() {
if (needsValidating) {
needsValidating = false;
validate();
@ -223,18 +205,7 @@ public final CompoundTag getUpdateTag() {
return nbt;
}
@Override
public final void handleUpdateTag(CompoundTag nbt) {
super.handleUpdateTag(nbt);
var oldXIndex = xIndex;
var oldYIndex = yIndex;
xIndex = nbt.getInt(NBT_X);
yIndex = nbt.getInt(NBT_Y);
width = nbt.getInt(NBT_WIDTH);
height = nbt.getInt(NBT_HEIGHT);
private void onClientLoad(int oldXIndex, int oldYIndex) {
if (oldXIndex != xIndex || oldYIndex != yIndex) {
// If our index has changed then it's possible the origin monitor has changed. Thus
// we'll clear our cache. If we're the origin then we'll need to remove the glList as well.
@ -401,7 +372,7 @@ void resize(int width, int height) {
monitor.serverMonitor = serverMonitor;
monitor.needsUpdate = monitor.needsValidating = false;
monitor.updateBlockState();
monitor.updateBlock();
BlockEntityHelpers.updateBlock(monitor);
}
}
}
@ -476,7 +447,7 @@ private void validate() {
}
// endregion
private void monitorTouched(float xPos, float yPos, float zPos) {
void monitorTouched(float xPos, float yPos, float zPos) {
if (!advanced) return;
var pair = XYPair

View File

@ -34,11 +34,11 @@ synchronized void rebuild() {
var textScale = this.textScale * 0.5;
var termWidth = (int) Math.max(
Math.round((origin.getWidth() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 6.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)),
(double) Math.round((origin.getWidth() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 6.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)),
1.0
);
var termHeight = (int) Math.max(
Math.round((origin.getHeight() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 9.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)),
(double) Math.round((origin.getHeight() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 9.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)),
1.0
);

View File

@ -6,35 +6,23 @@
package dan200.computercraft.shared.peripheral.printer;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.common.HorizontalContainerBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.stats.Stats;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import javax.annotation.Nullable;
public class PrinterBlock extends GenericBlock {
private static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public class PrinterBlock extends HorizontalContainerBlock {
public static final BooleanProperty TOP = BooleanProperty.create("top");
public static final BooleanProperty BOTTOM = BooleanProperty.create("bottom");
public PrinterBlock(Properties settings) {
super(settings, ModRegistry.BlockEntities.PRINTER);
super(settings);
registerDefaultState(getStateDefinition().any()
.setValue(FACING, Direction.NORTH)
.setValue(TOP, false)
@ -46,42 +34,9 @@ protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockSt
properties.add(FACING, TOP, BOTTOM);
}
@Override
@Deprecated
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext placement) {
return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite());
}
@Override
public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity te, ItemStack stack) {
if (te instanceof Nameable nameable && nameable.hasCustomName()) {
player.awardStat(Stats.BLOCK_MINED.get(this));
player.causeFoodExhaustion(0.005F);
var result = new ItemStack(this);
result.setHoverName(nameable.getCustomName());
popResource(world, pos, result);
} else {
super.playerDestroy(world, player, pos, state, te, stack);
}
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
if (stack.hasCustomHoverName() && world.getBlockEntity(pos) instanceof PrinterBlockEntity printer) {
printer.customName = stack.getHoverName();
}
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return ModRegistry.BlockEntities.PRINTER.get().create(pos, state);
}
}

View File

@ -6,34 +6,26 @@
package dan200.computercraft.shared.peripheral.printer;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.common.AbstractContainerBlockEntity;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.container.BasicWorldlyContainer;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.util.ColourUtils;
import dan200.computercraft.shared.util.DefaultSidedInventory;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.*;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
public final class PrinterBlockEntity extends GenericTile implements DefaultSidedInventory, Nameable, MenuProvider {
private static final String NBT_NAME = "CustomName";
public final class PrinterBlockEntity extends AbstractContainerBlockEntity implements BasicWorldlyContainer {
private static final String NBT_PRINTING = "Printing";
private static final String NBT_PAGE_TITLE = "PageTitle";
@ -43,12 +35,8 @@ public final class PrinterBlockEntity extends GenericTile implements DefaultSide
private static final int[] TOP_SLOTS = new int[]{ 1, 2, 3, 4, 5, 6 };
private static final int[] SIDE_SLOTS = new int[]{ 0 };
@Nullable
Component customName;
private LockCode lockCode = LockCode.NO_LOCK;
private final PrinterPeripheral peripheral = new PrinterPeripheral(this);
private final NonNullList<ItemStack> inventory = NonNullList.withSize(SLOTS, ItemStack.EMPTY);
private @Nullable IPeripheral peripheral;
private final NetworkedTerminal page = new NetworkedTerminal(PrintoutItem.LINE_MAX_LENGTH, PrintoutItem.LINES_PER_PAGE, true);
private String pageTitle = "";
@ -58,30 +46,14 @@ public PrinterBlockEntity(BlockEntityType<PrinterBlockEntity> type, BlockPos pos
super(type, pos, state);
}
@Override
public void destroy() {
ejectContents();
}
@Override
public boolean isUsable(Player player) {
return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName());
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching()) return InteractionResult.PASS;
if (!getLevel().isClientSide && isUsable(player)) player.openMenu(this);
return InteractionResult.SUCCESS;
public IPeripheral peripheral() {
return peripheral;
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
customName = nbt.contains(NBT_NAME) ? Component.Serializer.fromJson(nbt.getString(NBT_NAME)) : null;
// Read page
synchronized (page) {
printing = nbt.getBoolean(NBT_PRINTING);
@ -91,91 +63,35 @@ public void load(CompoundTag nbt) {
// Read inventory
ContainerHelper.loadAllItems(nbt, inventory);
lockCode = LockCode.fromTag(nbt);
}
@Override
public void saveAdditional(CompoundTag nbt) {
if (customName != null) nbt.putString(NBT_NAME, Component.Serializer.toJson(customName));
public void saveAdditional(CompoundTag tag) {
// Write page
synchronized (page) {
nbt.putBoolean(NBT_PRINTING, printing);
nbt.putString(NBT_PAGE_TITLE, pageTitle);
page.writeToNBT(nbt);
tag.putBoolean(NBT_PRINTING, printing);
tag.putString(NBT_PAGE_TITLE, pageTitle);
page.writeToNBT(tag);
}
// Write inventory
ContainerHelper.saveAllItems(nbt, inventory);
ContainerHelper.saveAllItems(tag, inventory);
lockCode.addToTag(nbt);
super.saveAdditional(nbt);
super.saveAdditional(tag);
}
boolean isPrinting() {
return printing;
}
// IInventory implementation
@Override
public int getContainerSize() {
return inventory.size();
public NonNullList<ItemStack> getContents() {
return inventory;
}
@Override
public boolean isEmpty() {
for (var stack : inventory) {
if (!stack.isEmpty()) return false;
}
return true;
}
@Override
public ItemStack getItem(int slot) {
return inventory.get(slot);
}
@Override
public ItemStack removeItemNoUpdate(int slot) {
var result = inventory.get(slot);
inventory.set(slot, ItemStack.EMPTY);
setChanged();
updateBlockState();
return result;
}
@Override
public ItemStack removeItem(int slot, int count) {
var stack = inventory.get(slot);
if (stack.isEmpty()) return ItemStack.EMPTY;
if (stack.getCount() <= count) {
setItem(slot, ItemStack.EMPTY);
return stack;
}
var part = stack.split(count);
if (inventory.get(slot).isEmpty()) {
inventory.set(slot, ItemStack.EMPTY);
updateBlockState();
}
setChanged();
return part;
}
@Override
public void setItem(int slot, ItemStack stack) {
inventory.set(slot, stack);
setChanged();
updateBlockState();
}
@Override
public void clearContent() {
for (var i = 0; i < inventory.size(); i++) inventory.set(i, ItemStack.EMPTY);
setChanged();
public void setChanged() {
super.setChanged();
updateBlockState();
}
@ -190,24 +106,17 @@ public boolean canPlaceItem(int slot, ItemStack stack) {
}
}
@Override
public boolean stillValid(Player playerEntity) {
return isUsable(playerEntity);
}
// ISidedInventory implementation
@Override
public int[] getSlotsForFace(Direction side) {
return switch (side) {
case DOWN -> BOTTOM_SLOTS; // Out tray
case UP -> TOP_SLOTS; // In tray
default -> SIDE_SLOTS; // Ink
case DOWN -> BOTTOM_SLOTS; // Bottom (Out tray)
case UP -> TOP_SLOTS; // Top (In tray)
default -> SIDE_SLOTS; // Sides (Ink)
};
}
@Nullable
Terminal getCurrentPage() {
NetworkedTerminal getCurrentPage() {
synchronized (page) {
return printing ? page : null;
}
@ -325,19 +234,6 @@ private boolean outputPage() {
return false;
}
private void ejectContents() {
for (var i = 0; i < 13; i++) {
var stack = inventory.get(i);
if (!stack.isEmpty()) {
// Remove the stack from the inventory
setItem(i, ItemStack.EMPTY);
// Spawn the item in the world
WorldUtil.dropItemStack(stack, getLevel(), Vec3.atLowerCornerOf(getBlockPos()).add(0.5, 0.75, 0.5));
}
}
}
private void updateBlockState() {
boolean top = false, bottom = false;
for (var i = 1; i < 7; i++) {
@ -367,34 +263,8 @@ private void updateBlockState(boolean top, boolean bottom) {
getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(PrinterBlock.TOP, top).setValue(PrinterBlock.BOTTOM, bottom));
}
public IPeripheral peripheral() {
if (peripheral == null) peripheral = new PrinterPeripheral(this);
return peripheral;
}
@Override
public boolean hasCustomName() {
return customName != null;
}
@Nullable
@Override
public Component getCustomName() {
return customName;
}
@Override
public Component getName() {
return customName != null ? customName : Component.translatable(getBlockState().getBlock().getDescriptionId());
}
@Override
public Component getDisplayName() {
return Nameable.super.getDisplayName();
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
protected AbstractContainerMenu createMenu(int id, Inventory inventory) {
return new PrinterMenu(id, inventory, this);
}
}

View File

@ -6,8 +6,8 @@
package dan200.computercraft.shared.peripheral.printer;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.SingleIntArray;
import dan200.computercraft.shared.util.ValidatingSlot;
import dan200.computercraft.shared.container.SingleContainerData;
import dan200.computercraft.shared.container.ValidatingSlot;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
@ -58,7 +58,7 @@ public PrinterMenu(int id, Inventory player) {
}
public PrinterMenu(int id, Inventory player, PrinterBlockEntity printer) {
this(id, player, printer, (SingleIntArray) () -> printer.isPrinting() ? 1 : 0);
this(id, player, printer, (SingleContainerData) () -> printer.isPrinting() ? 1 : 0);
}
public boolean isPrinting() {

View File

@ -6,31 +6,27 @@
package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.GenericBlock;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import javax.annotation.Nullable;
public class SpeakerBlock extends GenericBlock {
private static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public class SpeakerBlock extends HorizontalDirectionalBlock implements EntityBlock {
private static final BlockEntityTicker<SpeakerBlockEntity> serverTicker = (level, pos, state, drive) -> drive.serverTick();
public SpeakerBlock(Properties settings) {
super(settings, ModRegistry.BlockEntities.SPEAKER);
super(settings);
registerDefaultState(getStateDefinition().any()
.setValue(FACING, Direction.NORTH));
}
@ -40,18 +36,6 @@ protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockSt
properties.add(FACING);
}
@Override
@Deprecated
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext placement) {
@ -61,6 +45,12 @@ public BlockState getStateForPlacement(BlockPlaceContext placement) {
@Override
@Nullable
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
return level.isClientSide ? null : BaseEntityBlock.createTickerHelper(type, ModRegistry.BlockEntities.SPEAKER.get(), serverTicker);
return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, ModRegistry.BlockEntities.SPEAKER.get(), serverTicker);
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return ModRegistry.BlockEntities.SPEAKER.get().create(pos, state);
}
}

View File

@ -7,17 +7,17 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
public class SpeakerBlockEntity extends GenericTile {
public class SpeakerBlockEntity extends BlockEntity {
private final SpeakerPeripheral peripheral;
public SpeakerBlockEntity(BlockEntityType<SpeakerBlockEntity> type, BlockPos pos, BlockState state) {

View File

@ -15,9 +15,11 @@
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.items.ITurtleItem;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.WaterloggableHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.Containers;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
@ -27,7 +29,9 @@
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -69,18 +73,6 @@ protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockSt
builder.add(FACING, WATERLOGGED);
}
@Override
@Deprecated
public BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Override
@Deprecated
public RenderShape getRenderShape(BlockState state) {
@ -116,6 +108,19 @@ public BlockState updateShape(BlockState state, Direction side, BlockState other
return state;
}
@Override
@Deprecated
public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
if (state.is(newState.getBlock())) return;
if (level.getBlockEntity(pos) instanceof TurtleBlockEntity turtle) {
if (!level.isClientSide) Containers.dropContents(level, pos, turtle);
level.updateNeighbourForOutputSignal(pos, this);
}
super.onRemove(state, level, pos, newState, isMoving);
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity entity, ItemStack stack) {
super.setPlacedBy(world, pos, state, entity, stack);
@ -161,6 +166,6 @@ protected ItemStack getItem(AbstractComputerBlockEntity tile) {
@Override
@Nullable
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
return level.isClientSide ? BaseEntityBlock.createTickerHelper(type, this.type.get(), clientTicker) : super.getTicker(level, state, type);
return level.isClientSide ? BlockEntityHelpers.createTickerHelper(type, this.type.get(), clientTicker) : super.getTicker(level, state, type);
}
}

View File

@ -11,20 +11,16 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.common.GenericTile;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
import dan200.computercraft.shared.computer.blocks.ComputerPeripheral;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
import dan200.computercraft.shared.util.DefaultInventory;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.RedstoneUtil;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
@ -43,13 +39,12 @@
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.Collections;
public class TurtleBlockEntity extends AbstractComputerBlockEntity implements ITurtleBlockEntity, DefaultInventory {
public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer, ITurtleBlockEntity {
public static final int INVENTORY_SIZE = 16;
public static final int INVENTORY_WIDTH = 4;
public static final int INVENTORY_HEIGHT = 4;
@ -68,7 +63,7 @@ enum MoveState {
private @Nullable IPeripheral peripheral;
private @Nullable Runnable onMoved;
public TurtleBlockEntity(BlockEntityType<? extends GenericTile> type, BlockPos pos, BlockState state, ComputerFamily family) {
public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state, family);
}
@ -88,39 +83,13 @@ protected ServerComputer createComputer(int id) {
return computer;
}
@Override
public void destroy() {
if (!hasMoved()) {
// Stop computer
super.destroy();
// Drop contents
if (!getLevel().isClientSide) {
var size = getContainerSize();
for (var i = 0; i < size; i++) {
var stack = getItem(i);
if (!stack.isEmpty()) {
WorldUtil.dropItemStack(stack, getLevel(), getBlockPos());
}
}
}
} else {
// Just turn off any redstone we had on
for (var dir : DirectionUtil.FACINGS) {
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
}
}
}
@Override
protected void unload() {
if (!hasMoved()) {
super.unload();
}
if (!hasMoved()) super.unload();
}
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
public InteractionResult use(Player player, InteractionHand hand) {
// Apply dye
var currentItem = player.getItemInHand(hand);
if (!currentItem.isEmpty()) {
@ -152,7 +121,7 @@ public InteractionResult onActivate(Player player, InteractionHand hand, BlockHi
}
// Open GUI or whatever
return super.onActivate(player, hand, hit);
return super.use(player, hand);
}
@Override
@ -161,7 +130,7 @@ protected boolean canNameWithTag(Player player) {
}
@Override
protected double getInteractRange(Player player) {
protected double getInteractRange() {
return 12.0;
}
@ -189,13 +158,8 @@ protected void updateBlockState(ComputerState newState) {
}
@Override
public void onNeighbourChange(BlockPos neighbour) {
if (moveState == MoveState.NOT_MOVED) super.onNeighbourChange(neighbour);
}
@Override
public void onNeighbourTileEntityChange(BlockPos neighbour) {
if (moveState == MoveState.NOT_MOVED) super.onNeighbourTileEntityChange(neighbour);
public void neighborChanged(BlockPos neighbour) {
if (moveState == MoveState.NOT_MOVED) super.neighborChanged(neighbour);
}
public void notifyMoveStart() {
@ -208,8 +172,8 @@ public void notifyMoveEnd() {
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
public void loadServer(CompoundTag nbt) {
super.loadServer(nbt);
// Read inventory
var nbttaglist = nbt.getList("Items", Tag.TAG_COMPOUND);
@ -315,66 +279,8 @@ void setOwningPlayer(GameProfile player) {
// IInventory
@Override
public int getContainerSize() {
return INVENTORY_SIZE;
}
@Override
public boolean isEmpty() {
for (var stack : inventory) {
if (!stack.isEmpty()) return false;
}
return true;
}
@Override
public ItemStack getItem(int slot) {
return slot >= 0 && slot < INVENTORY_SIZE ? inventory.get(slot) : ItemStack.EMPTY;
}
@Override
public ItemStack removeItemNoUpdate(int slot) {
var result = getItem(slot);
setItem(slot, ItemStack.EMPTY);
return result;
}
@Override
public ItemStack removeItem(int slot, int count) {
if (count == 0) return ItemStack.EMPTY;
var stack = getItem(slot);
if (stack.isEmpty()) return ItemStack.EMPTY;
if (stack.getCount() <= count) {
setItem(slot, ItemStack.EMPTY);
return stack;
}
var part = stack.split(count);
onInventoryDefinitelyChanged();
return part;
}
@Override
public void setItem(int i, ItemStack stack) {
if (i >= 0 && i < INVENTORY_SIZE && !ItemStack.matches(stack, inventory.get(i))) {
inventory.set(i, stack);
onInventoryDefinitelyChanged();
}
}
@Override
public void clearContent() {
var changed = false;
for (var i = 0; i < INVENTORY_SIZE; i++) {
if (!inventory.get(i).isEmpty()) {
inventory.set(i, ItemStack.EMPTY);
changed = true;
}
}
if (changed) onInventoryDefinitelyChanged();
public NonNullList<ItemStack> getContents() {
return inventory;
}
@Override
@ -395,11 +301,6 @@ public boolean stillValid(Player player) {
return isUsable(player);
}
private void onInventoryDefinitelyChanged() {
super.setChanged();
inventoryChanged = true;
}
public void onTileEntityChange() {
super.setChanged();
}
@ -414,8 +315,8 @@ public CompoundTag getUpdateTag() {
}
@Override
public void handleUpdateTag(CompoundTag nbt) {
super.handleUpdateTag(nbt);
public void loadClient(CompoundTag nbt) {
super.loadClient(nbt);
brain.readDescription(nbt);
}

View File

@ -17,11 +17,12 @@
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.InventoryDelegate;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import dan200.computercraft.shared.util.InventoryDelegate;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
@ -345,6 +346,8 @@ public float getVisualYaw(float f) {
yaw += 360.0f;
}
}
default -> {
}
}
return yaw;
}
@ -454,7 +457,7 @@ public void playAnimation(TurtleAnimation animation) {
animationProgress = 0;
lastAnimationProgress = 0;
}
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
}
public @Nullable ResourceLocation getOverlay() {
@ -464,7 +467,7 @@ public void playAnimation(TurtleAnimation animation) {
public void setOverlay(ResourceLocation overlay) {
if (!Objects.equal(this.overlay, overlay)) {
this.overlay = overlay;
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
}
}
@ -481,7 +484,7 @@ public void setDyeColour(@Nullable DyeColor dyeColour) {
}
if (colourHex != newColour) {
colourHex = newColour;
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
}
}
@ -490,11 +493,11 @@ public void setColour(int colour) {
if (colour >= 0 && colour <= 0xFFFFFF) {
if (colourHex != colour) {
colourHex = colour;
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
}
} else if (colourHex != -1) {
colourHex = -1;
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
}
}
@ -525,7 +528,7 @@ public void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
// updateBlock for us.
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
// Recompute peripherals in case an upgrade being removed has exposed a new peripheral.
// TODO: Only update peripherals, or even only two sides?
@ -568,7 +571,7 @@ public CompoundTag getUpgradeNBTData(TurtleSide side) {
@Override
public void updateUpgradeNBTData(TurtleSide side) {
owner.updateBlock();
BlockEntityHelpers.updateBlock(owner);
}
public Vec3 getRenderOffset(float f) {

View File

@ -12,7 +12,7 @@
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.util.SingleIntArray;
import dan200.computercraft.shared.container.SingleContainerData;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
@ -65,7 +65,7 @@ public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) {
return new TurtleMenu(
// Laziness in turtle.getOwner() is important here!
id, p -> turtle.getOwner().stillValid(p), turtle.getFamily(), turtle.getOwner().createServerComputer(), null,
player, turtle.getInventory(), (SingleIntArray) turtle::getSelectedSlot
player, turtle.getInventory(), (SingleContainerData) turtle::getSelectedSlot
);
}

View File

@ -106,7 +106,7 @@ public List<ItemStack> doCrafting(Level world, int maxCount) {
}
}
return results;
return Collections.unmodifiableList(results);
}
@Override

View File

@ -0,0 +1,69 @@
/*
* 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.util;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
public final class BlockEntityHelpers {
/**
* The maximum limit a player can be away from a block to still have its UI open.
*
* @see #isUsable(BlockEntity, Player, double)
*/
public static final double DEFAULT_INTERACT_RANGE = 8.0;
private BlockEntityHelpers() {
}
@Nullable
@SuppressWarnings("unchecked")
public static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTickerHelper(
BlockEntityType<A> actualType, BlockEntityType<E> expectedType, BlockEntityTicker<? super E> ticker
) {
return actualType == expectedType ? (BlockEntityTicker<A>) ticker : null;
}
/**
* Determine if a block entity is "usable" by a player.
*
* @param blockEntity The current block entity.
* @param player The player who is trying to interact with the block.
* @param range The default distance the player can be away. This typically defaults to {@link #DEFAULT_INTERACT_RANGE},
* but a custom value may be used. If {@link PlatformHelper#getReachDistance(Player)} is larger,
* that will be used instead.
* @return Whether this block entity is usable.
*/
public static boolean isUsable(BlockEntity blockEntity, Player player, double range) {
var level = blockEntity.getLevel();
var pos = blockEntity.getBlockPos();
range = Math.max(range, PlatformHelper.get().getReachDistance(player));
return player.isAlive() && player.getCommandSenderWorld() == level &&
!blockEntity.isRemoved() && level.getBlockEntity(pos) == blockEntity &&
player.distanceToSqr(Vec3.atCenterOf(pos)) <= range * range;
}
/**
* Update a block entity, marking it as changed and propagating changes to the client.
*
* @param blockEntity The block entity which has updated.
*/
public static void updateBlock(BlockEntity blockEntity) {
blockEntity.setChanged();
var state = blockEntity.getBlockState();
blockEntity.getLevel().sendBlockUpdated(blockEntity.getBlockPos(), state, state, Block.UPDATE_ALL);
}
}

View File

@ -1,31 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
public interface DefaultInventory extends Container {
@Override
default int getMaxStackSize() {
return 64;
}
@Override
default void startOpen(Player player) {
}
@Override
default void stopOpen(Player player) {
}
@Override
default boolean canPlaceItem(int slot, ItemStack stack) {
return true;
}
}

View File

@ -63,14 +63,16 @@ public static ShapedTemplate getTemplate(JsonObject json) {
Set<Character> missingKeys = Sets.newHashSet(ingMap.keySet());
missingKeys.remove(' ');
var i = 0;
var ingredientIdx = 0;
for (var line : pattern) {
for (var chr : line.toCharArray()) {
for (var i = 0; i < line.length(); i++) {
var chr = line.charAt(i);
var ing = ingMap.get(chr);
if (ing == null) {
throw new JsonSyntaxException("Pattern references symbol '" + chr + "' but it's not defined in the key");
}
ingredients.set(i++, ing);
ingredients.set(ingredientIdx++, ing);
missingKeys.remove(chr);
}
}

View File

@ -29,6 +29,7 @@
import java.util.function.Predicate;
public final class WorldUtil {
@SuppressWarnings("UnnecessaryLambda")
private static final Predicate<Entity> CAN_COLLIDE = x -> x != null && x.isAlive() && x.isPickable();
public static boolean isLiquidBlock(Level world, BlockPos pos) {

View File

@ -24,7 +24,7 @@ protected boolean matchesSafely(ItemStack item) {
@Override
public void describeTo(Description description) {
description.appendValue(stack);
description.appendValue(stack).appendValue(stack.getTag());
}
public static Matcher<ItemStack> isStack(ItemStack stack) {

View File

@ -35,7 +35,7 @@ public void tickAndContinue(long ticks) {
// Mimic the original behaviour.
} catch (AssertionError e) {
parent.fail(e);
} catch (Exception e) {
} catch (Exception | LinkageError | VirtualMachineError e) {
// Fail the test, rather than crashing the server.
TestHooks.LOG.error("{} threw unexpected exception", parent.getTestName(), e);
parent.fail(e);

View File

@ -6,15 +6,19 @@
package dan200.computercraft.gametest
import dan200.computercraft.core.apis.FSAPI
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveState
import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.getApi
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.RedStoneWireBlock
import org.junit.jupiter.api.Assertions.assertEquals
@GameTestHolder
@ -52,4 +56,70 @@ fun Adds_removes_mount(helper: GameTestHelper) = helper.sequence {
thenIdle(2)
thenOnComputer { assertEquals(null, getApi<FSAPI>().getDrive("disk")) }
}
/**
* Check comparators can read the contents of the disk drive
*/
@GameTest
fun Comparator(helper: GameTestHelper) = helper.sequence {
val drivePos = BlockPos(2, 2, 2)
val dustPos = BlockPos(2, 2, 4)
// Adding items should provide power
thenExecute {
val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get())
drive.setItem(0, ItemStack(ModRegistry.Items.TREASURE_DISK.get()))
drive.setChanged()
}
thenIdle(2)
thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 15) }
// And removing them should reset power.
thenExecute {
val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get())
drive.setItem(0, ItemStack.EMPTY)
drive.setChanged()
}
thenIdle(2)
thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 0) }
}
/**
* Changing the inventory contents updates the block state
*/
@GameTest
fun Contents_updates_state(helper: GameTestHelper) = helper.sequence {
val pos = BlockPos(2, 2, 2)
thenExecute {
val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.DISK_DRIVE.get())
drive.setItem(0, ItemStack(Items.DIRT))
drive.setChanged()
helper.assertBlockHas(pos, DiskDriveBlock.STATE, DiskDriveState.INVALID)
drive.setItem(0, ItemStack(ModRegistry.Items.TREASURE_DISK.get()))
drive.setChanged()
helper.assertBlockHas(pos, DiskDriveBlock.STATE, DiskDriveState.FULL)
drive.setItem(0, ItemStack.EMPTY)
drive.setChanged()
helper.assertBlockHas(pos, DiskDriveBlock.STATE, DiskDriveState.EMPTY)
}
}
/**
* When the block is broken, we drop the contents and an optionally named stack.
*/
@GameTest
fun Drops_contents(helper: GameTestHelper) = helper.sequence {
thenExecute {
helper.level.destroyBlock(helper.absolutePos(BlockPos(2, 2, 2)), true)
helper.assertExactlyItems(
ItemStack(ModRegistry.Items.DISK_DRIVE.get()).setHoverName(Component.literal("My Disk Drive")),
ItemStack(ModRegistry.Items.TREASURE_DISK.get()),
message = "Breaking a disk drive should drop the contents",
)
}
}
}

View File

@ -5,11 +5,10 @@
*/
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.GameTestHolder
import dan200.computercraft.gametest.api.getBlockEntity
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.gametest.api.setBlock
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
@ -47,4 +46,16 @@ fun Ensures_valid_on_place(context: GameTestHelper) = context.sequence {
}
}
}
/**
* When a monitor is destroyed, assert its neighbors correctly contract.
*/
@GameTest
fun Contract_on_destroy(helper: GameTestHelper) = helper.sequence {
thenExecute {
helper.setBlock(BlockPos(2, 2, 2), Blocks.AIR.defaultBlockState())
helper.assertBlockHas(BlockPos(1, 2, 2), MonitorBlock.STATE, MonitorEdgeState.NONE)
helper.assertBlockHas(BlockPos(3, 2, 2), MonitorBlock.STATE, MonitorEdgeState.NONE)
}
}
}

View File

@ -0,0 +1,91 @@
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.peripheral.printer.PrinterBlock
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.RedStoneWireBlock
@GameTestHolder
class Printer_Test {
/**
* Check comparators can read the contents of the disk drive
*/
@GameTest
fun Comparator(helper: GameTestHelper) = helper.sequence {
val printerPos = BlockPos(2, 2, 2)
val dustPos = BlockPos(2, 2, 4)
// Adding items should provide power
thenExecute {
val drive = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get())
drive.setItem(0, ItemStack(Items.BLACK_DYE))
drive.setItem(1, ItemStack(Items.PAPER))
drive.setChanged()
}
thenIdle(2)
thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 1) }
// And removing them should reset power.
thenExecute {
val drive = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get())
drive.clearContent()
drive.setChanged()
}
thenIdle(2)
thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 0) }
}
/**
* Changing the inventory contents updates the block state
*/
@GameTest
fun Contents_updates_state(helper: GameTestHelper) = helper.sequence {
val pos = BlockPos(2, 2, 2)
thenExecute {
val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get())
drive.setItem(1, ItemStack(Items.PAPER))
drive.setChanged()
helper.assertBlockHas(pos, PrinterBlock.TOP, true, message = "One item in the top row")
helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "One item in the top row")
drive.setItem(7, ItemStack(Items.PAPER))
drive.setChanged()
helper.assertBlockHas(pos, PrinterBlock.TOP, true, message = "One item in each row")
helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "One item in each row")
drive.setItem(1, ItemStack.EMPTY)
drive.setChanged()
helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "One item in the bottom")
helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "One item in the bottom row")
drive.setItem(7, ItemStack.EMPTY)
drive.setChanged()
helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty")
helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "Empty")
}
}
/**
* When the block is broken, we drop the contents and an optionally named stack.
*/
@GameTest
fun Drops_contents(helper: GameTestHelper) = helper.sequence {
thenExecute {
helper.level.destroyBlock(helper.absolutePos(BlockPos(2, 2, 2)), true)
helper.assertExactlyItems(
ItemStack(ModRegistry.Items.PRINTER.get()).setHoverName(Component.literal("My Printer")),
ItemStack(Items.PAPER),
ItemStack(Items.BLACK_DYE),
message = "Breaking a printer should drop the contents",
)
}
}
}

View File

@ -36,6 +36,7 @@
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import java.util.*
import kotlin.time.Duration.Companion.milliseconds
@GameTestHolder
class Turtle_Test {
@ -402,7 +403,9 @@ fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
val testInfo = (helper as GameTestHelperAccessor).testInfo as GameTestInfoAccessor
val events = mutableListOf<Pair<String, String>>()
var running = false
thenStartComputer("listen") {
running = true
while (true) {
val event = pullEvent()
TestHooks.LOG.info("[{}] Got event {} at tick {}", testInfo, event[0], testInfo.`computercraft$getTick`())
@ -411,8 +414,9 @@ fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
}
}
}
thenIdle(2)
thenOnComputer("turtle") {
while (!running) sleep(10.milliseconds)
turtle.forward().await().assertArrayEquals(true, message = "Moved turtle forward")
turtle.back().await().assertArrayEquals(true, message = "Moved turtle forward")
TestHooks.LOG.info("[{}] Finished turtle at {}", testInfo, testInfo.`computercraft$getTick`())

View File

@ -11,6 +11,7 @@
import dan200.computercraft.shared.platform.PlatformHelper
import dan200.computercraft.shared.platform.Registries
import dan200.computercraft.test.core.computer.LuaTaskContext
import dan200.computercraft.test.shared.ItemStackMatcher.isStack
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
@ -24,6 +25,8 @@
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.Property
import org.hamcrest.Matchers
import org.hamcrest.StringDescription
/**
* Globally usable structures.
@ -202,6 +205,16 @@ Items do not match (first mismatch at slot $slot).
if (peripheral != null) fail("Expected no peripheral, got a ${peripheral.type}", pos)
}
fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: String? = null) {
val actual = getEntities(EntityType.ITEM).map { it.item }
val matcher = Matchers.containsInAnyOrder(expected.map { isStack(it) })
if (!matcher.matches(actual)) {
val description = StringDescription()
matcher.describeMismatch(actual, description)
fail(if (message.isNullOrEmpty()) description.toString() else "$message: $description")
}
}
private fun getName(type: BlockEntityType<*>): ResourceLocation = Registries.BLOCK_ENTITY_TYPES.getKey(type)!!
/**

View File

@ -0,0 +1,139 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:disk_drive{facing:north,state:empty}", nbt: {id: "computercraft:disk_drive"}},
{pos: [2, 1, 3], state: "minecraft:comparator{facing:north,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
{pos: [2, 1, 4], state: "minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}",
"computercraft:disk_drive{facing:north,state:empty}",
"minecraft:comparator{facing:north,mode:compare,powered:false}"
]
}

View File

@ -0,0 +1,137 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:disk_drive{facing:north,state:empty}", nbt: {ForgeCaps: {}, id: "computercraft:disk_drive"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:disk_drive{facing:north,state:empty}"
]
}

View File

@ -0,0 +1,137 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:disk_drive{facing:north,state:full}", nbt: {CustomName: '{"text":"My Disk Drive"}', ForgeCaps: {}, Item: {Count: 1b, id: "computercraft:treasure_disk"}, id: "computercraft:disk_drive"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:disk_drive{facing:north,state:full}"
]
}

View File

@ -0,0 +1,139 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:l}", nbt: {ForgeCaps: {}, Height: 1, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lr}", nbt: {ForgeCaps: {}, Height: 1, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:r}", nbt: {ForgeCaps: {}, Height: 1, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:monitor_advanced{facing:north,orientation:north,state:l}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lr}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:r}"
]
}

View File

@ -0,0 +1,139 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {ForgeCaps: {}, Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}},
{pos: [2, 1, 3], state: "minecraft:comparator{facing:north,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
{pos: [2, 1, 4], state: "minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}",
"computercraft:printer{bottom:false,facing:north,top:false}",
"minecraft:comparator{facing:north,mode:compare,powered:false}"
]
}

View File

@ -0,0 +1,137 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {ForgeCaps: {}, Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:printer{bottom:false,facing:north,top:false}"
]
}

View File

@ -0,0 +1,137 @@
{
DataVersion: 3120,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:printer{bottom:false,facing:north,top:true}", nbt: {CustomName: '{"text":"My Printer"}', ForgeCaps: {}, Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:printer{bottom:false,facing:north,top:true}"
]
}

View File

@ -99,8 +99,6 @@ minecraft {
mods.register("cctest") {
source(sourceSets["testMod"])
source(sourceSets["testFixtures"])
// FIXME: We need this for running in-dev but not from Gradle:
// source(project(":core").sourceSets.testFixtures.get())
}
}
@ -159,6 +157,7 @@ dependencies {
testRuntimeOnly(libs.bundles.testRuntime)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":forge")))
"cctJavadoc"(libs.cctJavadoc)
}

View File

@ -13,7 +13,7 @@
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
@AutoService(ClientPlatformHelper.class)
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {

View File

@ -7,6 +7,8 @@
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
@ -14,6 +16,7 @@
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.function.BooleanSupplier;
/**
* A {@link ICapabilityProvider} which provides a different single capability, with different instances for each
@ -28,15 +31,21 @@
public final class SidedCapabilityProvider<T> implements ICapabilityProvider {
private final Capability<T> cap;
private final Provider<T> supplier;
private final BooleanSupplier isRemoved;
private @Nullable LazyOptional<T>[] instances;
private SidedCapabilityProvider(Capability<T> cap, Provider<T> supplier) {
private SidedCapabilityProvider(Capability<T> cap, Provider<T> supplier, BooleanSupplier isRemoved) {
this.cap = Objects.requireNonNull(cap, "Capability cannot be null");
this.supplier = supplier;
this.isRemoved = isRemoved;
}
public static <T> SidedCapabilityProvider<T> attach(AttachCapabilitiesEvent<?> event, ResourceLocation id, Capability<T> cap, Provider<T> supplier) {
var provider = new SidedCapabilityProvider<>(cap, supplier);
BooleanSupplier isRemoved
= event.getObject() instanceof BlockEntity be ? be::isRemoved
: event.getObject() instanceof Entity entity ? entity::isRemoved
: () -> true;
var provider = new SidedCapabilityProvider<>(cap, supplier, isRemoved);
event.addCapability(id, provider);
event.addListener(provider::invalidate);
return provider;
@ -49,7 +58,7 @@ public void invalidate() {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <U> LazyOptional<U> getCapability(Capability<U> cap, @Nullable Direction side) {
if (cap != this.cap) return LazyOptional.empty();
if (cap != this.cap || isRemoved.getAsBoolean()) return LazyOptional.empty();
var instances = this.instances;
if (instances == null) instances = this.instances = new LazyOptional[6];