User Tools

Site Tools


zh_cn:tutorial:inventory

在方块中存储物品

阅读本教程之前,请确保已经做好了方块实体

将物品存储在 BlockEntity(方块实体)中的标准方法是使其成为 Inventory。这使得漏斗(或其他模组)无需任何额外的工作即可从您的 BlockEntity 放入和提取物品。

这个教程是用于 1.21 的,对于旧版本,一些方法可能改变。

实现 Inventory 接口

Inventory 只是一个接口,这意味着实际的 ItemStack 状态将需要存储在您的BlockEntity上。可以使用DefaultedList <ItemStack>作为存储这些ItemStacks的简便方法,且可以将其默认设置为ItemStack.Empty,用来表示物品堆没有任何物品。实现 Inventory非常简单,但乏味且容易出错,因此,我们将使用其默认实现,该实现只需要给它一个DefaultList <ItemStack>(将其复制为新文件):

ImplementedInventory.java
/**
 * 一个简单的 {@code Inventory} 实现,仅有默认的方法和物品列表的 getter。
 *
 * Originally by Juuz
 */
public interface ImplementedInventory extends Inventory {
 
    /**
     * 从此物品栏中检索物品。
     * 每次被调用时必须返回相同实例。
     */
    DefaultedList<ItemStack> getItems();
 
    /**
     * 从物品列表创建物品栏。
     */
    static ImplementedInventory of(DefaultedList<ItemStack> items) {
        return () -> items;
    }
 
    /**
     * 根据指定的尺寸创建新的物品栏。
     */
    static ImplementedInventory ofSize(int size) {
        return of(DefaultedList.ofSize(size, ItemStack.EMPTY));
    }
 
    /**
     * 返回物品栏的大小。
     */
    @Override
    default int size() {
        return getItems().size();
    }
 
    /**
     * 检查物品栏是否为空。
     * @return true,如果物品栏仅有一个空堆,否则为true。
     */
    @Override
    default boolean isEmpty() {
        for (int i = 0; i < size(); i++) {
            ItemStack stack = getStack(i);
            if (!stack.isEmpty()) {
                return false;
            }
        }
        return true;
    }
 
    /**
     * 检索槽位中的物品。
     */
    @Override
    default ItemStack getStack(int slot) {
        return getItems().get(slot);
    }
 
    /**
     * 从物品栏槽位移除物品。
     * @param slot  从该槽位移除。
     * @param count 需要移除的物品个数。如果槽位中的物品少于需要的,则将其全部取出。
     */
    @Override
    default ItemStack removeStack(int slot, int count) {
        ItemStack result = Inventories.splitStack(getItems(), slot, count);
        if (!result.isEmpty()) {
            markDirty();
        }
        return result;
    }
 
    /**
     * 从物品栏槽位移除所有物品。
     * @param slot 从该槽位移除。
     */
    @Override
    default ItemStack removeStack(int slot) {
        return Inventories.removeStack(getItems(), slot);
    }
 
    /**
     * 将物品栏槽位中的当前物品堆替换为提供的物品堆。
     * @param slot  替换该槽位的物品堆。
     * @param stack 替换后新的物品堆。如果堆对于此物品栏过大({@link Inventory#getMaxCountPerStack()}),则压缩为物品栏的最大数量。
     */
    @Override
    default void setStack(int slot, ItemStack stack) {
        getItems().set(slot, stack);
        if (stack.getCount() > getMaxCountPerStack()) {
            stack.setCount(getMaxCountPerStack());
        }
    }
 
    /**
     * 清除物品栏。
     */
    @Override
    default void clear() {
        getItems().clear();
    }
 
    /**
     * 将方块状态标记为脏。
     * 更改物品栏之后必须调用,所以游戏正确地储存物品栏内容并提取邻近方块物品栏改变。
     */ 
    @Override
    default void markDirty() {
        // 需要行为时,覆盖此方法。
    }
 
    /**
     * @return true 如果玩家可以使用物品栏,否则为 false。i
     */ 
    @Override
    default boolean canPlayerUse(PlayerEntity player) {
        return true;
    }
}

现在在你的 BlockEntity 中实现 ImplementedInventory,并为其提供存储该物品的 DefaultedList<ItemStack> items 实例。对于此例,我们将在物品栏中最多存储 2 件物品:

DemoBlockEntity.java
public class DemoBlockEntity extends BlockEntity implements ImplementedInventory {
    private final DefaultedList<ItemStack> items = DefaultedList.ofSize(2, ItemStack.EMPTY);
 
    @Override
    public DefaultedList<ItemStack> getItems() {
        return items;
    }
    [...]
 
}

我们还需要将物品栏保存到 NBT 并从那里加载。Inventories 具有辅助方法,可以使得这个非常轻松:

DemoBlockEntity.java
public class DemoBlockEntity extends BlockEntity implements ImplementedInventory {
    [...]
    @Override
    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.readNbt(nbt, registryLookup);
        Inventories.readNbt(nbt, items, registryLookup);
    }
 
    @Override
    public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        Inventories.writeNbt(nbt, items, registryLookup);
        return super.writeNbt(nbt, registryLookup);
    }
}

从物品栏(或任何物品栏)中提取和放入

我们覆盖方块类中的 onUss 行为以从我们的物品栏中加入和提取物品。注意这也可以对任何 Inventory 实例完成,不仅是我们自己的(例如,也因此可以对箱子方块做同样的事)。

首先我们处理第一个槽位,如果是空的。玩家如果拿着物品,则会将拿着的物品放入。物品进入第一个槽位,如果是空的,或者进入第二个槽位,如果第一个是空的,或者如果第二个是空的,我们则会输出与物品栏有关的信息。

注意我们将 ItemStack 插入物品栏时调用 copy(),这样不会随着玩家的 ItemStack 而被破坏。

DemoBlock.java
public class DemoBlock extends BlockWithEntity {
    [...]
    @Override
    protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
        if (world.isClient) return ActionResult.SUCCESS;
 
        if (!(world.getBlockEntity(pos) instanceof DemoBlockEntity blockEntity)) {
            return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
        }
 
        if (!player.getStackInHand(hand).isEmpty()) {
            // 检查第一个开放槽位是什么,并从玩家手中将物品放入
            if (blockEntity.getStack(0).isEmpty()) {
                // 将玩家持有的物品堆放入物品栏
                blockEntity.setStack(0, player.getStackInHand(hand).copy());
                // 从玩家手中移除物品堆
                player.getStackInHand(hand).setCount(0);
            } else if (blockEntity.getStack(1).isEmpty()) {
                blockEntity.setStack(1, player.getStackInHand(hand).copy());
                player.getStackInHand(hand).setCount(0);
            } else {
                // 如果物品栏满,提醒玩家
                player.sendMessage(Text.literal("物品栏满!第一个槽位是")
                    .append(blockEntity.getStack(0).getName())
                    .append(",第二个槽位是")
                    .append(blockEntity.getStack(1).getName()));
            }
        } 
        return ItemActionResult.SUCCESS;
    }
}

玩家不持有物品时,我们将采取相反的行为。我们将从第二个槽位中取出物品,如果第二个是空的就第一个。如果第一个也是空的,就不做任何事情。

DemoBlock.java
public class DemoBlock extends BlockWithEntity {
    [...]
    @Override
    protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
        ...
        if (!player.getStackInHand(hand).isEmpty()) {
            ...
        } else {
            // 如果玩家手中没有东西,就依次给玩家方块实体中的物品
 
            // 找到第一个有物品的槽位,并给玩家
            if (!blockEntity.getStack(1).isEmpty()) {
                // 给玩家物品栏中的物品堆
                player.getInventory().offerOrDrop(blockEntity.getStack(1));
                // 从物品栏移除物品堆
                blockEntity.removeStack(1);
            } else if (!blockEntity.getStack(0).isEmpty()) {
                player.getInventory().offerOrDrop(blockEntity.getStack(0));
                blockEntity.removeStack(0);
            } else {
                return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
            }
        }
 
        return ItemActionResult.SUCCESS;
    }
}

实现 SidedInventory 接口

如果你希望有基于与方块不同的面(漏斗或者其他模组)进行交互的不同逻辑,你可以实现 SidedInventory 接口。如果说你想使得方块不能从上侧插入,可以这样做:

DemoBlockEntity.java
public class DemoBlockEntity extends BlockEntity implements ImplementedInventory, SidedInventory {
    [...]
    @Override
    public int[] getInvAvailableSlots(Direction side) {
        // Just return an array of all slots
        return IntStream.of(getItems().size()).toArray();
    }
 
    @Override
    public boolean canInsert(int slot, ItemStack stack, Direction direction) {
        return direction != Direction.UP;
    }
 
    @Override
    public boolean canExtract(int slot, ItemStack stack, Direction direction) {
        return true;
    }
}
zh_cn/tutorial/inventory.txt · Last modified: 2024/08/27 03:03 by solidblock