Контейнеры блоков
Хорошей практикой при создании блоков, которые могут хранить предметы, например, сундуки и печи, является реализация Container. Это делает возможным, например, взаимодействие с блоком с помощью воронок.
В этом руководстве мы создадим блок, который будет использовать свой контейнер для дублирования любых помещённых в него предметов.
Создание блока
Это должно быть знакомо читателю, если он следовал руководствам Создание вашего первого блока и Блок-сущности. Мы создадим DuplicatorBlock, который наследует BaseEntityBlock и реализует EntityBlock.
java
public class DuplicatorBlock extends BaseEntityBlock {
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new DuplicatorBlockEntity(pos, state);
}
// ...
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Затем нам нужно создать DuplicatorBlockEntity, который должен реализовывать интерфейс Container. Поскольку большинство контейнеров обычно работают одинаково, вы можете скопировать вспомогательный класс ImplementedContainer, который выполняет большую часть работы, оставляя нам лишь несколько методов для реализации.
Показать ImplementedContainer
java
package com.example.docs.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 simple {@link Container} implementation with only default methods + an item list getter.
*
* @author Juuz
*/
public interface ImplementedContainer extends Container {
/**
* Retrieves the item list of this container.
* Must return the same instance every time it's called.
*/
NonNullList<ItemStack> getItems();
/**
* Creates a container from the item list.
*/
static ImplementedContainer of(NonNullList<ItemStack> items) {
return () -> items;
}
/**
* Creates a new container with the specified size.
*/
static ImplementedContainer ofSize(int size) {
return of(NonNullList.withSize(size, ItemStack.EMPTY));
}
/**
* Returns the container size.
*/
@Override
default int getContainerSize() {
return getItems().size();
}
/**
* Checks if the container is empty.
* @return true if this container has only empty stacks, false otherwise.
*/
@Override
default boolean isEmpty() {
for (int i = 0; i < getContainerSize(); i++) {
ItemStack stack = getItem(i);
if (!stack.isEmpty()) {
return false;
}
}
return true;
}
/**
* Retrieves the item in the slot.
*/
@Override
default ItemStack getItem(int slot) {
return getItems().get(slot);
}
/**
* Removes items from a container slot.
* @param slot The slot to remove from.
* @param count How many items to remove. If there are fewer items in the slot than what are requested,
* takes all items in that slot.
*/
@Override
default ItemStack removeItem(int slot, int count) {
ItemStack result = ContainerHelper.removeItem(getItems(), slot, count);
if (!result.isEmpty()) {
setChanged();
}
return result;
}
/**
* Removes all items from a container slot.
* @param slot The slot to remove from.
*/
@Override
default ItemStack removeItemNoUpdate(int slot) {
return ContainerHelper.takeItem(getItems(), slot);
}
/**
* Replaces the current stack in an container slot with the provided stack.
* @param slot The container slot of which to replace the item stack.
* @param stack The replacing item stack. If the stack is too big for
* this container ({@link Container#getMaxStackSize()}),
* it gets resized to this container's maximum amount.
*/
@Override
default void setItem(int slot, ItemStack stack) {
getItems().set(slot, stack);
if (stack.getCount() > stack.getMaxStackSize()) {
stack.setCount(stack.getMaxStackSize());
}
}
/**
* Clears the container.
*/
@Override
default void clearContent() {
getItems().clear();
}
/**
* Marks that the state has changed.
* Must be called after changes in the container, so that the game can properly save
* the container contents and notify neighboring blocks of container changes.
*/
@Override
default void setChanged() {
// Override if you want behavior.
}
/**
* @return true if the player can use the container, false otherwise.
*/
@Override
default boolean stillValid(Player player) {
return true;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
java
public class DuplicatorBlockEntity extends BlockEntity implements ImplementedContainer {
private final NonNullList<ItemStack> items = NonNullList.withSize(1, ItemStack.EMPTY);
public DuplicatorBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.DUPLICATOR_BLOCK_ENTITY, pos, state);
}
@Override
public NonNullList<ItemStack> getItems() {
return items;
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Список items — это место, где хранится содержимое контейнера. Для этого блока мы установили размер контейнера равным 1 слоту для входа.
Не забудьте зарегистрировать блок и блок-сущность в соответствующих классах!
Сохранение & Загрузка
Если мы хотим, чтобы содержимое сохранялось между перезагрузками игры, как у ванильной BlockEntity, нам нужно сохранять его в формате NBT. К счастью, Mojang предоставляет вспомогательный класс ContainerHelper, содержащий всю необходимую логику.
java
@Override
protected void loadAdditional(ValueInput input) {
super.loadAdditional(input);
ContainerHelper.loadAllItems(input, items);
}
@Override
protected void saveAdditional(ValueOutput output) {
ContainerHelper.saveAllItems(output, items);
super.saveAdditional(output);
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Взаимодействие с контейнером
Технически контейнер уже функционирует. Однако, чтобы вставлять предметы, в текущем виде необходимо использовать воронки. Давайте сделаем так, чтобы мы могли вставлять предметы, нажимая ПКМ по блоку.
Для этого нам нужно переопределить метод useItemOn в DuplicatorBlock:
java
@Override
protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (!(world.getBlockEntity(pos) instanceof DuplicatorBlockEntity duplicatorBlockEntity)) {
return InteractionResult.PASS;
}
if (!player.getItemInHand(hand).isEmpty() && duplicatorBlockEntity.isEmpty()) {
duplicatorBlockEntity.setItem(0, player.getItemInHand(hand).copy());
player.getItemInHand(hand).setCount(0);
}
return InteractionResult.SUCCESS;
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Здесь, если игрок держит предмет и есть пустой слот, мы перемещаем предмет из руки игрока в контейнер блока и возвращаем InteractionResult.SUCCESS.
Теперь, когда вы нажимаете ПКМ по блоку с предметом, он исчезнет из вашей руки! Если выполнить команду /data get block на блоке, вы увидите предмет в поле Items в NBT.

Дублирование предметов
Теперь давайте сделаем так, чтобы блок дублировал стак, который вы в него поместили, но только по два предмета за раз. И давайте сделаем задержку в одну секунду, чтобы не заспамливать игрока предметами!
Для этого мы добавим функцию tick в DuplicatorBlockEntity, а также поле для хранения времени ожидания:
java
private int timeSinceDropped = 0;
public static void tick(Level world, BlockPos blockPos, BlockState blockState, DuplicatorBlockEntity duplicatorBlockEntity) {
if (duplicatorBlockEntity.isEmpty()) return;
duplicatorBlockEntity.timeSinceDropped++;
if (duplicatorBlockEntity.timeSinceDropped < 10) return;
duplicatorBlockEntity.timeSinceDropped = 0;
ItemStack duplicate = duplicatorBlockEntity.getItem(0).split(1);
Block.popResourceFromFace(world, blockPos, Direction.UP, duplicate);
Block.popResourceFromFace(world, blockPos, Direction.UP, duplicate);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Теперь в DuplicatorBlock должен быть метод getTicker, который возвращает ссылку на DuplicatorBlockEntity::tick.
Мировые контейнеры
По умолчанию вы можете вставлять и извлекать предметы из контейнера с любой стороны. Однако такое поведение не всегда желательно: например, печь принимает топливо только сбоку, а предметы — сверху.
Чтобы реализовать такое поведение, нам нужно реализовать интерфейс WorldlyContainer в BlockEntity. Этот интерфейс имеет три метода:
getSlotsForFace(Direction)позволяет управлять тем, с какими слотами можно взаимодействовать с определённой стороны.canPlaceItemThroughFace(int, ItemStack, Direction)позволяет управлять тем, можно ли вставить предмет в слот с определённой стороны.canTakeItemThroughFace(int, ItemStack, Direction)позволяет управлять тем, можно ли извлечь предмет из слота с определённой стороны.
Давайте изменим DuplicatorBlockEntity, чтобы он принимал предметы только сверху:
java
@Override
public int[] getSlotsForFace(Direction side) {
return new int[]{ 0 };
}
@Override
public boolean canPlaceItemThroughFace(int slot, ItemStack stack, @Nullable Direction dir) {
return dir == Direction.UP;
}
@Override
public boolean canTakeItemThroughFace(int slot, ItemStack stack, Direction dir) {
return true;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
getSlotsForFace возвращает массив индексов слотов, с которыми можно взаимодействовать с указанной стороны. В данном случае у нас есть только один слот (0), поэтому мы возвращаем массив, содержащий только этот индекс.
Также нам следует изменить метод useItemOn в DuplicatorBlock, чтобы он действительно учитывал новое поведение:
java
if (!duplicatorBlockEntity.canPlaceItemThroughFace(0, stack, hit.getDirection())) {
return InteractionResult.PASS;
}1
2
3
2
3
Теперь, если мы попробуем вставить предметы сбоку, а не сверху, это не сработает!
Меню
Чтобы получить доступ к новому блоку-контейнеру через меню, как это делается с сундуком, обратитесь к руководству Меню контейнеров.

