Skip to content

Commit e2f0dbe

Browse files
CraftingTable- & FurnaceWindow
1 parent 6596367 commit e2f0dbe

10 files changed

+645
-4
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package xyz.xenondevs.invui.internal.menu;
2+
3+
import net.minecraft.world.inventory.MenuType;
4+
import org.bukkit.entity.Player;
5+
6+
/**
7+
* A packet-based crafting table menu.
8+
*/
9+
public class CustomCraftingTableMenu extends CustomRecipeBookPoweredMenu{
10+
11+
/**
12+
* Creates a new {@link CustomCraftingTableMenu} for the specified viewer.
13+
*
14+
* @param player The player that will view the menu
15+
*/
16+
public CustomCraftingTableMenu(Player player) {
17+
super(MenuType.CRAFTING, player);
18+
}
19+
20+
}
21+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package xyz.xenondevs.invui.internal.menu;
2+
3+
import net.minecraft.world.inventory.MenuType;
4+
import org.bukkit.entity.Player;
5+
6+
/**
7+
* A packet-based furnace menu.
8+
*/
9+
public class CustomFurnaceMenu extends CustomRecipeBookPoweredMenu{
10+
11+
/**
12+
* Creates a new {@link CustomFurnaceMenu} for the specified viewer.
13+
*
14+
* @param player The player that will view the menu
15+
*/
16+
public CustomFurnaceMenu(Player player) {
17+
super(MenuType.FURNACE, player);
18+
}
19+
20+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package xyz.xenondevs.invui.internal.menu;
2+
3+
import net.kyori.adventure.key.Key;
4+
import net.kyori.adventure.text.Component;
5+
import net.minecraft.core.registries.Registries;
6+
import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket;
7+
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
8+
import net.minecraft.resources.ResourceKey;
9+
import net.minecraft.resources.ResourceLocation;
10+
import net.minecraft.server.MinecraftServer;
11+
import net.minecraft.world.inventory.MenuType;
12+
import net.minecraft.world.item.crafting.display.RecipeDisplayEntry;
13+
import org.bukkit.entity.Player;
14+
import org.jspecify.annotations.Nullable;
15+
import xyz.xenondevs.invui.internal.network.PacketListener;
16+
17+
import java.util.concurrent.atomic.AtomicReference;
18+
import java.util.function.Consumer;
19+
20+
/**
21+
* Abstract superclass for all custom container menus that have a recipe book.
22+
*/
23+
public abstract class CustomRecipeBookPoweredMenu extends CustomContainerMenu {
24+
25+
private @Nullable Consumer<? super Key> recipeSelectHandler;
26+
27+
/**
28+
* Creates a new {@link CustomContainerMenu} for the specified player.
29+
*
30+
* @param menuType The type of the menu
31+
* @param player The player that will see the menu
32+
*/
33+
protected CustomRecipeBookPoweredMenu(MenuType<?> menuType, Player player) {
34+
super(menuType, player);
35+
}
36+
37+
@Override
38+
public void open(Component title) {
39+
var pl = PacketListener.getInstance();
40+
pl.redirectIncoming(player, ServerboundPlaceRecipePacket.class, this::handleRecipePlace);
41+
super.open(title);
42+
}
43+
44+
@Override
45+
public void handleClosed() {
46+
var pl = PacketListener.getInstance();
47+
pl.removeRedirect(player, ServerboundPlaceRecipePacket.class);
48+
super.handleClosed();
49+
}
50+
51+
/**
52+
* Displays a ghost recipe of the given id in the menu.
53+
*
54+
* @param id The recipe id
55+
*/
56+
public void sendGhostRecipe(Key id) {
57+
var entryRef = new AtomicReference<@Nullable RecipeDisplayEntry>();
58+
var rk = ResourceKey.create(Registries.RECIPE, ResourceLocation.fromNamespaceAndPath(id.namespace(), id.value()));
59+
MinecraftServer.getServer().getRecipeManager().listDisplaysForRecipe(rk, entryRef::set);
60+
var entry = entryRef.get();
61+
if (entry == null)
62+
return;
63+
64+
PacketListener.getInstance().injectOutgoing(
65+
player,
66+
new ClientboundPlaceGhostRecipePacket(containerId, entry.display())
67+
);
68+
}
69+
70+
/**
71+
* Sets the handler that is called when a recipe is selected in the recipe book.
72+
*
73+
* @param recipeClickHandler The recipe click handler
74+
*/
75+
public void setRecipeClickHandler(Consumer<? super Key> recipeClickHandler) {
76+
this.recipeSelectHandler = recipeClickHandler;
77+
}
78+
79+
private void handleRecipePlace(ServerboundPlaceRecipePacket packet) {
80+
if (recipeSelectHandler == null)
81+
return;
82+
var displayInfo = MinecraftServer.getServer()
83+
.getRecipeManager()
84+
.getRecipeFromDisplay(packet.recipe());
85+
if (displayInfo == null)
86+
return;
87+
var rl = displayInfo
88+
.parent()
89+
.id()
90+
.location();
91+
var key = Key.key(rl.getNamespace(), rl.getPath());
92+
recipeSelectHandler.accept(key);
93+
}
94+
95+
}

invui/src/main/java/xyz/xenondevs/invui/window/AbstractSplitWindow.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
@ApiStatus.Internal
2020
sealed abstract class AbstractSplitWindow<M extends CustomContainerMenu>
2121
extends AbstractWindow<M>
22-
permits AnvilWindowImpl, CartographyWindowImpl, CrafterWindowImpl, MerchantWindowImpl, NormalSplitWindowImpl, StonecutterWindowImpl
22+
permits AnvilWindowImpl, CartographyWindowImpl, CrafterWindowImpl, CraftingTableWindowImpl, FurnaceWindowImpl, MerchantWindowImpl, NormalSplitWindowImpl, StonecutterWindowImpl
2323
{
2424

2525
private final AbstractGui lowerGui;
@@ -41,7 +41,7 @@ sealed abstract class AbstractSplitWindow<M extends CustomContainerMenu>
4141
static sealed abstract class AbstractBuilder<W extends Window, S extends Builder.Split<W, S>>
4242
extends AbstractWindow.AbstractBuilder<W, S>
4343
implements Builder.Split<W, S>
44-
permits AnvilWindowImpl.BuilderImpl, CartographyWindowImpl.BuilderImpl, CrafterWindowImpl.BuilderImpl, MerchantWindowImpl.BuilderImpl, NormalSplitWindowImpl.BuilderImpl, StonecutterWindowImpl.BuilderImpl
44+
permits AnvilWindowImpl.BuilderImpl, CartographyWindowImpl.BuilderImpl, CrafterWindowImpl.BuilderImpl, CraftingTableWindowImpl.BuilderImpl, FurnaceWindowImpl.BuilderImpl, MerchantWindowImpl.BuilderImpl, NormalSplitWindowImpl.BuilderImpl, StonecutterWindowImpl.BuilderImpl
4545
{
4646

4747
private @Nullable Supplier<? extends Gui> lowerGuiSupplier;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package xyz.xenondevs.invui.window;
2+
3+
import xyz.xenondevs.invui.gui.Gui;
4+
5+
import java.util.function.Supplier;
6+
7+
/**
8+
* A {@link Window} that uses a crafter inventory.
9+
*/
10+
public sealed interface CraftingTableWindow extends Window, RecipeBookPowered permits CraftingTableWindowImpl {
11+
12+
/**
13+
* Creates a new {@link Builder} for a split {@link CraftingTableWindow}.
14+
*
15+
* @return The new {@link Builder}
16+
*/
17+
static Builder builder() {
18+
return new CraftingTableWindowImpl.BuilderImpl();
19+
}
20+
21+
/**
22+
* A {@link CraftingTableWindow} builder.
23+
*/
24+
sealed interface Builder extends Window.Builder.Split<CraftingTableWindow, Builder>, RecipeBookPowered.Builder<CraftingTableWindow.Builder> permits CraftingTableWindowImpl.BuilderImpl {
25+
26+
/**
27+
* Sets the 3x3 crafting input {@link Gui} of the {@link CraftingTableWindow}.
28+
*
29+
* @param gui The 3x3 crafting input {@link Gui}
30+
* @return This {@link Builder}
31+
*/
32+
default Builder setCraftingGui(Gui gui) {
33+
return setCraftingGui(() -> gui);
34+
}
35+
36+
/**
37+
* Sets the {@link Gui.Builder} for the 3x3 crafting input {@link Gui} of this {@link Builder}.
38+
* The {@link Gui.Builder} will be called every time a new {@link CraftingTableWindow} is created using this builder.
39+
*
40+
* @param builder The {@link Gui.Builder} for the 3x3 crafting input {@link Gui}
41+
* @return This {@link Builder}
42+
*/
43+
default Builder setCraftingGui(Gui.Builder<?, ?> builder) {
44+
return setCraftingGui(builder::build);
45+
}
46+
47+
/**
48+
* Sets the {@link Gui} {@link Supplier} for the 3x3 crafting input {@link Gui} of this {@link Builder}.
49+
* The {@link Supplier} will be called every time a new {@link CraftingTableWindow} is created using this builder.
50+
*
51+
* @param guiSupplier The {@link Gui} {@link Supplier} for the 3x3 crafting input {@link Gui}
52+
* @return This {@link Builder}
53+
*/
54+
Builder setCraftingGui(Supplier<? extends Gui> guiSupplier);
55+
56+
/**
57+
* Sets the result {@link Gui} of the {@link CraftingTableWindow}.
58+
*
59+
* @param gui The result input {@link Gui} of the {@link CraftingTableWindow}
60+
* @return This {@link Builder}
61+
*/
62+
default Builder setResultGui(Gui gui) {
63+
return setResultGui(() -> gui);
64+
}
65+
66+
/**
67+
* Sets the {@link Gui.Builder} for the result input {@link Gui} of this {@link Builder}.
68+
* The {@link Gui.Builder} will be called every time a new {@link CraftingTableWindow} is created using this builder.
69+
*
70+
* @param builder The {@link Gui.Builder} for the result input {@link Gui}
71+
* @return This {@link Builder}
72+
*/
73+
default Builder setResultGui(Gui.Builder<?, ?> builder) {
74+
return setResultGui(builder::build);
75+
}
76+
77+
/**
78+
* Sets the {@link Gui} {@link Supplier} for the result input {@link Gui} of this {@link Builder}.
79+
* The {@link Supplier} will be called every time a new {@link CraftingTableWindow} is created using this builder.
80+
*
81+
* @param guiSupplier The {@link Gui} {@link Supplier} for the result input {@link Gui}
82+
* @return This {@link Builder}
83+
*/
84+
Builder setResultGui(Supplier<? extends Gui> guiSupplier);
85+
86+
}
87+
88+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package xyz.xenondevs.invui.window;
2+
3+
import net.kyori.adventure.key.Key;
4+
import net.kyori.adventure.text.Component;
5+
import org.bukkit.entity.Player;
6+
import org.jetbrains.annotations.Unmodifiable;
7+
import xyz.xenondevs.invui.gui.AbstractGui;
8+
import xyz.xenondevs.invui.gui.Gui;
9+
import xyz.xenondevs.invui.internal.menu.CustomCraftingTableMenu;
10+
import xyz.xenondevs.invui.internal.menu.CustomRecipeBookPoweredMenu;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.function.Consumer;
15+
import java.util.function.Supplier;
16+
17+
final class CraftingTableWindowImpl extends AbstractSplitWindow<CustomCraftingTableMenu> implements CraftingTableWindow {
18+
19+
private final AbstractGui craftingGui;
20+
private final AbstractGui resultGui;
21+
private final AbstractGui lowerGui;
22+
private final List<Consumer<? super Key>> recipeClickHandlers = new ArrayList<>();
23+
24+
public CraftingTableWindowImpl(
25+
Player player,
26+
Supplier<? extends Component> title,
27+
AbstractGui craftingGui,
28+
AbstractGui resultGui,
29+
AbstractGui lowerGui,
30+
boolean closeable
31+
) {
32+
super(player, title, lowerGui, 46, new CustomCraftingTableMenu(player), closeable);
33+
if (craftingGui.getWidth() != 3 || craftingGui.getHeight() != 3)
34+
throw new IllegalArgumentException("Crafting Gui must be of dimensions 3x3");
35+
if (resultGui.getWidth() != 1 || resultGui.getHeight() != 1)
36+
throw new IllegalArgumentException("Result Gui must be of dimensions 1x1");
37+
38+
this.craftingGui = craftingGui;
39+
this.resultGui = resultGui;
40+
this.lowerGui = lowerGui;
41+
menu.setRecipeClickHandler(this::handleRecipeClick);
42+
}
43+
44+
private void handleRecipeClick(Key recipeId) {
45+
for (var handler : recipeClickHandlers) {
46+
handler.accept(recipeId);
47+
}
48+
}
49+
50+
@Override
51+
public @Unmodifiable List<Gui> getGuis() {
52+
return List.of(resultGui, craftingGui, lowerGui);
53+
}
54+
55+
@Override
56+
public void sendGhostRecipe(Key recipeId) {
57+
if (!isOpen())
58+
throw new IllegalStateException("Window is not open");
59+
menu.sendGhostRecipe(recipeId);
60+
}
61+
62+
@Override
63+
public void addRecipeClickHandler(Consumer<? super Key> handler) {
64+
recipeClickHandlers.add(handler);
65+
}
66+
67+
@Override
68+
public void removeRecipeClickHandler(Consumer<? super Key> handler) {
69+
recipeClickHandlers.remove(handler);
70+
}
71+
72+
@Override
73+
public void setRecipeClickHandlers(List<? extends Consumer<? super Key>> handlers) {
74+
recipeClickHandlers.clear();
75+
recipeClickHandlers.addAll(handlers);
76+
}
77+
78+
public static final class BuilderImpl
79+
extends AbstractSplitWindow.AbstractBuilder<CraftingTableWindow, CraftingTableWindow.Builder>
80+
implements CraftingTableWindow.Builder
81+
{
82+
83+
private final List<Consumer<? super Key>> recipeClickHandlers = new ArrayList<>();
84+
private Supplier<? extends Gui> craftingGuiSupplier = () -> Gui.empty(3, 3);
85+
private Supplier<? extends Gui> resultGuiSupplier = () -> Gui.empty(1, 1);
86+
87+
@Override
88+
public CraftingTableWindow.Builder setCraftingGui(Supplier<? extends Gui> guiSupplier) {
89+
this.craftingGuiSupplier = guiSupplier;
90+
return this;
91+
}
92+
93+
@Override
94+
public CraftingTableWindow.Builder setResultGui(Supplier<? extends Gui> guiSupplier) {
95+
this.resultGuiSupplier = guiSupplier;
96+
return this;
97+
}
98+
99+
@Override
100+
public CraftingTableWindow.Builder setRecipeClickHandlers(List<? extends Consumer<? super Key>> handlers) {
101+
this.recipeClickHandlers.clear();
102+
this.recipeClickHandlers.addAll(handlers);
103+
return this;
104+
}
105+
106+
@Override
107+
public CraftingTableWindow.Builder addRecipeClickHandler(Consumer<? super Key> handler) {
108+
this.recipeClickHandlers.add(handler);
109+
return this;
110+
}
111+
112+
@Override
113+
public CraftingTableWindow build(Player viewer) {
114+
var window = new CraftingTableWindowImpl(
115+
viewer,
116+
titleSupplier,
117+
(AbstractGui) craftingGuiSupplier.get(),
118+
(AbstractGui) resultGuiSupplier.get(),
119+
supplyLowerGui(viewer),
120+
closeable
121+
);
122+
123+
window.setRecipeClickHandlers(recipeClickHandlers);
124+
applyModifiers(window);
125+
126+
return window;
127+
}
128+
}
129+
130+
}

0 commit comments

Comments
 (0)