diff --git a/src/main/java/codechicken/nei/BookmarkPanel.java b/src/main/java/codechicken/nei/BookmarkPanel.java index 61b75ebe..ffa73d37 100644 --- a/src/main/java/codechicken/nei/BookmarkPanel.java +++ b/src/main/java/codechicken/nei/BookmarkPanel.java @@ -1294,7 +1294,7 @@ public class BookmarkPanel extends PanelWidget { public BookmarkRecipeId getBookmarkRecipeId(int itemIndex) { BookmarkItem item = this.grid.getCalculatedItem(itemIndex); return item == null || item.type == BookmarkItemType.INGREDIENT || item.recipeId == null ? null - : new BookmarkRecipeId(item.recipeId.getHandleName(), item.recipeId.getIngredients()); + : new BookmarkRecipeId(item.recipeId.getHandlerName(), item.recipeId.getIngredients()); } @Deprecated diff --git a/src/main/java/codechicken/nei/FavoriteRecipes.java b/src/main/java/codechicken/nei/FavoriteRecipes.java index f41d82ea..f0271d3b 100644 --- a/src/main/java/codechicken/nei/FavoriteRecipes.java +++ b/src/main/java/codechicken/nei/FavoriteRecipes.java @@ -225,7 +225,7 @@ public class FavoriteRecipes { continue; } - if (FAVORITE_ITEM.equals(recipeId.getHandleName()) || RecipeHandlerRef.of(recipeId) != null) { + if (FAVORITE_ITEM.equals(recipeId.getHandlerName()) || RecipeHandlerRef.of(recipeId) != null) { manualFavorites.add(stack, recipeId); } else { hasErrors = true; diff --git a/src/main/java/codechicken/nei/NEICPH.java b/src/main/java/codechicken/nei/NEICPH.java index 285676c7..32afb872 100644 --- a/src/main/java/codechicken/nei/NEICPH.java +++ b/src/main/java/codechicken/nei/NEICPH.java @@ -6,6 +6,7 @@ import static codechicken.nei.PacketIDs.S2C; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.inventory.GuiInventory; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.play.INetHandlerPlayClient; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.world.World; @@ -14,6 +15,7 @@ import codechicken.core.ClientUtils; import codechicken.lib.inventory.InventoryUtils; import codechicken.lib.packet.PacketCustom; import codechicken.lib.packet.PacketCustom.IClientPacketHandler; +import codechicken.nei.commands.CommandBookmarkAdd; import cpw.mods.fml.relauncher.Side; public class NEICPH implements IClientPacketHandler { @@ -56,6 +58,18 @@ public class NEICPH implements IClientPacketHandler { case S2C.OPEN_POTION_GUI: ClientUtils.openSMPGui(packet.readUByte(), new GuiPotionCreator(mc.thePlayer.inventory)); break; + case S2C.CACHE_BOOKMARK_DATA: + handleCacheBookmarkData(packet); + break; + } + } + + private void handleCacheBookmarkData(PacketCustom packet) { + final String payloadId = packet.readString(); + final NBTTagCompound payloadTag = packet.readNBTTagCompound(); + + if (payloadTag != null) { + CommandBookmarkAdd.cachePayload(payloadId, payloadTag); } } @@ -186,9 +200,9 @@ public class NEICPH implements IClientPacketHandler { packet.sendToServer(); } - public static void sendChatLink(ItemStack stackover) { + public static void sendChatLink(NBTTagCompound payloadTag) { PacketCustom packet = new PacketCustom(channel, C2S.SEND_CHAT_ITEM_LINK); - packet.writeItemStack(stackover); + packet.writeNBTTagCompound(payloadTag); packet.sendToServer(); } diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index 743059e5..b1cd0ce0 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -839,6 +839,7 @@ public class NEIClientConfig { API.addKeyBind("bookmark.favorite_item", Keyboard.KEY_F); API.addKeyBind("bookmark.remove_recipe", Keyboard.KEY_A); API.addKeyBind("bookmark.pull_items", Keyboard.KEY_V); + API.addKeyBind("bookmark.chat_link", Keyboard.KEY_L); API.addKeyBind("bookmark.hide", Keyboard.KEY_B); API.addKeyBind("itemzoom.toggle", Keyboard.KEY_Z); @@ -860,7 +861,6 @@ public class NEIClientConfig { API.addKeyBind("copy.name", Keyboard.KEY_C); API.addKeyBind("copy.oredict", Keyboard.KEY_D); API.addKeyBind("copy.identifier", Keyboard.KEY_X); - API.addKeyBind("copy.chat_link_item", Keyboard.KEY_L); } public static OptionList getOptionList() { diff --git a/src/main/java/codechicken/nei/NEIClientUtils.java b/src/main/java/codechicken/nei/NEIClientUtils.java index 74a9b3de..8a3caed3 100644 --- a/src/main/java/codechicken/nei/NEIClientUtils.java +++ b/src/main/java/codechicken/nei/NEIClientUtils.java @@ -38,6 +38,7 @@ import net.minecraft.inventory.Container; import net.minecraft.inventory.Slot; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.IChatComponent; import net.minecraft.util.ResourceLocation; @@ -379,8 +380,8 @@ public class NEIClientUtils extends NEIServerUtils { else sendCommand(getStringSetting("command.heal"), mc().thePlayer.getCommandSenderName()); } - public static void sendChatItemLink(ItemStack stackover) { - if (hasSMPCounterPart()) NEICPH.sendChatLink(stackover); + public static void sendChatItemLink(NBTTagCompound payload) { + if (hasSMPCounterPart()) NEICPH.sendChatLink(payload); } public static void toggleMagnetMode() { diff --git a/src/main/java/codechicken/nei/NEISPH.java b/src/main/java/codechicken/nei/NEISPH.java index a5a7002d..257e22ac 100644 --- a/src/main/java/codechicken/nei/NEISPH.java +++ b/src/main/java/codechicken/nei/NEISPH.java @@ -11,6 +11,7 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.InventoryBasic; import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.play.INetHandlerPlayServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityMobSpawner; @@ -95,7 +96,8 @@ public class NEISPH implements IServerPacketHandler { } private void handleSendChatItemLink(EntityPlayerMP sender, PacketCustom packet) { - NEIServerUtils.sendChatItemLink(sender, packet.readItemStack()); + final NBTTagCompound payloadTag = packet.readNBTTagCompound(); + NEIServerUtils.sendChatItemLink(sender, payloadTag); } private void handleDummySlotSet(EntityPlayerMP sender, PacketCustom packet) { diff --git a/src/main/java/codechicken/nei/NEIServerUtils.java b/src/main/java/codechicken/nei/NEIServerUtils.java index a6fd65e8..3fa55c4e 100644 --- a/src/main/java/codechicken/nei/NEIServerUtils.java +++ b/src/main/java/codechicken/nei/NEIServerUtils.java @@ -10,6 +10,7 @@ import java.util.AbstractList; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.zip.ZipException; import net.minecraft.command.ICommandSender; @@ -28,6 +29,7 @@ import net.minecraft.nbt.NBTException; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; @@ -44,6 +46,7 @@ import codechicken.lib.inventory.InventoryRange; import codechicken.lib.inventory.InventoryUtils; import codechicken.lib.packet.PacketCustom; import codechicken.nei.PacketIDs.S2C; +import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.NBTHelper; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.registry.GameRegistry; @@ -86,16 +89,46 @@ public class NEIServerUtils { player.extinguish(); } - public static void sendChatItemLink(EntityPlayerMP sender, ItemStack stackover) { - ServerUtils.sendChatToAll( - new ChatComponentTranslation( - "nei.chat.item_link.text", - sender.getDisplayName(), - stackover.func_151000_E().setChatStyle( - stackover.func_151000_E().getChatStyle().setChatClickEvent( - new ClickEvent( - ClickEvent.Action.RUN_COMMAND, - "/nei_bookmark " + stackover.writeToNBT(new NBTTagCompound())))))); + public static void sendChatItemLink(EntityPlayerMP sender, NBTTagCompound payload) { + if (payload == null) { + sender.addChatMessage(new ChatComponentText("Invalid bookmark payload")); + return; + } + + final String linkType = payload.getString("linkType"); + final String payloadId = UUID.randomUUID().toString().substring(0, 8); + final PacketCustom packet = new PacketCustom(NEISPH.channel, S2C.CACHE_BOOKMARK_DATA).writeString(payloadId) + .writeNBTTagCompound((NBTTagCompound) payload.copy()); + + for (EntityPlayer player : ServerUtils.getPlayers()) { + if (player instanceof EntityPlayerMP playerMP) { + packet.sendToPlayer(playerMP); + } + } + + try { + final IChatComponent clickable; + + if (payload.hasKey("item")) { + clickable = StackInfo.loadFromNBT(payload.getCompoundTag("item")).func_151000_E(); + } else { + clickable = new ChatComponentText("[") + .appendSibling(new ChatComponentTranslation("nei.chat.item_link." + linkType + ".text")) + .appendText("]"); + } + + if (clickable == null) { + throw new Exception("Invalid ItemStack NBT"); + } + + clickable.getChatStyle() + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/nei_bookmark " + payloadId)); + ServerUtils.sendChatToAll( + new ChatComponentTranslation("nei.chat.item_link.text", sender.getDisplayName(), clickable)); + } catch (Exception e) { + sender.addChatMessage(new ChatComponentText(e.toString())); + } + } public static long getTime(World world) { diff --git a/src/main/java/codechicken/nei/PacketIDs.java b/src/main/java/codechicken/nei/PacketIDs.java index 5c9661d3..a25e395d 100644 --- a/src/main/java/codechicken/nei/PacketIDs.java +++ b/src/main/java/codechicken/nei/PacketIDs.java @@ -37,5 +37,6 @@ public final class PacketIDs { public static final int OPEN_ENCHANTMENT_GUI = 21; public static final int SET_CREATIVE_PLUS_MODE = 23; public static final int OPEN_POTION_GUI = 24; + public static final int CACHE_BOOKMARK_DATA = 25; } } diff --git a/src/main/java/codechicken/nei/api/ShortcutInputHandler.java b/src/main/java/codechicken/nei/api/ShortcutInputHandler.java index b2c84552..c3e65c28 100644 --- a/src/main/java/codechicken/nei/api/ShortcutInputHandler.java +++ b/src/main/java/codechicken/nei/api/ShortcutInputHandler.java @@ -30,6 +30,7 @@ import codechicken.nei.bookmark.BookmarkGrid; import codechicken.nei.bookmark.BookmarkGroup; import codechicken.nei.bookmark.BookmarkItem; import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType; +import codechicken.nei.bookmark.BookmarkPayload; import codechicken.nei.bookmark.BookmarksGridSlot; import codechicken.nei.recipe.AutoCraftingManager; import codechicken.nei.recipe.GuiCraftingRecipe; @@ -95,6 +96,11 @@ public abstract class ShortcutInputHandler { return ItemPanels.bookmarkPanel.pullBookmarkItems(groupId, NEIClientUtils.shiftKey()); } + if (KeyManager.isKeyDown("bookmark.chat_link") && NEIClientUtils.controlKey()) { + NEIClientUtils.sendChatItemLink(BookmarkPayload.of(groupId).toNBT()); + return true; + } + if (NEIClientConfig.autocraftingEnabled() && KeyManager.isKeyDown("gui.craft_items") && NEIClientUtils.shiftKey() && ItemPanels.bookmarkPanel.getGrid().isCraftingMode(groupId)) { @@ -134,8 +140,8 @@ public abstract class ShortcutInputHandler { return copyItemStackOreDictionary(stackover); } - if (KeyManager.isHashDown("copy.chat_link_item", NEIClientUtils.CTRL_HASH)) { - return sendItemStackChatLink(stackover); + if (KeyManager.isKeyDown("bookmark.chat_link") && NEIClientUtils.controlKey()) { + return sendRecipeInChatLink(stackover, NEIClientUtils.shiftKey()); } if (KeyManager.isHashDown("recipe.recipe")) { @@ -198,10 +204,37 @@ public abstract class ShortcutInputHandler { return true; } - private static boolean sendItemStackChatLink(ItemStack stackover) { - if (stackover == null) return false; + private static boolean sendRecipeInChatLink(ItemStack stackover, boolean saveIngredients) { + final Point mousePos = GuiDraw.getMousePosition(); + final BookmarksGridSlot slot = ItemPanels.bookmarkPanel.getSlotMouseOver(mousePos.x, mousePos.y); - NEIClientUtils.sendChatItemLink(stackover); // I wish clients could just send formatted messages + if (slot != null && saveIngredients && slot.getType() == BookmarkItemType.RESULT) { + NEIClientUtils.sendChatItemLink(BookmarkPayload.of(slot.getBookmarkItem()).toNBT()); + return true; + } + + final Recipe recipe = getFocusedRecipe(stackover, mousePos.x, mousePos.y, saveIngredients); + + if (recipe != null && saveIngredients) { + NEIClientUtils.sendChatItemLink(BookmarkPayload.of(recipe).toNBT()); + return true; + } + + final RecipeId recipeId; + + if (slot != null) { + recipeId = slot.getType() == BookmarkItemType.INGREDIENT ? null : slot.getRecipeId(); + } else { + recipeId = recipe != null ? recipe.getRecipeId() : null; + } + + final boolean existsRecipe = NEIClientConfig.getBooleanSetting("inventory.bookmarks.bookmarkItemsWithRecipe"); + + if (ItemPanels.itemPanel.containsWithSubpanels(mousePos.x, mousePos.y)) { + stackover = ItemQuantityField.prepareStackWithQuantity(stackover, 0); + } + + NEIClientUtils.sendChatItemLink(BookmarkPayload.of(stackover, existsRecipe ? recipeId : null).toNBT()); return true; } @@ -449,6 +482,10 @@ public abstract class ShortcutInputHandler { KeyManager.getKeyName("bookmark.remove_recipe", NEIClientUtils.SHIFT_HASH), NEIClientUtils.translate("bookmark.group.remove_recipe")); + hotkeys.put( + KeyManager.getKeyName("bookmark.chat_link", NEIClientUtils.CTRL_HASH), + NEIClientUtils.translate("bookmark.group.chat_link")); + if (BookmarkContainerInfo.getBookmarkContainerHandler(gui) != null) { hotkeys.put( KeyManager.getKeyName("bookmark.pull_items"), @@ -579,9 +616,10 @@ public abstract class ShortcutInputHandler { hotkeys.put( KeyManager.getKeyName("copy.identifier", NEIClientUtils.CTRL_HASH), NEIClientUtils.translate("itempanel.copy_id")); + hotkeys.put( - KeyManager.getKeyName("copy.chat_link_item", NEIClientUtils.CTRL_HASH), - NEIClientUtils.translate("itempanel.chat_link_item")); + KeyManager.getKeyName("bookmark.chat_link", NEIClientUtils.CTRL_HASH), + NEIClientUtils.translate("bookmark.item.chat_link")); if (!(gui instanceof GuiRecipe) && NEIClientConfig.canCheatItem(stack)) { hotkeys.put( @@ -639,6 +677,9 @@ public abstract class ShortcutInputHandler { } } + hotkeys.put( + KeyManager.getKeyName("bookmark.chat_link", NEIClientUtils.SHIFT_HASH + NEIClientUtils.CTRL_HASH), + NEIClientUtils.translate("bookmark.recipe.chat_link")); } hotkeysCache.update(mousex, mousey, stack, groupId, hotkeys); diff --git a/src/main/java/codechicken/nei/bookmark/BookmarkItem.java b/src/main/java/codechicken/nei/bookmark/BookmarkItem.java index 2e57e63c..48bc0f52 100644 --- a/src/main/java/codechicken/nei/bookmark/BookmarkItem.java +++ b/src/main/java/codechicken/nei/bookmark/BookmarkItem.java @@ -10,12 +10,16 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.fluids.FluidStack; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + import codechicken.nei.NEIClientUtils; import codechicken.nei.PositionedStack; import codechicken.nei.recipe.Recipe; import codechicken.nei.recipe.Recipe.RecipeId; import codechicken.nei.recipe.Recipe.RecipeIngredient; import codechicken.nei.recipe.StackInfo; +import codechicken.nei.util.NBTJson; public class BookmarkItem { @@ -245,6 +249,45 @@ public class BookmarkItem { return builder(groupId, stack).build(); } + public static BookmarkItem of(JsonObject jsonObject) { + final NBTTagCompound itemStackNBT = (NBTTagCompound) NBTJson.toNbt(jsonObject.get("item")); + final ItemStack itemStack = StackInfo.loadFromNBT(itemStackNBT); + + if (itemStack != null) { + final int groupId = jsonObject.has("groupId") ? jsonObject.get("groupId").getAsInt() + : BookmarkGrid.DEFAULT_GROUP_ID; + final BookmarkItem.Builder builder = BookmarkItem.builder(groupId, itemStack); + + if (jsonObject.has("type")) { + builder.type(BookmarkItemType.fromInt(jsonObject.get("type").getAsInt())); + } else if (jsonObject.has("ingredient") && jsonObject.get("ingredient").getAsBoolean()) { + builder.type(BookmarkItemType.INGREDIENT); + } else { + builder.type(BookmarkItemType.RESULT); + } + + if (jsonObject.has("multiplier")) { + builder.multiplier(jsonObject.get("multiplier").getAsInt()); + } + + if (jsonObject.has("factor")) { + builder.factor(jsonObject.get("factor").getAsInt()); + } + + if (jsonObject.has("chance")) { + builder.chance(jsonObject.get("chance").getAsInt()); + } + + if (jsonObject.get("recipeId") instanceof JsonObject recipeJson) { + builder.recipeId(RecipeId.of(recipeJson)); + } + + return builder.build(); + } + + return null; + } + public BookmarkItem copyWithMultiplier(long multiplier) { return new BookmarkItem( this.groupId, @@ -364,4 +407,27 @@ public class BookmarkItem { return false; } + public JsonObject toJsonObject() { + final JsonObject jsonObject = new JsonObject(); + + jsonObject.add("item", NBTJson.toJsonObject(StackInfo.itemStackToNBT(getItemStack()))); + jsonObject.add("multiplier", new JsonPrimitive(this.multiplier)); + jsonObject.add("factor", new JsonPrimitive(this.factor)); + jsonObject.add("type", new JsonPrimitive(this.type.toInt())); + + if (this.groupId != BookmarkGrid.DEFAULT_GROUP_ID) { + jsonObject.add("groupId", new JsonPrimitive(this.groupId)); + } + + if (this.recipeId != null) { + jsonObject.add("recipeId", this.recipeId.toJsonObject()); + } + + if (this.chance != PositionedStack.CHANCE_FULL) { + jsonObject.add("chance", new JsonPrimitive(this.chance)); + } + + return jsonObject; + } + } diff --git a/src/main/java/codechicken/nei/bookmark/BookmarkPayload.java b/src/main/java/codechicken/nei/bookmark/BookmarkPayload.java new file mode 100644 index 00000000..0a04e7a8 --- /dev/null +++ b/src/main/java/codechicken/nei/bookmark/BookmarkPayload.java @@ -0,0 +1,283 @@ +package codechicken.nei.bookmark; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; + +import com.google.gson.JsonObject; + +import codechicken.lib.gui.GuiDraw; +import codechicken.nei.BookmarkPanel.BookmarkViewMode; +import codechicken.nei.ItemPanels; +import codechicken.nei.ItemQuantityField; +import codechicken.nei.ItemStackSet; +import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType; +import codechicken.nei.recipe.Recipe; +import codechicken.nei.recipe.Recipe.RecipeId; +import codechicken.nei.recipe.Recipe.RecipeIngredient; +import codechicken.nei.recipe.StackInfo; +import codechicken.nei.util.NBTJson; + +public class BookmarkPayload { + + private String linkType; + private NBTTagCompound target; + + private NBTTagCompound group; + + private NBTTagList items = new NBTTagList(); + private NBTTagList recipes = new NBTTagList(); + + private BookmarkPayload(String linkType, ItemStack stack) { + this.linkType = linkType; + this.target = stack != null ? StackInfo.itemStackToNBT(stack) : null; + } + + public static BookmarkPayload of(NBTTagCompound nbt) { + final BookmarkPayload payload = new BookmarkPayload(nbt.getString("linkType"), null); + + if (nbt.hasKey("item")) { + payload.target = nbt.getCompoundTag("item"); + } + + if (nbt.hasKey("group")) { + payload.group = nbt.getCompoundTag("group"); + } + + payload.items = nbt.getTagList("items", 10); + payload.recipes = nbt.getTagList("recipes", 10); + + return payload; + } + + public static BookmarkPayload of(BookmarkItem item) { + final BookmarkPayload payload = new BookmarkPayload("recipe", item.getItemStack()); + final BookmarkGrid grid = ItemPanels.bookmarkPanel.getGrid(); + final Map recipesIndexes = new HashMap<>(); + final RecipeId recipeId = item.recipeId; + final int groupId = item.groupId; + + payload.items = new NBTTagList(); + payload.recipes = new NBTTagList(); + + if (recipeId != null) { + + for (int itemIndex = 0; itemIndex < grid.size(); itemIndex++) { + final BookmarkItem currentItem = grid.getBookmarkItem(itemIndex); + + if (currentItem != null && currentItem.groupId == groupId && recipeId.equals(currentItem.recipeId)) { + addBookmarkItemToPayload(payload, currentItem, recipesIndexes); + } + + } + + } else { + addBookmarkItemToPayload(payload, item, recipesIndexes); + } + + return payload; + } + + public static BookmarkPayload of(Recipe recipe) { + final BookmarkPayload payload = new BookmarkPayload("recipe", recipe.getResult()); + final Map recipesIndexes = new HashMap<>(); + final ItemStackSet seenIngredients = new ItemStackSet(); + final ItemStackSet seenResults = new ItemStackSet(); + + for (RecipeIngredient result : recipe.getResults()) { + ItemStack stack = result.getItemStack(); + if (!seenResults.contains(stack)) { + final BookmarkItem item = BookmarkItem.builder(-1, stack, recipe, BookmarkItemType.RESULT).build(); + addBookmarkItemToPayload(payload, item, recipesIndexes); + seenResults.add(stack); + } + } + + for (RecipeIngredient ingr : recipe.getIngredients()) { + ItemStack stack = ingr.getItemStack(); + if (!seenIngredients.contains(stack)) { + final BookmarkItem item = BookmarkItem.builder(-1, stack, recipe, BookmarkItemType.INGREDIENT).build(); + addBookmarkItemToPayload(payload, item, recipesIndexes); + seenIngredients.add(stack); + } + } + + return payload; + } + + public static BookmarkPayload of(int groupId) { + final BookmarkPayload payload = new BookmarkPayload("group", null); + final Map recipesIndexes = new HashMap<>(); + final BookmarkGrid grid = ItemPanels.bookmarkPanel.getGrid(); + + for (int itemIndex = 0; itemIndex < grid.size(); itemIndex++) { + final BookmarkItem item = grid.getBookmarkItem(itemIndex); + + if (item != null && item.groupId == groupId) { + addBookmarkItemToPayload(payload, item, recipesIndexes); + } + } + + payload.group = generateGroup(grid.getGroup(groupId)); + + return payload; + } + + public static BookmarkPayload of(ItemStack stackover, RecipeId recipeId) { + final BookmarkPayload payload = new BookmarkPayload("item", stackover); + final Map recipesIndexes = new HashMap<>(); + final Point mousePos = GuiDraw.getMousePosition(); + + if (ItemPanels.itemPanel.containsWithSubpanels(mousePos.x, mousePos.y)) { + stackover = ItemQuantityField.prepareStackWithQuantity(stackover, 0); + } + + final BookmarkItem item = BookmarkItem.builder(-1, stackover).recipeId(recipeId).type(BookmarkItemType.ITEM) + .build(); + + addBookmarkItemToPayload(payload, item, recipesIndexes); + + return payload; + } + + private static NBTTagCompound generateGroup(BookmarkGroup group) { + final NBTTagCompound groupTag = new NBTTagCompound(); + final NBTTagList collapsedRecipes = new NBTTagList(); + + groupTag.setString("viewmode", group.viewMode.toString()); + groupTag.setBoolean("crafting", group.crafting != null); + groupTag.setBoolean("collapsed", group.collapsed); + + for (RecipeId recipeId : group.collapsedRecipes) { + collapsedRecipes.appendTag(NBTJson.toNbt(recipeId.toJsonObject())); + } + + groupTag.setTag("collapsedRecipes", collapsedRecipes); + + return groupTag; + } + + private static void addBookmarkItemToPayload(BookmarkPayload payload, BookmarkItem item, + Map recipesIndexes) { + final JsonObject itemTag = item.toJsonObject(); + itemTag.remove("groupId"); + + if (item.recipeId != null) { + final int recipeIndex = recipesIndexes + .computeIfAbsent(item.recipeId, recipeId -> addRecipeToPayload(payload, recipeId, recipesIndexes)); + itemTag.addProperty("recipeId", recipeIndex); + } + + payload.items.appendTag(NBTJson.toNbt(itemTag)); + } + + private static int addRecipeToPayload(BookmarkPayload payload, RecipeId recipeId, + Map recipesIndexes) { + final NBTTagCompound recipeTag = new NBTTagCompound(); + final int recipeIndex = payload.recipes.tagCount(); + + recipeTag.setTag("recipeId", NBTJson.toNbt(recipeId.toJsonObject())); + recipeTag.setInteger("recipeIndex", recipeIndex); + payload.recipes.appendTag(recipeTag); + + return recipeIndex; + } + + public NBTTagCompound toNBT() { + final NBTTagCompound payloadTag = new NBTTagCompound(); + payloadTag.setString("linkType", linkType); + + if (target != null) { + payloadTag.setTag("item", target); + } + + if (group != null) { + payloadTag.setTag("group", group); + } + + payloadTag.setTag("items", items); + payloadTag.setTag("recipes", recipes); + + return payloadTag; + } + + public String getLinkType() { + return this.linkType; + } + + public ItemStack getTargetStack() { + return this.target != null ? StackInfo.loadFromNBT(this.target) : null; + } + + public BookmarkGroup getGroup() { + if (this.group == null) { + return null; + } + + final boolean crafting = this.group.hasKey("crafting") && this.group.getBoolean("crafting"); + final BookmarkViewMode viewMode; + + if (this.group.hasKey("viewmode")) { + viewMode = BookmarkViewMode.valueOf(this.group.getString("viewmode")); + } else { + viewMode = BookmarkViewMode.TODO_LIST; + } + + final BookmarkGroup group = new BookmarkGroup(viewMode, crafting); + + if (this.group.hasKey("collapsed")) { + group.collapsed = this.group.getBoolean("collapsed"); + } + + if (this.group.hasKey("collapsedRecipes")) { + final NBTTagList collapsedRecipes = this.group.getTagList("collapsedRecipes", 10); + + for (int i = 0; i < collapsedRecipes.tagCount(); i++) { + final NBTTagCompound recipeTag = collapsedRecipes.getCompoundTagAt(i); + final RecipeId recipeId = RecipeId.of((JsonObject) NBTJson.toJsonObject(recipeTag)); + group.collapsedRecipes.add(recipeId); + } + } + + return group; + } + + public List getBookmarkItems(int groupId) { + final List bookmarkItems = new ArrayList<>(); + final Map recipesMap = new HashMap<>(); + + for (int i = 0; i < recipes.tagCount(); i++) { + final NBTTagCompound recipeTag = recipes.getCompoundTagAt(i); + final RecipeId recipeId = RecipeId + .of((JsonObject) NBTJson.toJsonObject(recipeTag.getCompoundTag("recipeId"))); + recipesMap.put(recipeTag.getInteger("recipeIndex"), recipeId); + } + + for (int i = 0; i < items.tagCount(); i++) { + final NBTTagCompound itemTag = items.getCompoundTagAt(i); + RecipeId recipeId = null; + + if (itemTag.hasKey("recipeId")) { + recipeId = recipesMap.get(itemTag.getInteger("recipeId")); + itemTag.removeTag("recipeId"); + } + + final BookmarkItem item = BookmarkItem.of((JsonObject) NBTJson.toJsonObject(itemTag)); + + if (item != null) { + item.groupId = groupId; + item.recipeId = recipeId; + bookmarkItems.add(item); + } + } + + return bookmarkItems; + } + +} diff --git a/src/main/java/codechicken/nei/bookmark/BookmarkStorage.java b/src/main/java/codechicken/nei/bookmark/BookmarkStorage.java index 8dd05fbc..23e9e987 100644 --- a/src/main/java/codechicken/nei/bookmark/BookmarkStorage.java +++ b/src/main/java/codechicken/nei/bookmark/BookmarkStorage.java @@ -10,7 +10,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import org.apache.commons.io.IOUtils; @@ -25,10 +24,7 @@ import com.google.gson.JsonSyntaxException; import codechicken.nei.BookmarkPanel.BookmarkViewMode; import codechicken.nei.NEIClientConfig; -import codechicken.nei.PositionedStack; -import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType; import codechicken.nei.recipe.Recipe.RecipeId; -import codechicken.nei.recipe.StackInfo; import codechicken.nei.util.NBTJson; public class BookmarkStorage { @@ -199,7 +195,11 @@ public class BookmarkStorage { continue; } - if (!addItemToGrid(grid, parser.parse(itemStr).getAsJsonObject())) { + final BookmarkItem item = BookmarkItem.of(parser.parse(itemStr).getAsJsonObject()); + + if (item != null) { + grid.addItem(item, false); + } else { wasError = true; NEIClientConfig.logger.warn( "Failed to load bookmarked ItemStack from json string, the item no longer exists:\n{}", @@ -238,53 +238,6 @@ public class BookmarkStorage { this.bookmarkFile = bookmarkFile; } - private boolean addItemToGrid(BookmarkGrid grid, JsonObject jsonObject) { - NBTTagCompound itemStackNBT; - - if (jsonObject.get("item") != null) { - itemStackNBT = (NBTTagCompound) NBTJson.toNbt(jsonObject.get("item")); - } else { // old format - itemStackNBT = (NBTTagCompound) NBTJson.toNbt(jsonObject); - } - - ItemStack itemStack = StackInfo.loadFromNBT(itemStackNBT); - - if (itemStack != null) { - int groupId = jsonObject.has("groupId") && grid.groups.containsKey(jsonObject.get("groupId").getAsInt()) - ? jsonObject.get("groupId").getAsInt() - : BookmarkGrid.DEFAULT_GROUP_ID; - final BookmarkItem.Builder builder = BookmarkItem.builder(groupId, itemStack); - - if (jsonObject.has("type")) { - builder.type(BookmarkItemType.fromInt(jsonObject.get("type").getAsInt())); - } else if (jsonObject.has("ingredient") && jsonObject.get("ingredient").getAsBoolean()) { - builder.type(BookmarkItemType.INGREDIENT); - } else { - builder.type(BookmarkItemType.RESULT); - } - - if (jsonObject.has("multiplier")) { - builder.multiplier(jsonObject.get("multiplier").getAsInt()); - } - - if (jsonObject.has("factor")) { - builder.factor(jsonObject.get("factor").getAsInt()); - } - - if (jsonObject.has("chance")) { - builder.chance(jsonObject.get("chance").getAsInt()); - } - - if (jsonObject.get("recipeId") instanceof JsonObject recipeJson) { - builder.recipeId(RecipeId.of(recipeJson)); - } - - grid.addItem(builder.build(), false); - } - - return itemStack != null; - } - public void save() { if (this.bookmarkFile == null) { @@ -325,26 +278,7 @@ public class BookmarkStorage { for (BookmarkItem item : grid.bookmarkItems) { try { - JsonObject row = new JsonObject(); - - row.add("item", NBTJson.toJsonObject(StackInfo.itemStackToNBT(item.getItemStack()))); - row.add("multiplier", new JsonPrimitive(item.multiplier)); - row.add("factor", new JsonPrimitive(item.factor)); - row.add("type", new JsonPrimitive(item.type.toInt())); - - if (item.groupId != BookmarkGrid.DEFAULT_GROUP_ID) { - row.add("groupId", new JsonPrimitive(item.groupId)); - } - - if (item.recipeId != null) { - row.add("recipeId", item.recipeId.toJsonObject()); - } - - if (item.chance != PositionedStack.CHANCE_FULL) { - row.add("chance", new JsonPrimitive(item.chance)); - } - - strings.add(NBTJson.toJson(row)); + strings.add(NBTJson.toJson(item.toJsonObject())); } catch (JsonSyntaxException e) { NEIClientConfig.logger.error("Failed to stringify bookmarked ItemStack to json string"); } diff --git a/src/main/java/codechicken/nei/bookmark/BookmarksGridSlot.java b/src/main/java/codechicken/nei/bookmark/BookmarksGridSlot.java index 957ce002..5bbede47 100644 --- a/src/main/java/codechicken/nei/bookmark/BookmarksGridSlot.java +++ b/src/main/java/codechicken/nei/bookmark/BookmarksGridSlot.java @@ -193,7 +193,7 @@ public class BookmarksGridSlot extends ItemsGridSlot { protected HandlerInfo getHandlerInfo() { if (this.bookmarkItem.recipeId != null && this.handlerInfo == null) { - this.handlerInfo = GuiRecipeTab.getHandlerInfo(this.bookmarkItem.recipeId.getHandleName(), null); + this.handlerInfo = GuiRecipeTab.getHandlerInfo(this.bookmarkItem.recipeId.getHandlerName(), null); } return this.handlerInfo; } diff --git a/src/main/java/codechicken/nei/commands/CommandBookmarkAdd.java b/src/main/java/codechicken/nei/commands/CommandBookmarkAdd.java index f66ac46f..5ab97f57 100644 --- a/src/main/java/codechicken/nei/commands/CommandBookmarkAdd.java +++ b/src/main/java/codechicken/nei/commands/CommandBookmarkAdd.java @@ -1,18 +1,39 @@ package codechicken.nei.commands; +import java.util.Arrays; + import net.minecraft.command.CommandBase; import net.minecraft.command.ICommandSender; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.JsonToNBT; -import net.minecraft.nbt.NBTException; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import codechicken.nei.ItemPanels; +import codechicken.nei.LRUCache; +import codechicken.nei.bookmark.BookmarkGrid; +import codechicken.nei.bookmark.BookmarkGroup; +import codechicken.nei.bookmark.BookmarkPayload; public class CommandBookmarkAdd extends CommandBase { + private static final LRUCache PAYLOAD_CACHE = new LRUCache<>(64); + + public static void cachePayload(String payloadId, NBTTagCompound payload) { + if (payloadId == null || payloadId.isEmpty() || payload == null) { + return; + } + + synchronized (PAYLOAD_CACHE) { + PAYLOAD_CACHE.put(payloadId, (NBTTagCompound) payload.copy()); + } + } + + private static NBTTagCompound getCachedPayload(String payloadId) { + synchronized (PAYLOAD_CACHE) { + return PAYLOAD_CACHE.get(payloadId); + } + } + @Override public String getCommandName() { return "nei_bookmark"; @@ -20,7 +41,7 @@ public class CommandBookmarkAdd extends CommandBase { @Override public String getCommandUsage(ICommandSender sender) { - return "/nei_bookmark "; + return "/nei_bookmark "; } @Override @@ -30,19 +51,37 @@ public class CommandBookmarkAdd extends CommandBase { @Override public void processCommand(ICommandSender sender, String[] args) { - - if (args.length < 1 || args[0].length() < 3) { - sender.addChatMessage(new ChatComponentText("§c/nei_bookmark ")); + if (args.length < 1) { + sender.addChatMessage(new ChatComponentText("§c/nei_bookmark ")); return; } try { - ItemPanels.bookmarkPanel.addItem( - ItemStack.loadItemStackFromNBT((NBTTagCompound) JsonToNBT.func_150315_a(String.join(" ", args)))); + final String payloadId = String.join(" ", Arrays.copyOfRange(args, 0, args.length)); + final NBTTagCompound payload = getCachedPayload(payloadId); + + if (payload == null) { + throw new IllegalArgumentException("Missing cached payload"); + } + + addBookmarkFromPayload((NBTTagCompound) payload.copy()); sender.addChatMessage(new ChatComponentTranslation("nei.chat.bookmark_added.text")); - } catch (NBTException e) { + } catch (Exception e) { sender.addChatMessage(new ChatComponentText(e.toString())); } } + private static void addBookmarkFromPayload(NBTTagCompound nbt) { + final BookmarkPayload payload = BookmarkPayload.of(nbt); + final BookmarkGrid grid = ItemPanels.bookmarkPanel.getGrid(); + final BookmarkGroup group = payload.getGroup(); + int groupId = BookmarkGrid.DEFAULT_GROUP_ID; + + if (group != null) { + groupId = grid.addGroup(group); + } + + payload.getBookmarkItems(groupId).forEach(item -> grid.addItem(item, true)); + } + } diff --git a/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java b/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java index 1a91cea2..4bcf1ab8 100644 --- a/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiCraftingRecipe.java @@ -82,8 +82,8 @@ public class GuiCraftingRecipe extends GuiRecipe { } else if ("recipeId".equals(outputId)) { ItemStack stack = (ItemStack) results[0]; RecipeId recipeId = (RecipeId) results[1]; - craftinghandlers = filterByHandlerName(craftinghandlers, recipeId.getHandleName()); - serialCraftingHandlers = filterByHandlerName(serialCraftingHandlers, recipeId.getHandleName()); + craftinghandlers = filterByHandlerName(craftinghandlers, recipeId.getHandlerName()); + serialCraftingHandlers = filterByHandlerName(serialCraftingHandlers, recipeId.getHandlerName()); recipeHandlerFunction = h -> h.getRecipeHandler("item", stack); } else { recipeHandlerFunction = h -> h.getRecipeHandler(outputId, results); diff --git a/src/main/java/codechicken/nei/recipe/GuiFavoriteButton.java b/src/main/java/codechicken/nei/recipe/GuiFavoriteButton.java index 0181ddef..2fc7abc2 100644 --- a/src/main/java/codechicken/nei/recipe/GuiFavoriteButton.java +++ b/src/main/java/codechicken/nei/recipe/GuiFavoriteButton.java @@ -230,7 +230,7 @@ public class GuiFavoriteButton extends GuiRecipeButton { } } - if (recipeId != null && FavoriteRecipes.FAVORITE_ITEM.contains(recipeId.getHandleName())) { + if (recipeId != null && FavoriteRecipes.FAVORITE_ITEM.contains(recipeId.getHandlerName())) { items.add(stack); } else { Recipe recipe = Recipe.of(recipeId); diff --git a/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java b/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java index 86b9fb50..f9564867 100644 --- a/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java +++ b/src/main/java/codechicken/nei/recipe/GuiOverlayButton.java @@ -22,6 +22,7 @@ import codechicken.nei.NEIClientUtils; import codechicken.nei.PositionedStack; import codechicken.nei.api.IOverlayHandler; import codechicken.nei.bookmark.BookmarkGrid; +import codechicken.nei.bookmark.BookmarkPayload; import codechicken.nei.drawable.DrawableBuilder; import codechicken.nei.drawable.DrawableResource; import codechicken.nei.util.NEIMouseUtils; @@ -171,6 +172,10 @@ public class GuiOverlayButton extends GuiRecipeButton { KeyManager.getKeyName("bookmark.add", NEIClientUtils.SHIFT_HASH + NEIClientUtils.CTRL_HASH), NEIClientUtils.translate("recipe.overlay.bookmarkRecipeAndCount")); + hotkeys.put( + KeyManager.getKeyName("bookmark.chat_link", NEIClientUtils.SHIFT_HASH + NEIClientUtils.CTRL_HASH), + NEIClientUtils.translate("recipe.overlay.sendRecipeToChat")); + return hotkeys; } @@ -329,6 +334,10 @@ public class GuiOverlayButton extends GuiRecipeButton { if (KeyManager.isKeyDown("bookmark.add") && NEIClientUtils.shiftKey()) { saveRecipeInBookmark(); } + + if (KeyManager.isKeyDown("bookmark.chat_link") && NEIClientUtils.controlKey() && NEIClientUtils.shiftKey()) { + sendRecipeChatLink(); + } } public void saveRecipeInBookmark() { @@ -343,4 +352,7 @@ public class GuiOverlayButton extends GuiRecipeButton { } } + private void sendRecipeChatLink() { + NEIClientUtils.sendChatItemLink(BookmarkPayload.of(getRecipe()).toNBT()); + } } diff --git a/src/main/java/codechicken/nei/recipe/GuiRecipe.java b/src/main/java/codechicken/nei/recipe/GuiRecipe.java index 997140c5..9aa907a1 100644 --- a/src/main/java/codechicken/nei/recipe/GuiRecipe.java +++ b/src/main/java/codechicken/nei/recipe/GuiRecipe.java @@ -476,7 +476,7 @@ public abstract class GuiRecipe extends GuiContainer i final H localHandler = this.currenthandlers.get(j); final HandlerInfo localHandlerInfo = GuiRecipeTab.getHandlerInfo(localHandler); - if (localHandlerInfo.getHandlerName().equals(this.recipeId.getHandleName())) { + if (localHandlerInfo.getHandlerName().equals(this.recipeId.getHandlerName())) { recipetype = j; if (!this.recipeId.getIngredients().isEmpty()) { diff --git a/src/main/java/codechicken/nei/recipe/Recipe.java b/src/main/java/codechicken/nei/recipe/Recipe.java index 203cd3c7..46154700 100644 --- a/src/main/java/codechicken/nei/recipe/Recipe.java +++ b/src/main/java/codechicken/nei/recipe/Recipe.java @@ -89,7 +89,7 @@ public class Recipe { return new RecipeId(result, handlerName, ingredients); } - public String getHandleName() { + public String getHandlerName() { return this.handlerName; } diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index dc303302..f0ca179b 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -32,7 +32,9 @@ nei.bookmark.group.pull_items=Pull all Items nei.bookmark.group.pull_items_shift=Pull missing Items nei.bookmark.group.craft_missing=Craft Missing Items nei.bookmark.group.craft_all=Craft Items +nei.bookmark.group.chat_link=Send Bookmark Group to Chat nei.bookmark.item.sorting=Move Position +nei.bookmark.item.chat_link=Send Item to Chat nei.bookmark.remove_recipe=Remove Recipe nei.bookmark.remove_item=Remove Item nei.bookmark.add_item=Add Bookmark @@ -42,6 +44,7 @@ nei.bookmark.add_item_with_count=Add Bookmark with Count nei.bookmark.change_quantity=Change Quantity nei.bookmark.change_quantity_step=Change Quantity with Multiplier nei.bookmark.recipe.toggle_collapse=Toggle Collapse/Expand +nei.bookmark.recipe.chat_link=Send Recipe to Chat nei.bookmark.crafting_chain=Crafting Chain nei.bookmark.crafting_chain.input=Ingredients nei.bookmark.crafting_chain.missing=Missing Items @@ -68,6 +71,7 @@ nei.itempanel.infinite_item=Give Infinite Item nei.itempanel.copy_name=Copy Item Name nei.itempanel.copy_oredict=Copy Item OreDictionary nei.itempanel.copy_id=Copy Item ID + nei.itempanel.chat_link_item=Send item link to chat nei.enchant=Enchant @@ -143,6 +147,9 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins nei.chat.moboverlay.0=Mob Spawning Overlay: §cOff nei.chat.moboverlay.1=Mob Spawning Overlay: §2On nei.chat.item_link.text=<%s> §6Click to add bookmark: %s +nei.chat.item_link.item.text=Bookmark Item +nei.chat.item_link.recipe.text=Bookmark Recipe +nei.chat.item_link.group.text=Bookmark Group nei.chat.bookmark_added.text=Bookmark added! nei.chat.recipeid.many_params=Too many parameters! Usage: %s @@ -194,6 +201,7 @@ nei.options.keys.bookmark.add=Add Bookmark nei.options.keys.bookmark.remove_recipe=Remove Group from Bookmarks (Shift +) nei.options.keys.bookmark.pull_items=Pull all Bookmarked Items nei.options.keys.bookmark.hide=Toggle Bookmark Panel Visibility +nei.options.keys.bookmark.chat_link=Send link to chat (Ctrl +) nei.options.keys.gui.overlay=Fill/Overlay Crafting Grid nei.options.keys.gui.getprevioussearch=Previous Search nei.options.keys.gui.getnextsearch=Next Search @@ -202,7 +210,6 @@ nei.options.keys.copy.name=Copy Item Name (Ctrl +) nei.options.keys.gui.craft_items=Craft Items (Shift +) nei.options.keys.copy.oredict=Copy Item OreDictionary (Ctrl +) nei.options.keys.copy.identifier=Copy Item ID (Ctrl +) -nei.options.keys.copy.chat_link_item=Send item link to chat (Ctrl +) nei.options.keys.itemzoom.toggle=Toggle Item Zoom (Shift +) nei.options.keys.itemzoom.hold=Hold to Display Item Zoom nei.options.keys.itemzoom.zoom_in=Item Zoom In @@ -605,6 +612,7 @@ nei.recipe.overlay.overlayRecipe=Overlay Recipe nei.recipe.overlay.fillCraftingGrid=Fill Crafting Grid nei.recipe.overlay.bookmarkRecipe=Save with Recipe nei.recipe.overlay.bookmarkRecipeAndCount=Save with Recipe & Count +nei.recipe.overlay.sendRecipeToChat=Send Recipe to Chat nei.recipe.favorite=Favorite nei.recipe.favorite.bookmark_recipe=Save Recipe Tree diff --git a/src/main/resources/assets/nei/lang/ru_RU.lang b/src/main/resources/assets/nei/lang/ru_RU.lang index 6f22dcab..f852421c 100644 --- a/src/main/resources/assets/nei/lang/ru_RU.lang +++ b/src/main/resources/assets/nei/lang/ru_RU.lang @@ -20,6 +20,7 @@ nei.bookmark.group.sorting=Сортировка nei.bookmark.group.remove_recipe=Удалить группу nei.bookmark.group.pull_items=Переместить все предметы nei.bookmark.group.pull_items_shift=Переместить недостающие предметы +nei.bookmark.group.chat_link=Отправить группу в чат nei.bookmark.crafting_chain=Цепочка крафта nei.bookmark.crafting_chain.input=Ингредиенты nei.bookmark.crafting_chain.output=Результаты @@ -141,6 +142,7 @@ nei.options.keys.bookmark.add=Добавить закладку nei.options.keys.bookmark.remove_recipe=Удалить группу из закладок (Shift +) nei.options.keys.bookmark.pull_items=Переместить все отмеченные предметы nei.options.keys.bookmark.hide=Переключить видимость панели закладок +nei.options.keys.bookmark.chat_link=Отправить ссылку в чат (Ctrl +) nei.options.keys.gui.overlay=Заполнить сетку крафта nei.options.keys.gui.getprevioussearch=Предыдущий поиск nei.options.keys.gui.getnextsearch=Следующий поиск @@ -149,7 +151,6 @@ nei.options.keys.copy.name=Скопировать название предме nei.options.keys.gui.craft_items=Создать предметы (Shift +) nei.options.keys.copy.oredict=Скопировать OreDictionary предмета (Ctrl +) nei.options.keys.copy.identifier=Скопировать ID предмета (Ctrl +) -nei.options.keys.copy.chat_link_item=Отправить ссылку на предмет в чат (Ctrl +) nei.options.keys.itemzoom.toggle=Переключить увеличение предмета (Shift +) nei.options.keys.itemzoom.hold=Удерживать для отображения увеличения предмета nei.options.keys.itemzoom.zoom_in=Увеличить предмет @@ -422,6 +423,7 @@ nei.recipe.overlay.missing=Отсутствует следующее nei.recipe.overlay.mismatch=Несоответствие сетки крафта nei.recipe.overlay.fillCraftingGrid=Заполнить сетку крафта nei.recipe.overlay.bookmarkRecipe=Сохранить с рецептом +nei.recipe.overlay.sendRecipeToChat=Отправить рецепт в чат nei.itemsort.minecraft=Minecraft nei.itemsort.minecraft.tip=Сортирует предметы из ванильного Minecraft первыми