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

feat: finish MOVE_ITEM_NEW_PACKET, fix rei protocol packet transformer (#615)

This commit is contained in:
MC_XiaoHei
2025-07-26 10:15:40 +08:00
committed by GitHub
parent c245ee6734
commit eb3d87b8aa
9 changed files with 1041 additions and 2 deletions

View File

@@ -37,6 +37,7 @@ public class PacketTransformer {
public void inbound(ResourceLocation id, RegistryFriendlyByteBuf buf, ServerPlayer player, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) { public void inbound(ResourceLocation id, RegistryFriendlyByteBuf buf, ServerPlayer player, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
UUID key = player.getUUID(); UUID key = player.getUUID();
PartData data; PartData data;
buf.readVarInt();
switch (buf.readByte()) { switch (buf.readByte()) {
case START -> { case START -> {
int partsNum = buf.readInt(); int partsNum = buf.readInt();

View File

@@ -5,6 +5,11 @@ import com.google.common.collect.ImmutableMap;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
@@ -46,8 +51,15 @@ import org.leavesmc.leaves.protocol.rei.display.SmeltingDisplay;
import org.leavesmc.leaves.protocol.rei.display.SmokingDisplay; import org.leavesmc.leaves.protocol.rei.display.SmokingDisplay;
import org.leavesmc.leaves.protocol.rei.display.StoneCuttingDisplay; import org.leavesmc.leaves.protocol.rei.display.StoneCuttingDisplay;
import org.leavesmc.leaves.protocol.rei.payload.DisplaySyncPayload; import org.leavesmc.leaves.protocol.rei.payload.DisplaySyncPayload;
import org.leavesmc.leaves.protocol.rei.transfer.InputSlotCrafter;
import org.leavesmc.leaves.protocol.rei.transfer.NewInputSlotCrafter;
import org.leavesmc.leaves.protocol.rei.transfer.slot.PlayerInventorySlotAccessor;
import org.leavesmc.leaves.protocol.rei.transfer.slot.SlotAccessor;
import org.leavesmc.leaves.protocol.rei.transfer.slot.VanillaSlotAccessor;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
@@ -63,9 +75,11 @@ public class REIServerProtocol implements LeavesProtocol {
public static final String CHEAT_PERMISSION = "leaves.protocol.rei.cheat"; 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 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_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_HOTBAR_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item_hotbar");
public static final ResourceLocation CREATE_ITEMS_GRAB_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "create_item_grab");
public static final ResourceLocation CREATE_ITEMS_MESSAGE_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "ci_msg"); public static final ResourceLocation CREATE_ITEMS_MESSAGE_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "ci_msg");
public static final ResourceLocation MOVE_ITEMS_NEW_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "move_items_new");
public static final ResourceLocation NOT_ENOUGH_ITEMS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "og_not_enough"); // this pack is under to-do at rei-client, so we don't handle it
public static final ResourceLocation SYNC_DISPLAYS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "sync_displays"); public static final ResourceLocation SYNC_DISPLAYS_PACKET = ResourceLocation.fromNamespaceAndPath("roughlyenoughitems", "sync_displays");
public static final Map<ResourceLocation, PacketTransformer> TRANSFORMERS = Util.make(() -> { public static final Map<ResourceLocation, PacketTransformer> TRANSFORMERS = Util.make(() -> {
@@ -75,6 +89,7 @@ public class REIServerProtocol implements LeavesProtocol {
builder.put(CREATE_ITEMS_PACKET, new PacketTransformer()); builder.put(CREATE_ITEMS_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_GRAB_PACKET, new PacketTransformer()); builder.put(CREATE_ITEMS_GRAB_PACKET, new PacketTransformer());
builder.put(CREATE_ITEMS_HOTBAR_PACKET, new PacketTransformer()); builder.put(CREATE_ITEMS_HOTBAR_PACKET, new PacketTransformer());
builder.put(MOVE_ITEMS_NEW_PACKET, new PacketTransformer());
return builder.build(); return builder.build();
}); });
private static final Set<ServerPlayer> enabledPlayers = new HashSet<>(); private static final Set<ServerPlayer> enabledPlayers = new HashSet<>();
@@ -294,7 +309,48 @@ public class REIServerProtocol implements LeavesProtocol {
@ProtocolHandler.BytebufReceiver(key = "move_items_new") @ProtocolHandler.BytebufReceiver(key = "move_items_new")
public static void handleMoveItem(ServerPlayer player, RegistryFriendlyByteBuf buf) { public static void handleMoveItem(ServerPlayer player, RegistryFriendlyByteBuf buf) {
// TODO handle to disable REI client warning BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer = (ignored, c2sWholeBuf) -> {
FriendlyByteBuf tmpBuf = new FriendlyByteBuf(Unpooled.buffer()).writeBytes(c2sWholeBuf.readByteArray());
AbstractContainerMenu container = player.containerMenu;
tmpBuf.readResourceLocation();
try {
boolean shift = tmpBuf.readBoolean();
try {
CompoundTag nbt = tmpBuf.readNbt();
if (nbt == null) {
throw new IllegalStateException("NBT data is null");
}
int version = nbt.getInt("Version").orElse(-1);
if (version != 1) {
throw new IllegalStateException("Server and client REI protocol version mismatch! Server: 1, Client: " + version);
}
List<List<ItemStack>> recipes = readInputs(player.registryAccess(), nbt.getListOrEmpty("Inputs"));
List<SlotAccessor> input = readSlots(container, player, nbt.getListOrEmpty("InputSlots"));
List<SlotAccessor> inventory = readSlots(container, player, nbt.getListOrEmpty("InventorySlots"));
NewInputSlotCrafter<AbstractContainerMenu> crafter = new NewInputSlotCrafter<>(container, input, inventory, recipes);
Bukkit.getScheduler().runTask(MinecraftInternalPlugin.INSTANCE, () -> {
try {
crafter.fillInputSlots(player, shift);
} catch (InputSlotCrafter.NotEnoughMaterialsException ignored1) {
} catch (IllegalStateException e) {
player.sendSystemMessage(Component.translatable(e.getMessage()).withStyle(ChatFormatting.RED));
} catch (Exception e) {
player.sendSystemMessage(Component.translatable("error.rei.internal.error", e.getMessage()).withStyle(ChatFormatting.RED));
e.printStackTrace();
}
});
} catch (IllegalStateException e) {
player.sendSystemMessage(Component.translatable(e.getMessage()).withStyle(ChatFormatting.RED));
} catch (Exception e) {
player.sendSystemMessage(Component.translatable("error.rei.internal.error", e.getMessage()).withStyle(ChatFormatting.RED));
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
};
inboundTransform(player, MOVE_ITEMS_NEW_PACKET, buf, consumer);
} }
private static void inboundTransform(ServerPlayer player, ResourceLocation id, RegistryFriendlyByteBuf buf, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) { private static void inboundTransform(ServerPlayer player, ResourceLocation id, RegistryFriendlyByteBuf buf, BiConsumer<ResourceLocation, RegistryFriendlyByteBuf> consumer) {
@@ -332,4 +388,44 @@ public class REIServerProtocol implements LeavesProtocol {
public int tickerInterval(String tickerID) { public int tickerInterval(String tickerID) {
return 200; return 200;
} }
private static List<List<ItemStack>> readInputs(RegistryAccess registryAccess, ListTag tag) {
List<List<ItemStack>> items = new ArrayList<>();
for (Tag t : tag) {
CompoundTag compoundTag = (CompoundTag) t;
compoundTag.getInt("Index").orElseThrow();
ListTag ingredientList = compoundTag.getListOrEmpty("Ingredient");
List<ItemStack> slotItems = new ArrayList<>();
for (Tag ingredient : ingredientList) {
CompoundTag ingredientTag = (CompoundTag) ingredient;
ItemStack stack = ItemStack.OPTIONAL_CODEC.parse(
registryAccess.createSerializationContext(NbtOps.INSTANCE),
ingredientTag.get("value")
).getOrThrow();
slotItems.add(stack);
}
items.add(slotItems);
}
return items;
}
private static List<SlotAccessor> readSlots(AbstractContainerMenu menu, ServerPlayer player, ListTag tag) {
List<SlotAccessor> slots = new ArrayList<>();
for (Tag t : tag) {
CompoundTag compoundTag = (CompoundTag) t;
String id = compoundTag.getString("id").orElseThrow();
if (!id.startsWith(PROTOCOL_ID + ":")) {
throw new IllegalStateException("Invalid slot id: " + id + ", expected to start with '" + PROTOCOL_ID + ":'");
}
id = id.substring((PROTOCOL_ID + ":").length());
int slot = compoundTag.getInt("Slot").orElseThrow();
SlotAccessor accessor = switch (id) {
case "vanilla" -> new VanillaSlotAccessor(menu.slots.get(slot));
case "player" -> new PlayerInventorySlotAccessor(player, slot);
default -> throw new IllegalStateException("Unknown container id: " + id);
};
slots.add(accessor);
}
return slots;
}
} }

View File

@@ -0,0 +1,176 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.leavesmc.leaves.protocol.rei.transfer.slot.SlotAccessor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public abstract class InputSlotCrafter<T extends AbstractContainerMenu> {
protected T container;
private Iterable<SlotAccessor> inputStacks;
private Iterable<SlotAccessor> inventoryStacks;
protected ServerPlayer player;
protected InputSlotCrafter(T container) {
this.container = container;
}
public void fillInputSlots(ServerPlayer player, boolean hasShift) {
this.player = player;
this.inventoryStacks = this.getInventorySlots();
this.inputStacks = this.getInputSlots();
// Return the already placed items on the grid
this.cleanInputs();
ItemRecipeFinder recipeFinder = new ItemRecipeFinder();
this.populateRecipeFinder(recipeFinder);
List<List<ItemStack>> ingredients = new ArrayList<>(this.getInputs());
if (recipeFinder.findRecipe(ingredients, 1, null)) {
this.fillInputSlots(recipeFinder, ingredients, hasShift);
} else {
this.cleanInputs();
this.markDirty();
throw new NotEnoughMaterialsException();
}
this.markDirty();
}
protected abstract Iterable<SlotAccessor> getInputSlots();
protected abstract Iterable<SlotAccessor> getInventorySlots();
protected abstract List<List<ItemStack>> getInputs();
protected abstract void populateRecipeFinder(ItemRecipeFinder recipeFinder);
protected abstract void markDirty();
public void alignRecipeToGrid(Iterable<SlotAccessor> inputStacks, Iterator<ItemStack> recipeItems, int craftsAmount) {
for (SlotAccessor inputStack : inputStacks) {
if (!recipeItems.hasNext()) {
return;
}
this.acceptAlignedInput(recipeItems.next(), inputStack, craftsAmount);
}
}
public void acceptAlignedInput(ItemStack toBeTakenStack, SlotAccessor inputStack, int craftsAmount) {
if (!toBeTakenStack.isEmpty()) {
for (int i = 0; i < craftsAmount; ++i) {
this.fillInputSlot(inputStack, toBeTakenStack);
}
}
}
protected void fillInputSlot(SlotAccessor slot, ItemStack toBeTakenStack) {
SlotAccessor takenSlot = this.takeInventoryStack(toBeTakenStack);
if (takenSlot != null) {
ItemStack takenStack = takenSlot.getItemStack().copy();
if (!takenStack.isEmpty()) {
if (takenStack.getCount() > 1) {
takenSlot.takeStack(1);
} else {
takenSlot.setItemStack(ItemStack.EMPTY);
}
takenStack.setCount(1);
if (!slot.canPlace(takenStack)) {
return;
}
if (slot.getItemStack().isEmpty()) {
slot.setItemStack(takenStack);
} else {
slot.getItemStack().grow(1);
}
}
}
}
protected void fillInputSlots(ItemRecipeFinder recipeFinder, List<List<ItemStack>> ingredients, boolean hasShift) {
int recipeCrafts = recipeFinder.countRecipeCrafts(ingredients, Integer.MAX_VALUE, null);
int amountToFill = hasShift ? recipeCrafts : 1;
List<ItemStack> recipeItems = new ArrayList<>();
if (recipeFinder.findRecipe(ingredients, amountToFill, recipeItems::add)) {
int finalCraftsAmount = amountToFill;
for (ItemStack itemId : recipeItems) {
// Fix issue with empty item id (grid slot) [shift-click issue]
if (itemId.isEmpty()) {
continue;
}
finalCraftsAmount = Math.min(finalCraftsAmount, itemId.getMaxStackSize());
}
recipeItems.clear();
if (recipeFinder.findRecipe(ingredients, finalCraftsAmount, recipeItems::add)) {
this.cleanInputs();
this.alignRecipeToGrid(inputStacks, recipeItems.iterator(), finalCraftsAmount);
}
}
}
protected abstract void cleanInputs();
@Nullable
public SlotAccessor takeInventoryStack(ItemStack itemStack) {
boolean rejectedModification = false;
for (SlotAccessor inventoryStack : inventoryStacks) {
ItemStack itemStack1 = inventoryStack.getItemStack();
if (!itemStack1.isEmpty() && areItemsEqual(itemStack, itemStack1) && !itemStack1.isDamaged() && !itemStack1.isEnchanted() && !itemStack1.has(DataComponents.CUSTOM_NAME)) {
if (!inventoryStack.allowModification(player)) {
rejectedModification = true;
} else {
return inventoryStack;
}
}
}
if (rejectedModification) {
throw new IllegalStateException("Unable to take item from inventory due to slot not allowing modification! Item requested: " + itemStack);
}
return null;
}
private static boolean areItemsEqual(ItemStack stack1, ItemStack stack2) {
return ItemStack.isSameItemSameComponents(stack1, stack2);
}
public static class NotEnoughMaterialsException extends RuntimeException {
}
}

View File

@@ -0,0 +1,126 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class ItemRecipeFinder {
private final Interner<ItemKey> keys = Interners.newWeakInterner();
private final RecipeFinder<ItemKey, Ingredient> finder = new RecipeFinder<>();
public boolean contains(ItemStack item) {
return finder.contains(ofKey(item));
}
public void take(ItemStack item, int amount) {
finder.take(ofKey(item), amount);
}
public void put(ItemStack item, int amount) {
finder.put(ofKey(item), amount);
}
public void addNormalItem(ItemStack itemStack) {
if (Inventory.isUsableForCrafting(itemStack)) {
this.addItem(itemStack);
}
}
public void addItem(ItemStack itemStack) {
this.addItem(itemStack, itemStack.getMaxStackSize());
}
public void addItem(ItemStack itemStack, int i) {
if (!itemStack.isEmpty()) {
int j = Math.min(i, itemStack.getCount());
this.finder.put(ofKey(itemStack), j);
}
}
public boolean findRecipe(List<List<ItemStack>> list, int maxCrafts, @Nullable Consumer<ItemStack> output) {
return finder.findRecipe(toIngredients(list), maxCrafts, flatten(itemStack -> {
if (output != null) {
output.accept(itemStack);
}
}));
}
public int countRecipeCrafts(List<List<ItemStack>> list, int maxCrafts, @Nullable Consumer<ItemStack> output) {
return finder.countRecipeCrafts(toIngredients(list), maxCrafts, flatten(itemStack -> {
if (output != null) {
output.accept(itemStack);
}
}));
}
private ItemKey ofKey(ItemStack itemStack) {
return keys.intern(new ItemKey(itemStack.getItemHolder(), itemStack.getComponentsPatch()));
}
private Ingredient ofKeys(int index, List<ItemStack> itemStack) {
return new Ingredient(index, itemStack.stream().map(this::ofKey).toList());
}
private List<Ingredient> toIngredients(List<List<ItemStack>> list) {
List<Ingredient> ingredients = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
List<ItemStack> stacks = list.get(i);
if (!stacks.isEmpty()) {
ingredients.add(ofKeys(i, stacks));
}
}
return ingredients;
}
private static BiConsumer<ItemKey, Ingredient> flatten(Consumer<ItemStack> consumer) {
int[] lastIndex = {-1};
return (itemKey, ingredient) -> {
for (int i = lastIndex[0] + 1; i < ingredient.index(); i++) {
consumer.accept(ItemStack.EMPTY);
}
consumer.accept(new ItemStack(itemKey.item(), 1, itemKey.patch()));
lastIndex[0] = ingredient.index();
};
}
private record Ingredient(int index, List<ItemKey> elements) implements RecipeFinder.Ingredient<ItemKey> {
}
private record ItemKey(Holder<Item> item, DataComponentPatch patch) {
}
}

View File

@@ -0,0 +1,67 @@
package org.leavesmc.leaves.protocol.rei.transfer;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import org.leavesmc.leaves.protocol.rei.transfer.slot.SlotAccessor;
import java.util.HashMap;
import java.util.List;
public class NewInputSlotCrafter<T extends AbstractContainerMenu> extends InputSlotCrafter<T> {
protected final List<SlotAccessor> inputSlots;
protected final List<SlotAccessor> inventorySlots;
protected final List<List<ItemStack>> inputs;
public NewInputSlotCrafter(T container, List<SlotAccessor> inputSlots, List<SlotAccessor> inventorySlots, List<List<ItemStack>> inputs) {
super(container);
this.inputSlots = inputSlots;
this.inventorySlots = inventorySlots;
this.inputs = inputs;
}
@Override
protected Iterable<SlotAccessor> getInputSlots() {
return this.inputSlots;
}
@Override
protected Iterable<SlotAccessor> getInventorySlots() {
return this.inventorySlots;
}
@Override
protected List<List<ItemStack>> getInputs() {
return this.inputs;
}
@Override
protected void populateRecipeFinder(ItemRecipeFinder recipeFinder) {
for (SlotAccessor slot : getInventorySlots()) {
recipeFinder.addNormalItem(slot.getItemStack());
}
}
@Override
protected void markDirty() {
player.getInventory().setChanged();
container.sendAllDataToRemote();
}
@Override
protected void cleanInputs() {
for (SlotAccessor slot : getInputSlots()) {
org.bukkit.inventory.ItemStack bukkitStack = slot.getItemStack().getBukkitStack();
if (bukkitStack.getType().isAir()) {
return;
}
HashMap<Integer, org.bukkit.inventory.ItemStack> notAdded = player.getBukkitEntity().getInventory().addItem(bukkitStack);
if (notAdded.isEmpty()) {
slot.setItemStack(ItemStack.EMPTY);
} else {
org.bukkit.inventory.ItemStack remain = notAdded.values().iterator().next();
slot.setItemStack(ItemStack.fromBukkitCopy(remain));
throw new IllegalStateException("rei.rei.no.slot.in.inv");
}
}
}
}

View File

@@ -0,0 +1,409 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import org.jetbrains.annotations.Nullable;
import java.util.BitSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
public class RecipeFinder<T, I extends RecipeFinder.Ingredient<T>> {
public final Reference2IntOpenHashMap<T> amounts = new Reference2IntOpenHashMap<>();
public boolean contains(T item) {
return this.amounts.getInt(item) > 0;
}
boolean containsAtLeast(T object, int i) {
return this.amounts.getInt(object) >= i;
}
public void take(T item, int amount) {
int taken = this.amounts.addTo(item, -amount);
if (taken < amount) {
throw new IllegalStateException("Took " + amount + " items, but only had " + taken);
}
}
public void put(T item, int amount) {
this.amounts.addTo(item, amount);
}
public boolean findRecipe(List<I> list, int maxCrafts, @Nullable BiConsumer<T, I> output) {
return new Filter(list).tryPick(maxCrafts, output);
}
public int countRecipeCrafts(List<I> list, int maxCrafts, @Nullable BiConsumer<T, I> output) {
return new Filter(list).tryPickAll(maxCrafts, output);
}
public void clear() {
this.amounts.clear();
}
class Filter {
private final List<I> ingredients;
private final int ingredientCount;
private final List<T> items;
private final int itemCount;
private final BitSet data;
private final IntList path = new IntArrayList();
public Filter(final List<I> list) {
this.ingredients = list;
this.ingredientCount = this.ingredients.size();
this.items = this.getUniqueAvailableIngredientItems();
this.itemCount = this.items.size();
this.data = new BitSet(this.visitedIngredientCount() + this.visitedItemCount() + this.satisfiedCount() + this.connectionCount() + this.residualCount());
this.setInitialConnections();
}
private void setInitialConnections() {
for (int i = 0; i < this.ingredientCount; i++) {
List<T> list = this.ingredients.get(i).elements();
for (int j = 0; j < this.itemCount; j++) {
if (list.contains(this.items.get(j))) {
this.setConnection(j, i);
}
}
}
}
public boolean tryPick(int maxCrafts, @Nullable BiConsumer<T, I> output) {
if (maxCrafts <= 0) {
return true;
} else {
int j = 0;
while (true) {
IntList intList = this.tryAssigningNewItem(maxCrafts);
if (intList == null) {
boolean bl = j == this.ingredientCount;
boolean bl2 = bl && output != null;
this.clearAllVisited();
this.clearSatisfied();
for (int l = 0; l < this.ingredientCount; l++) {
for (int m = 0; m < this.itemCount; m++) {
if (this.isAssigned(m, l)) {
this.unassign(m, l);
put(this.items.get(m), maxCrafts);
if (bl2) {
output.accept(this.items.get(m), this.ingredients.get(l));
}
break;
}
}
}
assert this.data.get(this.residualOffset(), this.residualOffset() + this.residualCount()).isEmpty();
return bl;
}
int k = intList.getInt(0);
take(this.items.get(k), maxCrafts);
int l = intList.size() - 1;
this.setSatisfied(intList.getInt(l));
j++;
for (int mx = 0; mx < intList.size() - 1; mx++) {
if (isPathIndexItem(mx)) {
int n = intList.getInt(mx);
int o = intList.getInt(mx + 1);
this.assign(n, o);
} else {
int n = intList.getInt(mx + 1);
int o = intList.getInt(mx);
this.unassign(n, o);
}
}
}
}
}
private static boolean isPathIndexItem(int i) {
return (i & 1) == 0;
}
private List<T> getUniqueAvailableIngredientItems() {
Set<T> set = new ReferenceOpenHashSet<>();
for (Ingredient<T> ingredient : this.ingredients) {
set.addAll(ingredient.elements());
}
set.removeIf(object -> !contains(object));
return List.copyOf(set);
}
@Nullable
private IntList tryAssigningNewItem(int i) {
this.clearAllVisited();
for (int j = 0; j < this.itemCount; j++) {
if (containsAtLeast(this.items.get(j), i)) {
IntList intList = this.findNewItemAssignmentPath(j);
if (intList != null) {
return intList;
}
}
}
return null;
}
@Nullable
private IntList findNewItemAssignmentPath(int i) {
this.path.clear();
this.visitItem(i);
this.path.add(i);
while (!this.path.isEmpty()) {
int j = this.path.size();
int k = this.path.getInt(j - 1);
if (isPathIndexItem(j - 1)) {
for (int l = 0; l < this.ingredientCount; l++) {
if (!this.hasVisitedIngredient(l) && this.hasConnection(k, l) && !this.isAssigned(k, l)) {
this.visitIngredient(l);
this.path.add(l);
break;
}
}
} else {
if (!this.isSatisfied(k)) {
return this.path;
}
for (int lx = 0; lx < this.itemCount; lx++) {
if (!this.hasVisitedItem(lx) && this.isAssigned(lx, k)) {
assert this.hasConnection(lx, k);
this.visitItem(lx);
this.path.add(lx);
break;
}
}
}
int l = this.path.size();
if (l == j) {
this.path.removeInt(l - 1);
}
}
return null;
}
private int visitedIngredientOffset() {
return 0;
}
private int visitedIngredientCount() {
return this.ingredientCount;
}
private int visitedItemOffset() {
return this.visitedIngredientOffset() + this.visitedIngredientCount();
}
private int visitedItemCount() {
return this.itemCount;
}
private int satisfiedOffset() {
return this.visitedItemOffset() + this.visitedItemCount();
}
private int satisfiedCount() {
return this.ingredientCount;
}
private int connectionOffset() {
return this.satisfiedOffset() + this.satisfiedCount();
}
private int connectionCount() {
return this.ingredientCount * this.itemCount;
}
private int residualOffset() {
return this.connectionOffset() + this.connectionCount();
}
private int residualCount() {
return this.ingredientCount * this.itemCount;
}
private boolean isSatisfied(int i) {
return this.data.get(this.getSatisfiedIndex(i));
}
private void setSatisfied(int i) {
this.data.set(this.getSatisfiedIndex(i));
}
private int getSatisfiedIndex(int i) {
assert i >= 0 && i < this.ingredientCount;
return this.satisfiedOffset() + i;
}
private void clearSatisfied() {
this.clearRange(this.satisfiedOffset(), this.satisfiedCount());
}
private void setConnection(int i, int j) {
this.data.set(this.getConnectionIndex(i, j));
}
private boolean hasConnection(int i, int j) {
return this.data.get(this.getConnectionIndex(i, j));
}
private int getConnectionIndex(int i, int j) {
assert i >= 0 && i < this.itemCount;
assert j >= 0 && j < this.ingredientCount;
return this.connectionOffset() + i * this.ingredientCount + j;
}
private boolean isAssigned(int i, int j) {
return this.data.get(this.getResidualIndex(i, j));
}
private void assign(int i, int j) {
int k = this.getResidualIndex(i, j);
assert !this.data.get(k);
this.data.set(k);
}
private void unassign(int i, int j) {
int k = this.getResidualIndex(i, j);
assert this.data.get(k);
this.data.clear(k);
}
private int getResidualIndex(int i, int j) {
assert i >= 0 && i < this.itemCount;
assert j >= 0 && j < this.ingredientCount;
return this.residualOffset() + i * this.ingredientCount + j;
}
private void visitIngredient(int i) {
this.data.set(this.getVisitedIngredientIndex(i));
}
private boolean hasVisitedIngredient(int i) {
return this.data.get(this.getVisitedIngredientIndex(i));
}
private int getVisitedIngredientIndex(int i) {
assert i >= 0 && i < this.ingredientCount;
return this.visitedIngredientOffset() + i;
}
private void visitItem(int i) {
this.data.set(this.getVisitiedItemIndex(i));
}
private boolean hasVisitedItem(int i) {
return this.data.get(this.getVisitiedItemIndex(i));
}
private int getVisitiedItemIndex(int i) {
assert i >= 0 && i < this.itemCount;
return this.visitedItemOffset() + i;
}
private void clearAllVisited() {
this.clearRange(this.visitedIngredientOffset(), this.visitedIngredientCount());
this.clearRange(this.visitedItemOffset(), this.visitedItemCount());
}
private void clearRange(int i, int j) {
this.data.clear(i, i + j);
}
public int tryPickAll(int i, @Nullable BiConsumer<T, I> output) {
int j = 0;
int k = Math.min(i, this.getMinIngredientCount()) + 1;
while (true) {
int l = (j + k) / 2;
if (this.tryPick(l, null)) {
if (k - j <= 1) {
if (l > 0) {
this.tryPick(l, output);
}
return l;
}
j = l;
} else {
k = l;
}
}
}
private int getMinIngredientCount() {
int i = Integer.MAX_VALUE;
for (Ingredient<T> ingredient : this.ingredients) {
int j = 0;
for (T object : ingredient.elements()) {
j = Math.max(j, amounts.getInt(object));
}
if (i > 0) {
i = Math.min(i, j);
}
}
return i;
}
}
public interface Ingredient<T> {
List<T> elements();
}
}

View File

@@ -0,0 +1,56 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
public class PlayerInventorySlotAccessor implements SlotAccessor {
protected Player player;
protected int index;
public PlayerInventorySlotAccessor(Player player, int index) {
this.player = player;
this.index = index;
}
@Override
public ItemStack getItemStack() {
return player.getInventory().getItem(index);
}
@Override
public void setItemStack(ItemStack stack) {
this.player.getInventory().setItem(index, stack);
}
@Override
public void takeStack(int amount) {
this.player.getInventory().removeItem(index, amount);
}
public int getIndex() {
return index;
}
}

View File

@@ -0,0 +1,43 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
public interface SlotAccessor {
ItemStack getItemStack();
void setItemStack(ItemStack stack);
void takeStack(int amount);
default boolean allowModification(Player player) {
return true;
}
default boolean canPlace(ItemStack stack) {
return true;
}
}

View File

@@ -0,0 +1,65 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.leavesmc.leaves.protocol.rei.transfer.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
public class VanillaSlotAccessor implements SlotAccessor {
protected Slot slot;
public VanillaSlotAccessor(Slot slot) {
this.slot = slot;
}
@Override
public ItemStack getItemStack() {
return slot.getItem();
}
@Override
public void setItemStack(ItemStack stack) {
this.slot.set(stack);
}
@Override
public void takeStack(int amount) {
slot.remove(amount);
}
public Slot getSlot() {
return slot;
}
@Override
public boolean allowModification(Player player) {
return slot.allowModification(player);
}
@Override
public boolean canPlace(ItemStack stack) {
return slot.mayPlace(stack);
}
}