Estações de trabalho 26.2
Aprenda a como criar estações de trabalho.
PRÉ-REQUISITOS
Essa estação de trabalho usa um tipo de receita customizada, que pode ser encontrada em Tipos de Receita Customizadas.
Esse tutorial fornecerá instruções sobre como criar uma estação de trabalho customizada. Diferente de baús, estações de trabalho não precisam necessariamente reter o seu inventário depois que a UI é fechada (blocos como a Bancada de Trabalho não salvam o seu inventário, mas há outros que salvam, como, por exemplo, fornalhas). Para demonstrar, nós não usaremos um BlockEntity.
Criando um Menu
INFO
Para mais detalhes sobre a criação de menus, verifique o guia Menus de Contêineres.
Para que possamos craftar a nossa receita na GUI, nós criaremos um bloco com o Menu. Para abrir o menu, nós precisaremos sobrescrever alguns métodos na nossa classe Block:
java
@Override
protected InteractionResult useWithoutItem(BlockState blockState, Level level, BlockPos blockPos, Player player, BlockHitResult blockHitResult) {
if (!level.isClientSide()) {
player.openMenu(blockState.getMenuProvider(level, blockPos));
//player.awardStat(); (you can increment a custom stat here)
}
return InteractionResult.SUCCESS;
}
@Override
protected @Nullable MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
return new SimpleMenuProvider(
(containerId, inventory, player) -> new UpgradingMenu(containerId, inventory, ContainerLevelAccess.create(level, pos)), this.getName()
);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Após isso, nós estamos prontos para criar o menu.
java
public class UpgradingMenu extends AbstractContainerMenu {
private final Container input = new SimpleContainer(2) {
@Override
public void setChanged() {
super.setChanged();
UpgradingMenu.this.slotsChanged(this);
}
};
private final ResultContainer output = new ResultContainer();
private final ContainerLevelAccess access;
public UpgradingMenu(int containerId, Inventory inventory) {
this(containerId, inventory, ContainerLevelAccess.NULL);
}
public UpgradingMenu(int containerId, Inventory inventory, ContainerLevelAccess access) {
super(ModMenuTypes.UPGRADING_MENU_TYPE, containerId);
this.access = access;
addSlot(new Slot(this.input, 0, 27, 47));
addSlot(new Slot(this.input, 1, 76, 47));
addSlot(new UpgradingResultSlot(this, this.output, 0, 134, 47));
addStandardInventorySlots(inventory, 8, 84);
}
/**
* Called by {@link UpgradingResultSlot#onTake(Player, ItemStack)}.
*/
protected void onTake(final Player player, final ItemStack stack) {
stack.onCraftedBy(player, stack.getCount());
this.output.awardUsedRecipes(player, List.of(this.input.getItem(0), this.input.getItem(1)));
this.input.removeItem(0, 1);
this.input.removeItem(1, 1);
}
@Override
public void slotsChanged(Container container) {
super.slotsChanged(container);
if (container != this.input) {
return;
}
this.access.execute((level, pos) -> {
if (level instanceof ServerLevel serverLevel) {
this.inputsChanged(serverLevel);
}
});
}
public void inputsChanged(ServerLevel level) {
UpgradingRecipeInput recipeInput = new UpgradingRecipeInput(this.input.getItem(0), this.input.getItem(1));
Optional<RecipeHolder<UpgradingRecipe>> maybeRecipe = level.recipeAccess().getRecipeFor(ExampleModRecipes.UPGRADING_RECIPE_TYPE, recipeInput, level);
if (maybeRecipe.isPresent()) {
RecipeHolder<UpgradingRecipe> recipeHolder = maybeRecipe.get();
UpgradingRecipe recipe = recipeHolder.value();
this.output.setRecipeUsed(recipeHolder);
this.output.setItem(0, recipe.assemble(recipeInput));
} else {
// We can set the used recipe to null if no recipe was found.
this.output.setRecipeUsed(null);
this.output.setItem(0, ItemStack.EMPTY);
}
}
@Override
public ItemStack quickMoveStack(Player player, int slotIndex) {
return ItemStack.EMPTY;
}
@Override
public boolean stillValid(Player player) {
return stillValid(this.access, player, ModBlocks.UPGRADING_BLOCK);
}
@Override
public void removed(Player player) {
super.removed(player);
this.access.execute((level, blockPos) -> this.clearContainer(player, this.input));
}
@Override
public boolean canTakeItemForPickAll(final ItemStack carried, final Slot target) {
return target.container != this.output && super.canTakeItemForPickAll(carried, target);
}
}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
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
Para acompanhar esse menu, nós também precisaremos de um resultado customizado Slot.
java
public class UpgradingResultSlot extends Slot {
private final UpgradingMenu menu;
public UpgradingResultSlot(UpgradingMenu menu, Container container, int slot, int x, int y) {
super(container, slot, x, y);
this.menu = menu;
}
@Override
public void onTake(Player player, ItemStack carried) {
this.menu.onTake(player, carried);
}
@Override
public boolean mayPlace(ItemStack itemStack) {
return false;
}
@Override
public boolean isFake() {
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Tem bastante coisa para explicar aqui! Esse menu tem dois slots de entrada e uma saída UpgradingResultSlot.
O contêiner de entrada é uma subclasse anonima de SimpleContainer, que chama o método slotsChanged do menu quando os seus itens mudam. Em slotsChanged, nós então criamos uma instância da nossa classe de receita de entrada.
Para ver se alguma receita corresponde, nós vamos primeiro assegurar que nós estamos no nível servidor, assim que os clientes não sabem quais receitas existem. Então, nós vamos buscar pelo RecipeManager via serverLevel.recipeAccess().
Uma observação: Sincronia de Receitas
Se o cliente não sabe quais receitas existem, então como o livro de receitas funciona?
Fico feliz que perguntou. O servidor conta ao cliente quais receitas existem baseado em quais receitas você desbloqueou (feito ao atender certos critérios descritos no Advancement JSON da receita, como, por exemplo, obter um item ou entrar na água (para barcos)). No entando, isso é meio incomodativo para mods visualizadores de receitas, que idealmente gostariam de ver todas as receitas disponíveis, mas podem ver apenas as receitas que o cliente recebe do servidor. Para contornar isso, nós podemos usar o Fabric API para sincronizar nossas receitas.
Nós chamaremos serverLevel.recipeAccess().getRecipeFor com a nossa entrada de receita para obter uma receita que corresponde as entradas. Caso uma receita for encontrada, nós podemos adicionar ou remover o resultado do contêiner de resultado.
Para detectara quando o usuário retira o resultado, nós usamos a sobrescrita onTake do UpgradingResultSlot. O método onTake do nosso menu então incrementa os itens de entrada.
Para garantir que o jogador está dentro do alcance de interação do bloco, nós sobrescrevemos stillValid.
WARNING
Certifique-se que o Block que você passa como um argumento para stillValid é o bloco abrindo o menu! Caso contrário, o menu e a tela podem abrir e em seguida fechar sozinhas imediatamente.
Finalmente, para prevenir a exclusão de itens, é importante devolver os itens fornecidos quando a tela é fechada, assim como é mostrado no método removed.
INFO
Você pode ter percebido que múltiplos métodos contém uma chamada ContainerLevelAccess#execute. Essa é uma classe wrapper usada pela Mojang para assegurar que o Level e a posição correta estejam em uso quando a interação ocorre, e previne que os jogadores acessem os contêineres que eles não deveriam. Note que o ContainerLevelAccess, especial NULL, não performa nenhuma ação quando execute é chamado nele.
O método mayPlace do Slot retorna false para que jogadores não possam inserir itens dentro do slot de resultado, e o método isFake avisa a Screen que a pilha que ela contém não possui um dono (ainda).
Você também precisa adicionar o menu ao registro:
java
public static final MenuType<UpgradingMenu> UPGRADING_MENU_TYPE = register("upgrading", UpgradingMenu::new);1
Finalmente, nós precisamos registrar o nosso bloco:
java
public static final BlockItemId UPGRADING_BLOCK = create("upgrading_block");1
java
public static final Block UPGRADING_BLOCK = register(
ModBlockItemIds.UPGRADING_BLOCK, UpgradingBlock::new, BlockBehaviour.Properties.of()
);1
2
3
2
3
Implementando quickMoveStack
INFO
Veja também: Menus de Contêineres: Criando o Menu
O Quick Move é chamado em qualquer momento que um clique com o botão esquerdo segurando shift é realizado em um Menu.
java
private static final int INPUT_SLOTS_COUNT = 2;
private static final int RESULT_SLOT = 0;
private static final int INPUT_SLOTS_START = RESULT_SLOT + 1; // 1
private static final int INPUT_SLOTS_END = INPUT_SLOTS_START + INPUT_SLOTS_COUNT; // 3
private static final int INVENTORY_START = INPUT_SLOTS_END; // 3
private static final int INVENTORY_END = INVENTORY_START + 27; // 30
private static final int HOTBAR_START = INVENTORY_END; // 30
private static final int HOTBAR_END = HOTBAR_START + 9; // 39
@Override
public ItemStack quickMoveStack(Player player, int slotIndex) {
Slot slot = this.slots.get(slotIndex);
//noinspection ConstantValue
if (slot == null || !slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack stack = slot.getItem();
ItemStack clicked = stack.copy();
if (slotIndex == RESULT_SLOT) {
stack.getItem().onCraftedBy(stack, player);
if (!this.moveItemStackTo(stack, INVENTORY_START, HOTBAR_END, true)) {
return ItemStack.EMPTY;
}
slot.onQuickCraft(stack, clicked);
} else if (slotIndex >= INVENTORY_START && slotIndex < HOTBAR_END) {
if (!this.moveItemStackTo(stack, INPUT_SLOTS_START, INPUT_SLOTS_END, false)) {
if (slotIndex < HOTBAR_START) {
if (!this.moveItemStackTo(stack, HOTBAR_START, HOTBAR_END, false)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(stack, INVENTORY_START, INVENTORY_END, false)) {
return ItemStack.EMPTY;
}
}
} else if (!this.moveItemStackTo(stack, INVENTORY_START, HOTBAR_END, false)) {
return ItemStack.EMPTY;
}
if (stack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
if (stack.getCount() == clicked.getCount()) {
return ItemStack.EMPTY;
}
slot.onTake(player, stack);
if (slotIndex == RESULT_SLOT) {
player.drop(stack, false);
}
return clicked;
}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
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
Uau, tem um monte de códigos de novo. Vamos tentar refletir sobre o que está acontecendo.
Normalmente, ao realizar o quick move sobre uma pilha da área do inventário, o menu primeiro verifica se o slot clicado é o slot de resultado (com o índex 0). Caso positivo, o menu tenta mover a pilha de resultado para dentro do inventário, mas caso falhe, nada acontece.
Próximo, o menu verifica se o slot clicado pertence ao inventário. Caso positivo, então o menu tenta mover a pilha para dentro das entradas. Se isso falhar, nós tentamos mover a pilha para dentro do inventário (slots clicados na barra rápida (hotbar) movem suas pilhas para os outros 27 slots do inventário e vice-versa).
Se o slot clicado não era o slot de resultado ou não estava dentro do inventário, o slot então é quase garantido de ter sido um dos nossos dois slots de entrada, então nós gostaríamos de mover as suas pilhas de volta ao inventário.
A Tela
INFO
Veja também: Menu de Contêineres: Criando a Tela
Por enquanto, nós podemos só pegar a textura do plano de fundo vanilla da bigorna emprestada.
java
public class UpgradingScreen extends AbstractContainerScreen<UpgradingMenu> {
private static final Identifier SCREEN_TEXTURE = Identifier.withDefaultNamespace("textures/gui/container/anvil.png");
public UpgradingScreen(UpgradingMenu abstractContainerMenu, Inventory inventory, Component component) {
super(abstractContainerMenu, inventory, component);
}
@Override
public void extractBackground(GuiGraphicsExtractor guiGraphics, int mouseX, int mouseY, float delta) {
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, SCREEN_TEXTURE, this.leftPos, this.topPos, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 256, 256);
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Não esqueça de vincular o seu tipo de menu à tela dentro do seu ClientModInitializer, da seguinte forma:
java
MenuScreens.register(ModMenuTypes.UPGRADING_MENU_TYPE, UpgradingScreen::new);1
Restos de Receitas
Quer fazer uma receita que suporte itens restantes? Nós recomendamos dar uma olhada no net.minecraft.world.inventory.ResultSlot#getRemainingItems. A bancada de trabalho usa isso como um slot de resultado, várias similaridades entre os documentos podem ser encontradas, mas também há algumas diferenças.



