9
0
mirror of https://github.com/LeavesMC/Leaves.git synced 2025-12-19 14:59:32 +00:00

Support REI protocol (#391)

This commit is contained in:
Fortern
2025-04-22 15:41:17 +08:00
committed by GitHub
parent 4bb13ebfc9
commit 4b77c25f30
25 changed files with 1565 additions and 252 deletions

View File

@@ -1,38 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Thu, 27 Mar 2025 13:04:35 +0800
Subject: [PATCH] Recipe send all
diff --git a/net/minecraft/stats/ServerRecipeBook.java b/net/minecraft/stats/ServerRecipeBook.java
index e3985b70cee7f7d56f179aeef8c2a6a6b312d83a..a476f6b6554b4c7ba1625ab4b9da3bcf3d40955b 100644
--- a/net/minecraft/stats/ServerRecipeBook.java
+++ b/net/minecraft/stats/ServerRecipeBook.java
@@ -150,12 +150,23 @@ public class ServerRecipeBook extends RecipeBook {
public void sendInitialRecipeBook(ServerPlayer player) {
player.connection.send(new ClientboundRecipeBookSettingsPacket(this.getBookSettings()));
- List<ClientboundRecipeBookAddPacket.Entry> list = new ArrayList<>(this.known.size());
+ // Leaves start - recipe-send-all
+ List<ClientboundRecipeBookAddPacket.Entry> list;
- for (ResourceKey<Recipe<?>> resourceKey : this.known) {
- this.displayResolver
- .displaysForRecipe(resourceKey, entry -> list.add(new ClientboundRecipeBookAddPacket.Entry(entry, false, this.highlight.contains(resourceKey))));
+ if (org.leavesmc.leaves.LeavesConfig.protocol.recipeSendAll) {
+ list = new ArrayList<>(player.server.getRecipeManager().getRecipes().size());
+ player.server.getRecipeManager().getRecipes().stream().map(RecipeHolder::id).forEach( key -> {
+ this.displayResolver
+ .displaysForRecipe(key, entry -> list.add(new ClientboundRecipeBookAddPacket.Entry(entry, false, this.highlight.contains(key))));
+ });
+ } else {
+ list = new ArrayList<>(this.known.size());
+ for (ResourceKey<Recipe<?>> resourceKey : this.known) {
+ this.displayResolver
+ .displaysForRecipe(resourceKey, entry -> list.add(new ClientboundRecipeBookAddPacket.Entry(entry, false, this.highlight.contains(resourceKey))));
+ }
}
+ // Leaves end - recipe-send-all
player.connection.send(new ClientboundRecipeBookAddPacket(list, true));
}

View File

@@ -0,0 +1,35 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Thu, 27 Mar 2025 13:04:35 +0800
Subject: [PATCH] Support REI protocol
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index d89a1aa9355205883412eaaf535dad30f945a4dc..33e05636164144b3d2bdbd091c72583728cc294e 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -1624,6 +1624,7 @@ public abstract class PlayerList {
serverPlayer.getRecipeBook().sendInitialRecipeBook(serverPlayer);
}
org.leavesmc.leaves.protocol.BBORProtocol.onDataPackReload(); // Leaves - bbor
+ org.leavesmc.leaves.protocol.rei.REIServerProtocol.onRecipeReload(); // Leaves - rei
}
public boolean isAllowCommandsForAllPlayers() {
diff --git a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
index 143601053a6aeea4396f8e0ee0746ff7d5bbb323..0af83ec1fe3aa85c6bfc814a1339a54e9d3725d6 100644
--- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
@@ -87,6 +87,12 @@ public class SmithingTransformRecipe implements SmithingRecipe {
);
}
+ // Leaves start
+ public ItemStack getResult() {
+ return this.result.copy();
+ }
+ // Leaves end
+
// CraftBukkit start
@Override
public org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id) {

View File

@@ -841,13 +841,22 @@ public final class LeavesConfig {
@GlobalConfig("lms-paster-protocol") @GlobalConfig("lms-paster-protocol")
public boolean lmsPasterProtocol = false; public boolean lmsPasterProtocol = false;
@GlobalConfig("rei-server-protocol") @GlobalConfig(value = "rei-server-protocol", validator = ReiValidator.class)
public boolean reiServerProtocol = false; public boolean reiServerProtocol = false;
public static class ReiValidator extends BooleanConfigValidator {
@Override
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
if (old != value && value != null) {
org.leavesmc.leaves.protocol.rei.REIServerProtocol.onConfigModify(value);
}
}
}
@GlobalConfig("chat-image-protocol") @GlobalConfig("chat-image-protocol")
public boolean chatImageProtocol = false; public boolean chatImageProtocol = false;
@GlobalConfig("recipe-send-all") @RemovedConfig(name = "recipe-send-all", category = {"protocol"})
public boolean recipeSendAll = false; public boolean recipeSendAll = false;
} }

View File

@@ -0,0 +1,138 @@
package org.leavesmc.leaves.protocol.rei;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.leavesmc.leaves.LeavesLogger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
public class PacketTransformer {
private static final byte START = 0x0;
private static final byte PART = 0x1;
private static final byte END = 0x2;
private static final byte ONLY = 0x3;
private final Map<UUID, PartData> cache = Collections.synchronizedMap(new HashMap<>());
private static class PartData {
private final ResourceLocation id;
private final int partsNum;
private final List<RegistryFriendlyByteBuf> parts;
public PartData(ResourceLocation id, int partsNum) {
this.id = id;
this.partsNum = partsNum;
this.parts = new ArrayList<>();
}
}
public void inbound(ResourceLocation id, RegistryFriendlyByteBuf buf, ServerPlayer player, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
UUID key = player.getUUID();
PartData data;
switch (buf.readByte()) {
case START -> {
int partsNum = buf.readInt();
data = new PartData(id, partsNum);
if (cache.put(key, data) != null) {
LeavesLogger.LOGGER.warning("Received invalid START packet for SplitPacketTransformer with packet id " + id);
}
buf.retain();
data.parts.add(buf);
}
case PART -> {
if ((data = cache.get(key)) == null) {
LeavesLogger.LOGGER.warning("Received invalid PART packet for SplitPacketTransformer with packet id " + id);
buf.release();
} else if (!data.id.equals(id)) {
LeavesLogger.LOGGER.warning("Received invalid PART packet for SplitPacketTransformer with packet id " + id + ", id in cache is {}" + data.id);
buf.release();
for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
cache.remove(key);
} else {
buf.retain();
data.parts.add(buf);
}
}
case END -> {
if ((data = cache.get(key)) == null) {
LeavesLogger.LOGGER.warning("Received invalid END packet for SplitPacketTransformer with packet id {}" + id);
buf.release();
} else if (!data.id.equals(id)) {
LeavesLogger.LOGGER.warning("Received invalid END packet for SplitPacketTransformer with packet id " + id + ", id in cache is {}" + data.id);
buf.release();
for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
cache.remove(key);
} else {
buf.retain();
data.parts.add(buf);
}
if (data == null) {
return;
}
if (data.parts.size() != data.partsNum) {
LeavesLogger.LOGGER.warning("Received invalid END packet for SplitPacketTransformer with packet id " + id + " with size " + data.parts + ", parts expected is {}" + data.partsNum);
for (RegistryFriendlyByteBuf part : data.parts) {
if (part != buf) {
part.release();
}
}
} else {
RegistryFriendlyByteBuf byteBuf = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(data.parts.toArray(new ByteBuf[0])), buf.registryAccess());
consumer.accept(data.id, byteBuf);
byteBuf.release();
}
cache.remove(key);
}
case ONLY -> consumer.accept(id, buf);
default -> throw new IllegalStateException("Illegal split packet header!");
}
}
public void outbound(ResourceLocation id, RegistryFriendlyByteBuf buf, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
int maxSize = 1048576 - 1 - 20 - id.toString().getBytes(StandardCharsets.UTF_8).length;
if (buf.readableBytes() <= maxSize) {
ByteBuf stateBuf = Unpooled.buffer(1);
stateBuf.writeByte(ONLY);
RegistryFriendlyByteBuf packetBuffer = new RegistryFriendlyByteBuf(Unpooled.wrappedBuffer(stateBuf, buf), buf.registryAccess());
consumer.accept(id, packetBuffer);
} else {
int partSize = maxSize - 4;
int parts = (int) Math.ceil(buf.readableBytes() / (float) partSize);
for (int i = 0; i < parts; i++) {
RegistryFriendlyByteBuf packetBuffer = new RegistryFriendlyByteBuf(Unpooled.buffer(), buf.registryAccess());
if (i == 0) {
packetBuffer.writeByte(START);
packetBuffer.writeInt(parts);
} else if (i == parts - 1) {
packetBuffer.writeByte(END);
} else {
packetBuffer.writeByte(PART);
}
int next = Math.min(buf.readableBytes(), partSize);
packetBuffer.writeBytes(buf.retainedSlice(buf.readerIndex(), next));
buf.skipBytes(next);
consumer.accept(id, packetBuffer);
}
buf.release();
}
}
}

View File

@@ -1,107 +1,341 @@
package org.leavesmc.leaves.protocol.rei; package org.leavesmc.leaves.protocol.rei;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.SmithingTemplateItem;
import net.minecraft.world.item.crafting.FireworkRocketRecipe;
import net.minecraft.world.item.crafting.MapCloningRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeMap;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.item.crafting.ShapelessRecipe;
import net.minecraft.world.item.crafting.SmithingTrimRecipe;
import net.minecraft.world.item.crafting.TippedArrowRecipe;
import net.minecraft.world.item.crafting.TransmuteRecipe;
import org.bukkit.Bukkit;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesConfig; import org.leavesmc.leaves.LeavesConfig;
import org.leavesmc.leaves.protocol.core.LeavesProtocol; import org.leavesmc.leaves.protocol.core.LeavesProtocol;
import org.leavesmc.leaves.protocol.core.LeavesProtocolManager.EmptyPayload;
import org.leavesmc.leaves.protocol.core.ProtocolHandler; import org.leavesmc.leaves.protocol.core.ProtocolHandler;
import org.leavesmc.leaves.protocol.core.ProtocolUtils; import org.leavesmc.leaves.protocol.core.ProtocolUtils;
import org.leavesmc.leaves.protocol.rei.payload.CreateItemGrabPayload; import org.leavesmc.leaves.protocol.rei.display.BlastingDisplay;
import org.leavesmc.leaves.protocol.rei.payload.CreateItemHotbarPayload; import org.leavesmc.leaves.protocol.rei.display.CampfireDisplay;
import org.leavesmc.leaves.protocol.rei.payload.CreateItemMessagePayload; import org.leavesmc.leaves.protocol.rei.display.Display;
import org.leavesmc.leaves.protocol.rei.payload.CreateItemPayload; import org.leavesmc.leaves.protocol.rei.display.ShapedDisplay;
import org.leavesmc.leaves.protocol.rei.display.ShapelessDisplay;
import org.leavesmc.leaves.protocol.rei.display.SmeltingDisplay;
import org.leavesmc.leaves.protocol.rei.display.SmokingDisplay;
import org.leavesmc.leaves.protocol.rei.display.StoneCuttingDisplay;
import org.leavesmc.leaves.protocol.rei.payload.BufCustomPacketPayload;
import org.leavesmc.leaves.protocol.rei.payload.DisplaySyncPayload;
// @LeavesProtocol(namespace = "roughlyenoughitems") TODO will fix import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@LeavesProtocol(namespace = REIServerProtocol.PROTOCOL_ID)
public class REIServerProtocol { public class REIServerProtocol {
public static final String PROTOCOL_ID = "roughlyenoughitems"; public static final String PROTOCOL_ID = "roughlyenoughitems";
public static final String CHEAT_PERMISSION = "leaves.protocol.rei.cheat";
public static final ResourceLocation DELETE_ITEMS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "delete_item");
public static final ResourceLocation CREATE_ITEMS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item");
public static final ResourceLocation CREATE_ITEMS_GRAB_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item_grab");
public static final ResourceLocation CREATE_ITEMS_HOTBAR_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item_hotbar");
public static final ResourceLocation CREATE_ITEMS_MESSAGE_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "ci_msg");
public static final ResourceLocation SYNC_DISPLAYS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "sync_displays");
public static final Map<ResourceLocation, PacketTransformer> TRANSFORMERS = Util.make(() -> {
ImmutableMap.Builder<ResourceLocation, PacketTransformer> builder = ImmutableMap.builder();
builder.put(SYNC_DISPLAYS_PACKET, new PacketTransformer());
builder.put(DELETE_ITEMS_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_GRAB_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_HOTBAR_PACKET, new PacketTransformer());
return builder.build();
});
private static final Set<ServerPlayer> enabledPlayers = new HashSet<>();
private static int minecraftRecipeVer = 0;
private static int nextReiRecipeVer = -1;
private static ImmutableList<CustomPacketPayload> cachedPayloads;
private static final Executor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
public static void onRecipeReload() {
minecraftRecipeVer = MinecraftServer.getServer().getTickCount();
}
@Contract("_ -> new") @Contract("_ -> new")
public static ResourceLocation id(String path) { public static ResourceLocation id(String path) {
return ResourceLocation.tryBuild(PROTOCOL_ID, path); return ResourceLocation.tryBuild(PROTOCOL_ID, path);
} }
@ProtocolHandler.PayloadReceiver(payload = EmptyPayload.class, payloadId = "delete_item") public static void onConfigModify(boolean enabled) {
public static void handleDeleteItem(ServerPlayer player, EmptyPayload payload) { PluginManager pluginManager = Bukkit.getServer().getPluginManager();
if (!check(player, true)) { if (enabled) {
return; if (pluginManager.getPermission(CHEAT_PERMISSION) == null) {
} pluginManager.addPermission(new Permission(CHEAT_PERMISSION, PermissionDefault.OP));
}
AbstractContainerMenu menu = player.containerMenu;
if (!menu.getCarried().isEmpty()) {
menu.setCarried(ItemStack.EMPTY);
menu.broadcastChanges();
}
}
@ProtocolHandler.PayloadReceiver(payload = CreateItemPayload.class, payloadId = "create_item")
public static void handleCreateItem(ServerPlayer player, CreateItemPayload payload) {
if (!check(player, true)) {
return;
}
ItemStack stack = payload.item();
if (player.getInventory().add(stack.copy())) {
ProtocolUtils.sendPayloadPacket(player, new CreateItemMessagePayload(stack.copy(), player.getScoreboardName()));
} else { } else {
player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false); pluginManager.removePermission(CHEAT_PERMISSION);
enabledPlayers.clear();
} }
} }
@ProtocolHandler.PayloadReceiver(payload = CreateItemGrabPayload.class, payloadId = "create_item_grab") @ProtocolHandler.PlayerLeave
public static void handleCreateItemGrab(ServerPlayer player, CreateItemGrabPayload payload) { public static void onPlayerLoggedOut(@NotNull ServerPlayer player) {
if (!check(player, true)) { if (LeavesConfig.protocol.reiServerProtocol) {
return; enabledPlayers.remove(player);
}
AbstractContainerMenu menu = player.containerMenu;
ItemStack itemStack = payload.item();
ItemStack stack = itemStack.copy();
if (!menu.getCarried().isEmpty() && ItemStack.isSameItemSameComponents(menu.getCarried(), stack)) {
stack.setCount(Mth.clamp(stack.getCount() + menu.getCarried().getCount(), 1, stack.getMaxStackSize()));
} else if (!menu.getCarried().isEmpty()) {
return;
}
menu.setCarried(stack.copy());
menu.broadcastChanges();
ProtocolUtils.sendPayloadPacket(player, new CreateItemMessagePayload(stack, player.getScoreboardName()));
}
@ProtocolHandler.PayloadReceiver(payload = CreateItemHotbarPayload.class, payloadId = "create_item_hotbar")
public static void handleCreateItemHotbar(ServerPlayer player, CreateItemHotbarPayload payload) {
if (!check(player, true)) {
return;
}
ItemStack stack = payload.item();
int hotbarSlotId = payload.hotbarSlot();
if (hotbarSlotId >= 0 && hotbarSlotId < 9) {
AbstractContainerMenu menu = player.containerMenu;
player.getInventory().items.set(hotbarSlotId, stack.copy());
menu.broadcastChanges();
ProtocolUtils.sendPayloadPacket(player, new CreateItemMessagePayload(stack, player.getScoreboardName()));
} else {
player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false);
} }
} }
private static boolean check(ServerPlayer player, boolean needOP) { @ProtocolHandler.Ticker
public static void tick() {
if (!LeavesConfig.protocol.reiServerProtocol) { if (!LeavesConfig.protocol.reiServerProtocol) {
return false; return;
} }
if (MinecraftServer.getServer().getTickCount() % 200 == 1 && minecraftRecipeVer != nextReiRecipeVer) {
nextReiRecipeVer = minecraftRecipeVer;
executor.execute(() -> reloadRecipe(nextReiRecipeVer));
}
}
if (needOP && MinecraftServer.getServer().getPlayerList().isOp(player.gameProfile)) { // TODO check permission node @SuppressWarnings({"unchecked", "rawtypes"})
player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false); private static void reloadRecipe(int reiRecipeVer) {
return false; ImmutableList.Builder<Display> builder = ImmutableList.builder();
MinecraftServer server = MinecraftServer.getServer();
RecipeMap recipeMap = server.getRecipeManager().recipes;
recipeMap.byType(RecipeType.CRAFTING).forEach(holder -> {
switch (holder.value()) {
case ShapedRecipe ignored -> builder.add(new ShapedDisplay((RecipeHolder) holder));
case ShapelessRecipe ignored -> builder.add(new ShapelessDisplay((RecipeHolder) holder));
case TransmuteRecipe ignored -> builder.addAll(Display.ofTransmuteRecipe((RecipeHolder) holder));
case TippedArrowRecipe ignored -> builder.addAll(Display.ofTippedArrowRecipe((RecipeHolder) holder));
case FireworkRocketRecipe ignored -> builder.addAll(Display.ofFireworkRocketRecipe((RecipeHolder) holder));
case MapCloningRecipe ignored -> builder.addAll(Display.ofMapCloningRecipe((RecipeHolder) holder));
// ignore ArmorDyeRecipe, BannerDuplicateRecipe, BookCloningRecipe, ShieldDecorationRecipe
default -> {
}
}
});
recipeMap.byType(RecipeType.STONECUTTING).forEach(holder -> builder.add(new StoneCuttingDisplay(holder)));
recipeMap.byType(RecipeType.SMELTING).forEach(holder -> builder.add(new SmeltingDisplay(holder)));
recipeMap.byType(RecipeType.BLASTING).forEach(holder -> builder.add(new BlastingDisplay(holder)));
recipeMap.byType(RecipeType.SMOKING).forEach(holder -> builder.add(new SmokingDisplay(holder)));
recipeMap.byType(RecipeType.CAMPFIRE_COOKING).forEach(holder -> builder.add(new CampfireDisplay(holder)));
recipeMap.byType(RecipeType.SMITHING).forEach(holder -> {
switch (holder.value()) {
case SmithingTrimRecipe ignored -> builder.addAll(Display.ofSmithingTrimRecipe((RecipeHolder) holder));
case SmithingTemplateItem ignored -> builder.add(Display.ofTransforming((RecipeHolder) holder));
default -> {
}
}
});
DisplaySyncPayload displaySyncPayload = new DisplaySyncPayload(
DisplaySyncPayload.SyncType.SET,
builder.build(),
reiRecipeVer
);
RegistryFriendlyByteBuf s2cBuf = ProtocolUtils.decorate(Unpooled.buffer());
DisplaySyncPayload.STREAM_CODEC.encode(s2cBuf, displaySyncPayload);
ImmutableList.Builder<CustomPacketPayload> listBuilder = ImmutableList.builder();
outboundTransform(SYNC_DISPLAYS_PACKET, s2cBuf, (id, splitBuf) ->
listBuilder.add(new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(splitBuf)))
);
cachedPayloads = listBuilder.build();
MinecraftServer.getServer().execute(() -> {
for (ServerPlayer player : enabledPlayers) {
for (CustomPacketPayload payload : cachedPayloads) {
ProtocolUtils.sendPayloadPacket(player, payload);
}
}
});
}
@ProtocolHandler.MinecraftRegister(ignoreId = true)
public static void onPlayerSubscribed(@NotNull ServerPlayer player, String channel) {
if (!LeavesConfig.protocol.reiServerProtocol) {
return;
} }
return true; enabledPlayers.add(player);
if (channel.equals("sync_displays")) {
if (cachedPayloads != null) {
cachedPayloads.forEach(payload -> ProtocolUtils.sendPayloadPacket(player, payload));
}
} else if (channel.equals("ci_msg")) {
// cheat rei-client into using "delete_item" packet
if (player.getServer().getProfilePermissions(player.getGameProfile()) < 1) {
player.getBukkitEntity().sendOpLevel((byte) 1);
}
}
}
@ProtocolHandler.PayloadReceiver(payload = BufCustomPacketPayload.class, payloadId = "delete_item")
public static void handleDeleteItem(ServerPlayer player, BufCustomPacketPayload payload) {
if (!LeavesConfig.protocol.reiServerProtocol || !hasCheatPermission(player)) {
return;
}
RegistryFriendlyByteBuf c2sBuf = ProtocolUtils.decorate(Unpooled.buffer());
c2sBuf.writeBytes(payload.payload());
inboundTransform(player, payload.id(), c2sBuf, (id, wholeBuf) -> {
AbstractContainerMenu menu = player.containerMenu;
if (!menu.getCarried().isEmpty()) {
menu.setCarried(ItemStack.EMPTY);
menu.broadcastChanges();
}
});
}
@ProtocolHandler.PayloadReceiver(payload = BufCustomPacketPayload.class, payloadId = "create_item")
public static void handleCreateItem(ServerPlayer player, BufCustomPacketPayload payload) {
if (!LeavesConfig.protocol.reiServerProtocol || !hasCheatPermission(player)) {
return;
}
RegistryFriendlyByteBuf c2sBuf = ProtocolUtils.decorate(Unpooled.buffer());
c2sBuf.writeBytes(payload.payload());
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
ItemStack itemStack = tmpBuf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC);
if (player.getInventory().add(itemStack.copy())) {
RegistryFriendlyByteBuf s2cWholeBuf = ProtocolUtils.decorate(Unpooled.buffer());
s2cWholeBuf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, itemStack.copy());
s2cWholeBuf.writeUtf(player.getScoreboardName(), 32767);
// Due to the bug in REI, no packets are actually sent here.
/*
outboundTransform(CREATE_ITEMS_MESSAGE_PACKET, s2cWholeBuf, (id, s2cSplitBuf) -> {
ProtocolUtils.sendPayloadPacket(player, new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(s2cSplitBuf)));
});
*/
} else {
player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false);
}
};
inboundTransform(player, payload.id(), c2sBuf, consumer);
}
@ProtocolHandler.PayloadReceiver(payload = BufCustomPacketPayload.class, payloadId = "create_item_grab")
public static void handleCreateItemGrab(ServerPlayer player, BufCustomPacketPayload payload) {
if (!LeavesConfig.protocol.reiServerProtocol || !hasCheatPermission(player)) {
return;
}
RegistryFriendlyByteBuf c2sBuf = ProtocolUtils.decorate(Unpooled.buffer());
c2sBuf.writeBytes(payload.payload());
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
ItemStack itemStack = tmpBuf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC);
ItemStack stack = itemStack.copy();
AbstractContainerMenu menu = player.containerMenu;
if (!menu.getCarried().isEmpty() && ItemStack.isSameItemSameComponents(menu.getCarried(), stack)) {
stack.setCount(Mth.clamp(stack.getCount() + menu.getCarried().getCount(), 1, stack.getMaxStackSize()));
} else if (!menu.getCarried().isEmpty()) {
return;
}
menu.setCarried(stack.copy());
menu.broadcastChanges();
RegistryFriendlyByteBuf s2cWholeBuf = ProtocolUtils.decorate(Unpooled.buffer());
s2cWholeBuf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, stack.copy());
s2cWholeBuf.writeUtf(player.getScoreboardName(), 32767);
// Due to the bug in REI, no packets are actually sent here.
/*
outboundTransform(CREATE_ITEMS_MESSAGE_PACKET, s2cWholeBuf, (id, s2cSplitBuf) -> {
ProtocolUtils.sendPayloadPacket(player, new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(s2cSplitBuf)));
});
*/
};
inboundTransform(player, payload.id(), c2sBuf, consumer);
}
@ProtocolHandler.PayloadReceiver(payload = BufCustomPacketPayload.class, payloadId = "create_item_hotbar")
public static void handleCreateItemHotbar(ServerPlayer player, BufCustomPacketPayload payload) {
if (!LeavesConfig.protocol.reiServerProtocol || !hasCheatPermission(player)) {
return;
}
RegistryFriendlyByteBuf c2sBuf = ProtocolUtils.decorate(Unpooled.buffer());
c2sBuf.writeBytes(payload.payload());
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
ItemStack stack = tmpBuf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC);
int hotbarSlotId = tmpBuf.readVarInt();
if (hotbarSlotId >= 0 && hotbarSlotId < 9) {
AbstractContainerMenu menu = player.containerMenu;
player.getInventory().items.set(hotbarSlotId, stack.copy());
menu.broadcastChanges();
RegistryFriendlyByteBuf s2cWholeBuf = ProtocolUtils.decorate(Unpooled.buffer());
s2cWholeBuf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, stack.copy());
s2cWholeBuf.writeUtf(player.getScoreboardName(), 32767);
// Due to the bug in REI, no packets are actually sent here.
/*
outboundTransform(CREATE_ITEMS_MESSAGE_PACKET, s2cWholeBuf, (id, s2cSplitBuf) -> {
ProtocolUtils.sendPayloadPacket(player, new BufCustomPacketPayload(new CustomPacketPayload.Type<>(id), ByteBufUtil.getBytes(s2cSplitBuf)));
});
*/
} else {
player.displayClientMessage(Component.translatable("text.rei.failed_cheat_items"), false);
}
};
inboundTransform(player, payload.id(), c2sBuf, consumer);
}
private static void inboundTransform(ServerPlayer player,
ResourceLocation id,
RegistryFriendlyByteBuf buf,
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
PacketTransformer transformer = TRANSFORMERS.get(id);
if (transformer != null) {
transformer.inbound(id, buf, player, consumer);
} else {
consumer.accept(id, buf);
}
}
private static void outboundTransform(ResourceLocation id,
RegistryFriendlyByteBuf buf,
BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
PacketTransformer transformer = TRANSFORMERS.get(id);
if (transformer != null) {
transformer.outbound(id, buf, consumer);
} else {
consumer.accept(id, buf);
}
}
private static boolean hasCheatPermission(ServerPlayer player) {
if (player.getBukkitEntity().hasPermission(CHEAT_PERMISSION)) {
return true;
}
player.displayClientMessage(Component.translatable("text.rei.no_permission_cheat").withStyle(ChatFormatting.RED), false);
return false;
} }
} }

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
public class BlastingDisplay extends CookingDisplay {
public BlastingDisplay(RecipeHolder<? extends AbstractCookingRecipe> recipe) {
super(recipe);
}
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/blasting");
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.CampfireCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
public class CampfireDisplay extends CookingDisplay {
public CampfireDisplay(RecipeHolder<CampfireCookingRecipe> recipe) {
super(recipe);
}
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/campfire");
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}

View File

@@ -0,0 +1,68 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
public abstract class CookingDisplay extends Display {
protected float xp;
protected double cookTime;
private static final StreamCodec<RegistryFriendlyByteBuf, CookingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CookingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CookingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CookingDisplay::getOptionalLocation,
ByteBufCodecs.FLOAT,
CookingDisplay::getXp,
ByteBufCodecs.DOUBLE,
CookingDisplay::getCookTime,
CookingDisplay::of
);
private CookingDisplay(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull ResourceLocation id, float xp, double cookTime) {
super(inputs, outputs, id);
this.xp = xp;
this.cookTime = cookTime;
}
public CookingDisplay(RecipeHolder<? extends AbstractCookingRecipe> recipe) {
this(
List.of(EntryIngredient.ofIngredient(recipe.value().input())),
List.of(EntryIngredient.of(recipe.value().assemble(new SingleRecipeInput(ItemStack.EMPTY), CraftRegistry.getMinecraftRegistry()))),
recipe.id().location(),
recipe.value().experience(),
recipe.value().cookingTime()
);
}
public float getXp() {
return xp;
}
public double getCookTime() {
return cookTime;
}
public StreamCodec<RegistryFriendlyByteBuf, CookingDisplay> streamCodec() {
return CODEC;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CookingDisplay of(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull Optional<ResourceLocation> id, float xp, double cookTime) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,21 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
public abstract class CraftingDisplay extends Display {
public CraftingDisplay(@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull ResourceLocation location) {
super(inputs, outputs, location);
}
public abstract int getWidth();
public abstract int getHeight();
}

View File

@@ -0,0 +1,72 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.BitSet;
import java.util.List;
import java.util.Optional;
public class CustomDisplay extends CraftingDisplay {
private final int width;
private final int height;
private static final StreamCodec<RegistryFriendlyByteBuf, CustomDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CustomDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CustomDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CustomDisplay::getOptionalLocation,
CustomDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/crafting/custom");
/**
* see me.shedaniel.rei.plugin.common.displays.crafting.DefaultCustomDisplay#DefaultCustomDisplay
*/
public CustomDisplay(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull ResourceLocation location) {
super(inputs, outputs, location);
BitSet row = new BitSet(3);
BitSet column = new BitSet(3);
for (int i = 0; i < 9; i++)
if (i < inputs.size()) {
EntryIngredient stacks = inputs.get(i);
if (stacks.stream().anyMatch(stack -> !stack.isEmpty())) {
row.set((i - (i % 3)) / 3);
column.set(i % 3);
}
}
this.width = column.cardinality();
this.height = row.cardinality();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
public StreamCodec<RegistryFriendlyByteBuf, CustomDisplay> streamCodec() {
return CODEC;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CustomDisplay of(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull Optional<ResourceLocation> id) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,327 @@
package org.leavesmc.leaves.protocol.rei.display;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tags.TagKey;
import net.minecraft.util.context.ContextMap;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.item.component.Fireworks;
import net.minecraft.world.item.crafting.FireworkRocketRecipe;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.MapCloningRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SmithingTransformRecipe;
import net.minecraft.world.item.crafting.SmithingTrimRecipe;
import net.minecraft.world.item.crafting.TippedArrowRecipe;
import net.minecraft.world.item.crafting.TransmuteRecipe;
import net.minecraft.world.item.crafting.display.RecipeDisplay;
import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay;
import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay;
import net.minecraft.world.item.crafting.display.SlotDisplay;
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
import net.minecraft.world.item.equipment.trim.ArmorTrim;
import net.minecraft.world.item.equipment.trim.TrimMaterial;
import net.minecraft.world.item.equipment.trim.TrimMaterials;
import net.minecraft.world.item.equipment.trim.TrimPattern;
import net.minecraft.world.item.equipment.trim.TrimPatterns;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
/**
* A display to be used alongside Roughly Enough Items.
* <p>
* see me.shedaniel.rei.api.common.display.Display
*/
public abstract class Display {
protected ResourceLocation id;
protected List<EntryIngredient> inputs;
protected List<EntryIngredient> outputs;
public Display(@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull ResourceLocation id) {
this.inputs = inputs;
this.outputs = outputs;
this.id = id;
}
public List<EntryIngredient> getInputEntries() {
return inputs;
}
public List<EntryIngredient> getOutputEntries() {
return outputs;
}
public ResourceLocation getDisplayLocation() {
return id;
}
public Optional<ResourceLocation> getOptionalLocation() {
return Optional.ofNullable(id);
}
@SuppressWarnings("unchecked")
public static StreamCodec<RegistryFriendlyByteBuf, Display> dispatchCodec() {
return new StreamCodec<>() {
@NotNull
@Override
public Display decode(@NotNull RegistryFriendlyByteBuf buffer) {
throw new UnsupportedOperationException();
}
@Override
public void encode(@NotNull RegistryFriendlyByteBuf buffer, @NotNull Display display) {
new FriendlyByteBuf(buffer).writeResourceLocation(display.getSerializerId());
((StreamCodec<RegistryFriendlyByteBuf, Display>) display.streamCodec()).encode(buffer, display);
}
};
}
public abstract ResourceLocation getSerializerId();
public abstract StreamCodec<RegistryFriendlyByteBuf, ? extends Display> streamCodec();
public static Collection<Display> ofTransmuteRecipe(@NotNull RecipeHolder<TransmuteRecipe> recipeHolder) {
TransmuteRecipe recipe = recipeHolder.value();
List<RecipeDisplay> displays = recipe.display();
List<Display> displayList = new ArrayList<>();
if (!displays.isEmpty()) {
RecipeDisplay recipeDisplay = displays.getFirst();
if (recipeDisplay instanceof ShapelessCraftingRecipeDisplay shapelessRecipeDisplay) {
displayList.add(new ShapelessDisplay(shapelessRecipeDisplay, recipeHolder.id().location()));
} else if (recipeDisplay instanceof ShapedCraftingRecipeDisplay shapelessRecipe) {
displayList.add(new ShapedDisplay(shapelessRecipe, recipeHolder.id().location()));
}
}
return displayList;
}
/**
* see me.shedaniel.rei.plugin.client.categories.crafting.filler.TippedArrowRecipeFiller#apply
*/
@NotNull
public static Collection<Display> ofTippedArrowRecipe(@NotNull RecipeHolder<TippedArrowRecipe> recipeHolder) {
EntryIngredient arrowIngredient = EntryIngredient.of(Items.ARROW);
Set<ResourceLocation> registeredPotions = new HashSet<>();
List<Display> displays = new ArrayList<>();
MinecraftServer.getServer().registryAccess().lookup(Registries.POTION).stream()
.flatMap(Registry::listElements)
.map(reference -> PotionContents.createItemStack(Items.LINGERING_POTION, reference))
.forEach(itemStack -> {
PotionContents potion = itemStack.get(DataComponents.POTION_CONTENTS);
if (potion == null || potion.potion().isEmpty()) {
return;
}
if (potion.potion().get().unwrapKey().isPresent() && registeredPotions.add(potion.potion().get().unwrapKey().get().location())) {
List<EntryIngredient> input = new ArrayList<>();
for (int i = 0; i < 4; i++)
input.add(arrowIngredient);
input.add(EntryIngredient.of(itemStack));
for (int i = 0; i < 4; i++)
input.add(arrowIngredient);
ItemStack outputStack = new ItemStack(Items.TIPPED_ARROW, 8);
outputStack.set(DataComponents.POTION_CONTENTS, potion);
displays.add(new CustomDisplay(input, List.of(EntryIngredient.of(outputStack)), recipeHolder.id().location()));
}
});
return displays;
}
/**
* see me.shedaniel.rei.plugin.client.categories.crafting.filler.TippedArrowRecipeFiller#apply
*/
@NotNull
public static Collection<Display> ofFireworkRocketRecipe(@NotNull RecipeHolder<FireworkRocketRecipe> recipeHolder) {
EntryIngredient[] inputs = new EntryIngredient[4];
inputs[0] = EntryIngredient.of(Items.GUNPOWDER);
inputs[1] = EntryIngredient.of(Items.PAPER);
inputs[2] = EntryIngredient.of(new ItemStack(Items.AIR), new ItemStack(Items.GUNPOWDER), new ItemStack(Items.GUNPOWDER));
inputs[3] = EntryIngredient.of(new ItemStack(Items.AIR), new ItemStack(Items.AIR), new ItemStack(Items.GUNPOWDER));
ItemStack[] outputs = new ItemStack[3];
for (int i = 0; i < 3; i++) {
outputs[i] = new ItemStack(Items.FIREWORK_ROCKET, 3);
outputs[i].set(DataComponents.FIREWORKS, new Fireworks(i + 1, List.of()));
}
return Collections.singleton(new ShapelessDisplay(List.of(inputs), List.of(EntryIngredient.of(outputs)), recipeHolder.id().location()));
}
/**
* see me.shedaniel.rei.plugin.client.categories.crafting.filler.MapCloningRecipeFiller#apply
*/
@NotNull
public static Collection<Display> ofMapCloningRecipe(@NotNull RecipeHolder<MapCloningRecipe> recipeHolder) {
return Collections.singleton(
new ShapelessDisplay(
List.of(EntryIngredient.of(Items.FILLED_MAP), EntryIngredient.of(Items.MAP)),
List.of(EntryIngredient.of(new ItemStack(Items.FILLED_MAP, 2))),
recipeHolder.id().location())
);
}
/**
* see me.shedaniel.rei.plugin.common.displays.DefaultSmithingDisplay#ofTransforming
*/
@NotNull
public static SmithingDisplay ofTransforming(RecipeHolder<SmithingTransformRecipe> recipeHolder) {
return new SmithingDisplay(
List.of(
recipeHolder.value().templateIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty()),
recipeHolder.value().baseIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty()),
recipeHolder.value().additionIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty())
),
List.of(EntryIngredient.of(recipeHolder.value().getResult())),
SmithingDisplay.SmithingRecipeType.TRANSFORM,
recipeHolder.id().location()
);
}
/**
* see me.shedaniel.rei.plugin.common.displays.DefaultSmithingDisplay#fromTrimming
*/
@NotNull
public static Collection<Display> ofSmithingTrimRecipe(@NotNull RecipeHolder<SmithingTrimRecipe> recipeHolder) {
RegistryAccess registryAccess = MinecraftServer.getServer().registryAccess();
SmithingTrimRecipe recipe = recipeHolder.value();
List<Display> displays = new ArrayList<>();
for (Holder<Item> templateItem : (Iterable<Holder<Item>>) recipe.templateIngredient().map(Ingredient::items).orElse(Stream.of())::iterator) {
Holder.Reference<TrimPattern> trimPattern = getPatternFromTemplate(registryAccess, templateItem)
.orElse(null);
if (trimPattern == null) continue;
for (Holder<Item> additionStack : (Iterable<Holder<Item>>) recipe.additionIngredient().map(Ingredient::items).orElse(Stream.of())::iterator) {
Holder.Reference<TrimMaterial> trimMaterial = getMaterialFromIngredient(registryAccess, additionStack)
.orElse(null);
if (trimMaterial == null) continue;
EntryIngredient baseIngredient = recipe.baseIngredient().map(EntryIngredient::ofIngredient).orElse(EntryIngredient.empty());
EntryIngredient templateOutput = baseIngredient.isEmpty() ? EntryIngredient.empty()
: getTrimmingOutput(registryAccess, templateItem.value().getDefaultInstance(), baseIngredient.get(0), additionStack.value().getDefaultInstance());
displays.add(new SmithingDisplay(List.of(
EntryIngredient.ofItemHolder(templateItem),
baseIngredient,
EntryIngredient.ofItemHolder(additionStack)
), List.of(templateOutput), SmithingDisplay.SmithingRecipeType.TRIM, recipeHolder.id().location()));
}
}
return displays;
}
public static EntryIngredient getTrimmingOutput(RegistryAccess registryAccess, ItemStack templateItem, ItemStack baseItem, ItemStack additionItem) {
Holder.Reference<TrimPattern> trimPattern = TrimPatterns.getFromTemplate(registryAccess, templateItem)
.orElse(null);
if (trimPattern == null) return EntryIngredient.empty();
Holder.Reference<TrimMaterial> trimMaterial = TrimMaterials.getFromIngredient(registryAccess, additionItem)
.orElse(null);
if (trimMaterial == null) return EntryIngredient.empty();
ArmorTrim armorTrim = new ArmorTrim(trimMaterial, trimPattern);
ArmorTrim trim = baseItem.get(DataComponents.TRIM);
if (trim != null && trim.hasPatternAndMaterial(trimPattern, trimMaterial)) return EntryIngredient.empty();
ItemStack newItem = baseItem.copyWithCount(1);
newItem.set(DataComponents.TRIM, armorTrim);
return EntryIngredient.of(newItem);
}
private static Optional<Holder.Reference<TrimPattern>> getPatternFromTemplate(HolderLookup.Provider provider, Holder<Item> item) {
return provider.lookupOrThrow(Registries.TRIM_PATTERN)
.listElements()
.filter(reference -> item == reference.value().templateItem())
.findFirst();
}
private static Optional<Holder.Reference<TrimMaterial>> getMaterialFromIngredient(HolderLookup.Provider provider, Holder<Item> item) {
return provider.lookupOrThrow(Registries.TRIM_MATERIAL)
.listElements()
.filter(reference -> item == reference.value().ingredient())
.findFirst();
}
public static EntryIngredient ofSlotDisplay(SlotDisplay slot) {
return switch (slot) {
case SlotDisplay.Empty ignored -> EntryIngredient.empty();
case SlotDisplay.ItemSlotDisplay s -> EntryIngredient.of(s.item().value());
case SlotDisplay.ItemStackSlotDisplay s -> EntryIngredient.of(s.stack());
case SlotDisplay.TagSlotDisplay s -> ofItemTag(s.tag());
case SlotDisplay.Composite s -> {
ArrayList<ItemStack> list = new ArrayList<>();
for (SlotDisplay slotDisplay : s.contents()) {
ofSlotDisplay(slotDisplay).stream().forEach(list::add);
}
yield EntryIngredient.of(list.toArray(new ItemStack[0]));
}
// REI Bad idea
case SlotDisplay.AnyFuel ignored -> EntryIngredient.empty();
default -> {
RegistryAccess access = MinecraftServer.getServer().registryAccess();
try {
List<ItemStack> stacks = slot.resolveForStacks(new ContextMap.Builder()
.withParameter(SlotDisplayContext.REGISTRIES, access)
.create(SlotDisplayContext.CONTEXT));
yield EntryIngredient.of(stacks.toArray(new ItemStack[0]));
} catch (Exception e) {
MinecraftServer.LOGGER.warn("Failed to resolve slot display: {}", slot, e);
yield EntryIngredient.empty();
}
}
};
}
public static List<EntryIngredient> ofSlotDisplays(Collection<SlotDisplay> slots) {
if (slots instanceof Collection<?> collection && collection.isEmpty()) return Collections.emptyList();
ImmutableList.Builder<EntryIngredient> ingredients = ImmutableList.builder();
for (SlotDisplay slot : slots) {
ingredients.add(ofSlotDisplay(slot));
}
return ingredients.build();
}
public static <T extends ItemLike> EntryIngredient ofItemTag(TagKey<T> tagKey) {
HolderGetter<T> getter = MinecraftServer.getServer().registryAccess().lookupOrThrow(tagKey.registry());
HolderSet.Named<T> holders = getter.get(tagKey).orElse(null);
if (holders == null) return EntryIngredient.empty();
int size = holders.size();
if (size == 0) return EntryIngredient.empty();
if (size == 1) return EntryIngredient.of(new ItemStack(holders.get(0).value()));
List<ItemStack> stackList = new ArrayList<>();
for (Holder<T> t : holders) {
ItemStack stack = new ItemStack(t.value());
if (!stack.isEmpty()) {
stackList.add(stack);
}
}
return EntryIngredient.of(stackList.toArray(new ItemStack[0]));
}
}

View File

@@ -0,0 +1,98 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.item.crafting.display.ShapedCraftingRecipeDisplay;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
/**
* see me.shedaniel.rei.plugin.common.displays.crafting.DefaultShapedDisplay#DefaultShapedDisplay(RecipeHolder)
*/
public class ShapedDisplay extends CraftingDisplay {
private final int width;
private final int height;
private static final StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CraftingDisplay::getOptionalLocation,
ByteBufCodecs.INT,
CraftingDisplay::getWidth,
ByteBufCodecs.INT,
CraftingDisplay::getHeight,
ShapedDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/crafting/shaped");
public ShapedDisplay(@NotNull RecipeHolder<ShapedRecipe> recipeHolder) {
super(
ofIngredient(recipeHolder.value()),
List.of(EntryIngredient.of(recipeHolder.value().assemble(CraftingInput.EMPTY, CraftRegistry.getMinecraftRegistry()))),
recipeHolder.id().location()
);
this.width = recipeHolder.value().getWidth();
this.height = recipeHolder.value().getHeight();
}
public ShapedDisplay(@NotNull ShapedCraftingRecipeDisplay recipeDisplay, ResourceLocation id) {
super(
Display.ofSlotDisplays(recipeDisplay.ingredients()),
List.of(ofSlotDisplay(recipeDisplay.result())),
id
);
this.width = recipeDisplay.width();
this.height = recipeDisplay.height();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
public StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> streamCodec() {
return CODEC;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CraftingDisplay of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<ResourceLocation> location, int width, int height) {
throw new UnsupportedOperationException();
}
private static List<EntryIngredient> ofIngredient(ShapedRecipe recipe) {
return recipe.getIngredients().stream().map(ingredient -> {
if (ingredient.isEmpty()) {
return EntryIngredient.empty();
}
ItemStack[] itemStacks = ingredient.get().items()
.map(itemHolder -> new ItemStack(itemHolder, 1))
.toArray(ItemStack[]::new);
return EntryIngredient.of(itemStacks);
}).toList();
}
}

View File

@@ -0,0 +1,77 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.ShapelessRecipe;
import net.minecraft.world.item.crafting.display.ShapelessCraftingRecipeDisplay;
import org.apache.commons.lang.NotImplementedException;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
public class ShapelessDisplay extends CraftingDisplay {
private static final StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
CraftingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
CraftingDisplay::getOptionalLocation,
ShapelessDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/crafting/shapeless");
public ShapelessDisplay(@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull ResourceLocation location) {
super(inputs, outputs, location);
}
public ShapelessDisplay(@NotNull RecipeHolder<ShapelessRecipe> recipeHolder) {
this(
recipeHolder.value().placementInfo().ingredients().stream().map(EntryIngredient::ofIngredient).toList(),
List.of(EntryIngredient.of(recipeHolder.value().assemble(CraftingInput.EMPTY, CraftRegistry.getMinecraftRegistry()))),
recipeHolder.id().location()
);
}
public ShapelessDisplay(@NotNull ShapelessCraftingRecipeDisplay recipeDisplay, ResourceLocation id) {
this(
ofSlotDisplays(recipeDisplay.ingredients()),
List.of(ofSlotDisplay(recipeDisplay.result())),
id
);
}
@Override
public int getWidth() {
return getInputEntries().size() > 4 ? 3 : 2;
}
@Override
public int getHeight() {
return getInputEntries().size() > 4 ? 3 : 2;
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
public StreamCodec<RegistryFriendlyByteBuf, CraftingDisplay> streamCodec() {
return CODEC;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static CraftingDisplay of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<ResourceLocation> location) {
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SmeltingRecipe;
public class SmeltingDisplay extends CookingDisplay {
public SmeltingDisplay(RecipeHolder<SmeltingRecipe> recipe) {
super(recipe);
}
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smelting");
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}

View File

@@ -0,0 +1,72 @@
package org.leavesmc.leaves.protocol.rei.display;
import com.mojang.serialization.Codec;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ByIdMap;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
import java.util.function.IntFunction;
public class SmithingDisplay extends Display {
private static final StreamCodec<RegistryFriendlyByteBuf, SmithingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
SmithingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
SmithingDisplay::getOutputEntries,
ByteBufCodecs.optional(SmithingRecipeType.STREAM_CODEC),
SmithingDisplay::getOptionalType,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
SmithingDisplay::getOptionalLocation,
SmithingDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smithing");
private final SmithingRecipeType type;
public Optional<SmithingRecipeType> getOptionalType() {
return Optional.of(type);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, ? extends Display> streamCodec() {
return CODEC;
}
public SmithingDisplay(
@NotNull List<EntryIngredient> inputs,
@NotNull List<EntryIngredient> outputs,
@NotNull SmithingRecipeType type,
@NotNull ResourceLocation location
) {
super(inputs, outputs, location);
this.type = type;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static SmithingDisplay of(List<EntryIngredient> inputs, List<EntryIngredient> outputs, Optional<SmithingRecipeType> type, Optional<ResourceLocation> location) {
throw new UnsupportedOperationException();
}
public enum SmithingRecipeType {
TRIM,
TRANSFORM,
;
public static final Codec<SmithingRecipeType> CODEC = Codec.STRING.xmap(SmithingRecipeType::valueOf, SmithingRecipeType::name);
public static final IntFunction<SmithingRecipeType> BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
public static final StreamCodec<ByteBuf, SmithingRecipeType> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Enum::ordinal);
}
}

View File

@@ -0,0 +1,18 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeHolder;
public class SmokingDisplay extends CookingDisplay {
public SmokingDisplay(RecipeHolder<? extends AbstractCookingRecipe> recipe) {
super(recipe);
}
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/smoking");
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
}

View File

@@ -0,0 +1,60 @@
package org.leavesmc.leaves.protocol.rei.display;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.SingleRecipeInput;
import net.minecraft.world.item.crafting.StonecutterRecipe;
import org.bukkit.craftbukkit.CraftRegistry;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.rei.ingredient.EntryIngredient;
import java.util.List;
import java.util.Optional;
/**
* see me.shedaniel.rei.plugin.common.displays.DefaultStoneCuttingDisplay
*/
public class StoneCuttingDisplay extends Display {
private static final StreamCodec<RegistryFriendlyByteBuf, StoneCuttingDisplay> CODEC = StreamCodec.composite(
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
StoneCuttingDisplay::getInputEntries,
EntryIngredient.CODEC.apply(ByteBufCodecs.list()),
StoneCuttingDisplay::getOutputEntries,
ByteBufCodecs.optional(ResourceLocation.STREAM_CODEC),
StoneCuttingDisplay::getOptionalLocation,
StoneCuttingDisplay::of
);
private static final ResourceLocation SERIALIZER_ID = ResourceLocation.tryBuild("minecraft", "default/stone_cutting");
public StoneCuttingDisplay(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull ResourceLocation id) {
super(inputs, outputs, id);
}
public StoneCuttingDisplay(RecipeHolder<StonecutterRecipe> recipeHolder) {
this(
List.of(EntryIngredient.ofIngredient(recipeHolder.value().input())),
List.of(EntryIngredient.of(recipeHolder.value().assemble(new SingleRecipeInput(ItemStack.EMPTY), CraftRegistry.getMinecraftRegistry()))),
recipeHolder.id().location()
);
}
@Override
public ResourceLocation getSerializerId() {
return SERIALIZER_ID;
}
@Override
public StreamCodec<RegistryFriendlyByteBuf, StoneCuttingDisplay> streamCodec() {
return CODEC;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static StoneCuttingDisplay of(@NotNull List<EntryIngredient> inputs, @NotNull List<EntryIngredient> outputs, @NotNull Optional<ResourceLocation> id) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,101 @@
package org.leavesmc.leaves.protocol.rei.ingredient;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.ItemLike;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
public class EntryIngredient {
private static final StreamCodec<RegistryFriendlyByteBuf, Holder<Item>> ITEM_STREAM_CODEC = ByteBufCodecs.holderRegistry(Registries.ITEM);
public static final StreamCodec<RegistryFriendlyByteBuf, EntryIngredient> CODEC = new StreamCodec<>() {
@NotNull
@Override
public EntryIngredient decode(@NotNull RegistryFriendlyByteBuf buffer) {
throw new UnsupportedOperationException();
}
@Override
public void encode(@NotNull RegistryFriendlyByteBuf buffer, @NotNull EntryIngredient value) {
ByteBufCodecs.writeCount(buffer, value.size(), Integer.MAX_VALUE);
value.stream().forEach(itemStack -> {
buffer.writeResourceLocation(ITEM_ID);
if (itemStack.isEmpty()) {
buffer.writeVarInt(0);
} else {
buffer.writeVarInt(itemStack.getCount());
ITEM_STREAM_CODEC.encode(buffer, itemStack.getItemHolder());
DataComponentPatch.STREAM_CODEC.encode(buffer, itemStack.components.asPatch());
}
});
}
};
private static final ResourceLocation ITEM_ID = ResourceLocation.withDefaultNamespace("item");
@NotNull
private final ItemStack[] array;
private static final EntryIngredient EMPTY = new EntryIngredient(new ItemStack[0]);
private EntryIngredient(@NotNull ItemStack[] array) {
this.array = Objects.requireNonNull(array);
}
public Stream<ItemStack> stream() {
return Arrays.stream(array);
}
public static EntryIngredient empty() {
return EMPTY;
}
public boolean isEmpty() {
return array.length == 0;
}
public ItemStack get(int index) {
return array[index].copy();
}
public int size() {
return array.length;
}
public static EntryIngredient ofItemHolder(@NotNull Holder<? extends ItemLike> item) {
return EntryIngredient.of(item.value());
}
public static EntryIngredient of(@NotNull ItemLike item) {
return EntryIngredient.of(new ItemStack(item));
}
public static EntryIngredient of(@NotNull ItemStack itemStack) {
return new EntryIngredient(new ItemStack[]{itemStack});
}
public static EntryIngredient of(@NotNull ItemStack... itemStacks) {
return new EntryIngredient(Arrays.copyOf(itemStacks, itemStacks.length));
}
public static EntryIngredient ofIngredient(Ingredient ingredient) {
if (ingredient.isEmpty()) {
return EntryIngredient.empty();
}
ItemStack[] itemStacks = ingredient.items()
.map(itemHolder -> new ItemStack(itemHolder, 1))
.toArray(ItemStack[]::new);
return EntryIngredient.of(itemStacks);
}
}

View File

@@ -0,0 +1,32 @@
package org.leavesmc.leaves.protocol.rei.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
public record BufCustomPacketPayload(
Type<BufCustomPacketPayload> type,
byte[] payload
) implements LeavesCustomPayload<BufCustomPacketPayload> {
@New
public static BufCustomPacketPayload create(ResourceLocation location, @NotNull FriendlyByteBuf buf) {
return new BufCustomPacketPayload(new Type<>(location), buf.readByteArray());
}
@Override
public void write(FriendlyByteBuf buf) {
FriendlyByteBuf.writeByteArray(buf, this.payload);
}
@Override
public ResourceLocation id() {
return type.id();
}
@NotNull
@Override
public Type<BufCustomPacketPayload> type() {
return type;
}
}

View File

@@ -1,28 +0,0 @@
package org.leavesmc.leaves.protocol.rei.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.rei.REIServerProtocol;
public record CreateItemGrabPayload(ItemStack item) implements LeavesCustomPayload<CreateItemGrabPayload> {
private static final ResourceLocation ID = REIServerProtocol.id("create_item_grab");
@New
public CreateItemGrabPayload(ResourceLocation location, @NotNull FriendlyByteBuf buf) {
this(buf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC));
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, item);
}
@Override
public ResourceLocation id() {
return ID;
}
}

View File

@@ -1,28 +0,0 @@
package org.leavesmc.leaves.protocol.rei.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.rei.REIServerProtocol;
public record CreateItemHotbarPayload(ItemStack item, int hotbarSlot) implements LeavesCustomPayload<CreateItemHotbarPayload> {
private static final ResourceLocation ID = REIServerProtocol.id("create_item_hotbar");
@New
public CreateItemHotbarPayload(ResourceLocation location, FriendlyByteBuf buf) {
this(buf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC), buf.readVarInt());
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, item);
buf.writeVarInt(hotbarSlot);
}
@Override
public ResourceLocation id() {
return ID;
}
}

View File

@@ -1,29 +0,0 @@
package org.leavesmc.leaves.protocol.rei.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.rei.REIServerProtocol;
public record CreateItemMessagePayload(ItemStack item, String playerName) implements LeavesCustomPayload<CreateItemMessagePayload> {
private static final ResourceLocation ID = REIServerProtocol.id("ci_msg");
@New
public CreateItemMessagePayload(ResourceLocation location, @NotNull FriendlyByteBuf buf) {
this(buf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC), buf.readUtf());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, item);
buf.writeUtf(playerName);
}
@Override
public ResourceLocation id() {
return ID;
}
}

View File

@@ -1,28 +0,0 @@
package org.leavesmc.leaves.protocol.rei.payload;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.rei.REIServerProtocol;
public record CreateItemPayload(ItemStack item) implements LeavesCustomPayload<CreateItemPayload> {
private static final ResourceLocation ID = REIServerProtocol.id("create_item");
@New
public CreateItemPayload(ResourceLocation location, @NotNull FriendlyByteBuf buf) {
this(buf.readJsonWithCodec(ItemStack.OPTIONAL_CODEC));
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeJsonWithCodec(ItemStack.OPTIONAL_CODEC, item);
}
@Override
public ResourceLocation id() {
return ID;
}
}

View File

@@ -0,0 +1,78 @@
package org.leavesmc.leaves.protocol.rei.payload;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.util.ByIdMap;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.LeavesLogger;
import org.leavesmc.leaves.protocol.rei.REIServerProtocol;
import org.leavesmc.leaves.protocol.rei.display.Display;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
public record DisplaySyncPayload(
SyncType syncType,
Collection<Display> displays,
long version
) implements CustomPacketPayload {
public static final CustomPacketPayload.Type<DisplaySyncPayload> TYPE = new CustomPacketPayload.Type<>(REIServerProtocol.SYNC_DISPLAYS_PACKET);
public static final StreamCodec<? super RegistryFriendlyByteBuf, DisplaySyncPayload> STREAM_CODEC = StreamCodec.composite(
SyncType.STREAM_CODEC,
DisplaySyncPayload::syncType,
Display.dispatchCodec().apply(codec -> new StreamCodec<RegistryFriendlyByteBuf, Display>() {
@Override
public void encode(@NotNull RegistryFriendlyByteBuf buf, @NotNull Display display) {
RegistryFriendlyByteBuf tmpBuf = new RegistryFriendlyByteBuf(Unpooled.buffer(), buf.registryAccess());
try {
codec.encode(tmpBuf, display);
} catch (Exception e) {
tmpBuf.release();
buf.writeBoolean(false);
LeavesLogger.LOGGER.warning("Failed to encode display: " + display, e);
return;
}
buf.writeBoolean(true);
RegistryFriendlyByteBuf.writeByteArray(buf, ByteBufUtil.getBytes(tmpBuf));
tmpBuf.release();
}
@NotNull
@Override
public Display decode(@NotNull RegistryFriendlyByteBuf buf) {
// The DisplayDecoder will not be called on the server side
throw new UnsupportedOperationException();
}
}
).apply(ByteBufCodecs.<RegistryFriendlyByteBuf, Display, Collection<Display>>collection(ArrayList::new)).map(
collection -> collection.stream().filter(Objects::nonNull).toList(),
UnaryOperator.identity()
),
DisplaySyncPayload::displays,
ByteBufCodecs.LONG,
DisplaySyncPayload::version,
DisplaySyncPayload::new
);
@Override
@NotNull
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public enum SyncType {
APPEND,
SET;
public static final IntFunction<SyncType> BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
public static final StreamCodec<ByteBuf, SyncType> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Enum::ordinal);
}
}

View File

@@ -1,30 +0,0 @@
package org.leavesmc.leaves.protocol.rei.payload;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
import org.leavesmc.leaves.protocol.rei.REIServerProtocol;
public record MoveItemPayload(ResourceLocation category, boolean isShift, CompoundTag nbt) implements LeavesCustomPayload<MoveItemPayload> {
private static final ResourceLocation ID = REIServerProtocol.id("move_items_new");
@New
public MoveItemPayload(ResourceLocation location, @NotNull FriendlyByteBuf buf) {
this(buf.readResourceLocation(), buf.readBoolean(), buf.readNbt());
}
@Override
public void write(@NotNull FriendlyByteBuf buf) {
buf.writeResourceLocation(category);
buf.writeBoolean(isShift);
buf.writeNbt(nbt);
}
@Override
public ResourceLocation id() {
return ID;
}
}