diff --git a/README.md b/README.md index 06ba32b3..ed4fca1d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,59 @@ If you have issues with NEI outside of the GTNH modpack you may report them in t * Cycle between Recipe, Utility, and Cheat mode by ctrl clicking on the Wrench Icon * GT5u Tools/Items and GT6 tools should now properly work with the Overlay Recipe Transfer +### Information Page Handler +Want to add some information about a block or item without making a massive tooltip for it? You can add information about any block or item by registering it in the Information Handler. + +Information page(s) are displayed when either the uses or recipes of a matching item are searched. All matching items will cycle at the top of the information page. + +Your mod can call: +```java +FMLInterModComms.sendMessage("NotEnoughItems", "addItemInfo", nbt); +``` +Where nbt is an NBTTagCompound formatted as described below. + +| Tag | Type | Description | +|------------------|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `filter` | String | A string filter to match what items should return this information page.
modname:itemname, metadata, Ore Dictionary tags, and regex can all be used. The full format may be seen in `config/NEI/collapsibleitems.cfg`. | +| `page` / `pages` | String / String NBTTagList | The actual text to display. See below for differences between single and multi-page messages.
Both formats will automatically translate provided strings. | + +#### Single Page Format + +Use the `"page"` string tag when you only want to add one page of information: +```java +NBTTagCompound tag = new NBTTagCompound(); +tag.setString("filter", "minecraft:log 3"); +tag.setString("page", "Exotic wood found in tropical climates."); + +FMLInterModComms.sendMessage("NotEnoughItems", "addItemInfo", tag); +``` + +#### Multiple Page Format + +Use the `"pages"` tag as an `NBTTagList` of strings when you want more than one page: +```java +NBTTagCompound tag = new NBTTagCompound(); +tag.setString("filter", "minecraft:diamond_sword"); + +NBTTagList pages = new NBTTagList(); +pages.appendTag(new NBTTagString("A powerful melee weapon.")); +pages.appendTag(new NBTTagString("Can be enchanted for extra effects.")); + +tag.setTag("pages", pages); + +FMLInterModComms.sendMessage("NotEnoughItems", "addItemInfo", tag); +``` +* Each list entry is one page. +* Pages will appear in NEI with buttons to scroll through them. + +#### Item Matching + +All items that match `filter` will show your information page. See `config/NEI/collapsibleitems.cfg` for the full filter format. + +#### Item Information Config File + +If you're a modpack creator and want to make custom item information pages, they can easily be added in `config/NEI/informationpages.cfg`, which has a few more examples. + ## Other items of note: * Remove TMI style diff --git a/src/main/java/codechicken/nei/ItemList.java b/src/main/java/codechicken/nei/ItemList.java index 88aa744d..91b28296 100644 --- a/src/main/java/codechicken/nei/ItemList.java +++ b/src/main/java/codechicken/nei/ItemList.java @@ -28,6 +28,7 @@ import codechicken.nei.ThreadOperationTimer.TimeoutException; import codechicken.nei.api.ItemFilter; import codechicken.nei.api.ItemFilter.ItemFilterProvider; import codechicken.nei.api.ItemInfo; +import codechicken.nei.recipe.InformationHandler; import codechicken.nei.search.TooltipFilter; public class ItemList { @@ -289,7 +290,7 @@ public class ItemList { .collect(Collectors.toList()); } - // For optimization it generate itemslist, permutations, orders & collapsibleitems + // Generate itemlist, permutations, orders, collapsibleitems, and informationhandler stacks @Override @SuppressWarnings("unchecked") public void execute() { @@ -303,6 +304,7 @@ public class ItemList { ItemSorter.instance.ordering.clear(); CollapsibleItems.clearCache(); TooltipFilter.clearCache(); + InformationHandler.clearCache(); List items = new ArrayList<>(); ListMultimap itemMap = ArrayListMultimap.create(); @@ -329,6 +331,7 @@ public class ItemList { CollapsibleItems.putItem(stack); TooltipFilter.getSearchTooltip(stack); + InformationHandler.populateStacks(stack); } } diff --git a/src/main/java/codechicken/nei/NEIClientConfig.java b/src/main/java/codechicken/nei/NEIClientConfig.java index f36d4bf7..a44ab846 100644 --- a/src/main/java/codechicken/nei/NEIClientConfig.java +++ b/src/main/java/codechicken/nei/NEIClientConfig.java @@ -58,6 +58,7 @@ import codechicken.nei.config.preset.GuiPresetList; import codechicken.nei.event.NEIConfigsLoadedEvent; import codechicken.nei.recipe.GuiRecipeTab; import codechicken.nei.recipe.IRecipeHandler; +import codechicken.nei.recipe.InformationHandler; import codechicken.nei.recipe.RecipeCatalysts; import codechicken.nei.recipe.RecipeInfo; import codechicken.nei.util.NEIKeyboardUtils; @@ -899,6 +900,7 @@ public class NEIClientConfig { LayoutManager.load(); NEIController.load(); BookmarkContainerInfo.load(); + InformationHandler.load(); mainNEIConfigLoaded = true; new Thread("NEI Plugin Loader") { diff --git a/src/main/java/codechicken/nei/config/IMCHandler.java b/src/main/java/codechicken/nei/config/IMCHandler.java index 06ab21af..62837a3c 100644 --- a/src/main/java/codechicken/nei/config/IMCHandler.java +++ b/src/main/java/codechicken/nei/config/IMCHandler.java @@ -1,13 +1,13 @@ package codechicken.nei.config; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; import codechicken.core.CommonUtils; import codechicken.nei.NEIClientConfig; @@ -15,6 +15,7 @@ import codechicken.nei.NEIServerUtils; import codechicken.nei.recipe.CatalystInfo; import codechicken.nei.recipe.GuiRecipeTab; import codechicken.nei.recipe.HandlerInfo; +import codechicken.nei.recipe.InformationHandler; import codechicken.nei.recipe.RecipeCatalysts; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; @@ -33,18 +34,11 @@ public class IMCHandler { if (type == null || type.isEmpty()) continue; if (CommonUtils.isClient()) { switch (type) { - case "registerHandlerInfo": - handleRegisterHandlerInfo(message); - break; - case "removeHandlerInfo": - handleRemoveHandlerInfo(message); - break; - case "registerCatalystInfo": - handleRegisterCatalystInfo(message); - break; - case "removeCatalystInfo": - handleRemoveCatalystInfo(message); - break; + case "registerHandlerInfo" -> handleRegisterHandlerInfo(message); + case "removeHandlerInfo" -> handleRemoveHandlerInfo(message); + case "registerCatalystInfo" -> handleRegisterCatalystInfo(message); + case "removeCatalystInfo" -> handleRemoveCatalystInfo(message); + case "addItemInfo" -> handleAddItemInfo(message); } } } @@ -57,7 +51,7 @@ public class IMCHandler { } final NBTTagCompound tag = message.getNBTValue(); final String handler = tag.getString("handler"); - NEIClientConfig.logger.info("Processing registerHandlerInfo `" + handler + "` from " + message.getSender()); + NEIClientConfig.logger.info("Processing registerHandlerInfo `{}` from {}", handler, message.getSender()); final String modName = tag.getString("modName"); final String modId = tag.getString("modId"); @@ -100,7 +94,7 @@ public class IMCHandler { : HandlerInfo.DEFAULT_MAX_PER_PAGE; info.setHandlerDimensions(imageHeight, imageWidth, maxRecipesPerPage); } catch (NumberFormatException ignored) { - NEIClientConfig.logger.info("Error setting handler dimensions for " + handler); + NEIClientConfig.logger.info("Error setting handler dimensions for {}", handler); } // true if not set to false @@ -118,7 +112,7 @@ public class IMCHandler { } final NBTTagCompound tag = message.getNBTValue(); final String handler = tag.getString("handler"); - NEIClientConfig.logger.info("Processing removeHandlerInfo `" + handler + "` from " + message.getSender()); + NEIClientConfig.logger.info("Processing removeHandlerInfo `{}` from {}", handler, message.getSender()); GuiRecipeTab.handlerRemoverFromIMC.add(handler); } @@ -130,7 +124,7 @@ public class IMCHandler { } if (!processedCatalystSenders.contains(message.getSender())) { - NEIClientConfig.logger.info("Processing registerCatalystInfo from " + message.getSender()); + NEIClientConfig.logger.info("Processing registerCatalystInfo from {}", message.getSender()); processedCatalystSenders.add(message.getSender()); } final NBTTagCompound tag = message.getNBTValue(); @@ -139,23 +133,14 @@ public class IMCHandler { NEIClientConfig.logger.warn("Missing handlerID for registerCatalystInfo!"); return; } - final String itemName = tag.getString("itemName"); - final String nbtInfo = tag.hasKey("nbtInfo") ? tag.getString("nbtInfo") : null; - if (itemName.isEmpty()) { - NEIClientConfig.logger.warn(String.format("Missing itemName for registerCatalystInfo in `%s`!", handlerID)); - return; - } - final ItemStack itemStack = NEIServerUtils.getModdedItem(itemName, nbtInfo); + final ItemStack itemStack = getItemStackFromIMC(message, "registerCatalystInfo"); if (itemStack == null) { - NEIClientConfig.logger.warn(String.format("Cannot find item `%s`!", itemName)); return; } final int priority = tag.getInteger("priority"); - RecipeCatalysts .addOrPut(RecipeCatalysts.catalystsAdderFromIMC, handlerID, new CatalystInfo(itemStack, priority)); - NEIClientConfig.logger - .info(String.format("Added catalyst `%s` to handler %s", itemStack.getDisplayName(), handlerID)); + NEIClientConfig.logger.info("Added catalyst `{}` to handler {}", itemStack.getDisplayName(), handlerID); } private static void handleRemoveCatalystInfo(IMCMessage message) { @@ -164,33 +149,62 @@ public class IMCHandler { return; } - NEIClientConfig.logger.info("Processing removeCatalystInfo from " + message.getSender()); final NBTTagCompound tag = message.getNBTValue(); final String handlerID = tag.getString("handlerID"); if (handlerID.isEmpty()) { - NEIClientConfig.logger.warn("Missing handlerID for registerCatalystInfo!"); + NEIClientConfig.logger.warn("Missing handlerID for removeCatalystInfo!"); return; } - final String itemName = tag.getString("itemName"); - final String nbtInfo = tag.hasKey("nbtInfo") ? tag.getString("nbtInfo") : null; - if (itemName.isEmpty()) { - NEIClientConfig.logger.warn(String.format("Missing itemName for registerCatalystInfo in `%s`!", handlerID)); - return; - } - final ItemStack itemStack = NEIServerUtils.getModdedItem(itemName, nbtInfo); + final ItemStack itemStack = getItemStackFromIMC(message, "removeCatalystInfo"); if (itemStack == null) { - NEIClientConfig.logger.warn(String.format("Cannot find item `%s`!", itemName)); return; } - if (RecipeCatalysts.catalystsRemoverFromIMC.containsKey(handlerID)) { - RecipeCatalysts.catalystsRemoverFromIMC.get(handlerID).add(itemStack); - } else { - RecipeCatalysts.catalystsRemoverFromIMC - .put(handlerID, new ArrayList<>(Collections.singletonList(itemStack))); + RecipeCatalysts.catalystsRemoverFromIMC.computeIfAbsent(handlerID, k -> new ArrayList<>()).add(itemStack); + NEIClientConfig.logger.info("Removed catalyst `{}` from handler {}", itemStack.getDisplayName(), handlerID); + } + + private static void handleAddItemInfo(IMCMessage message) { + if (!message.isNBTMessage()) { + logInvalidMessage(message, "NBT"); + return; } - NEIClientConfig.logger - .info(String.format("Removed catalyst `%s` from handler %s", itemStack.getDisplayName(), handlerID)); + + final NBTTagCompound tag = message.getNBTValue(); + if (tag.hasKey("pages")) addMultipleInfoPages(message); + if (tag.hasKey("page")) addSingleInfoPage(message); + } + + private static void addMultipleInfoPages(IMCMessage message) { + final NBTTagCompound tag = message.getNBTValue(); + final String filter = tag.getString("filter"); + final NBTTagList pages = tag.getTagList("pages", 8); // 8 = TAG_String + for (int i = 0; i < pages.tagCount(); i++) { + String page = pages.getStringTagAt(i); + InformationHandler.addInformationPage(filter, page); + } + } + + private static void addSingleInfoPage(IMCMessage message) { + final NBTTagCompound tag = message.getNBTValue(); + String filter = tag.getString("filter"); + String page = tag.getString("page"); + InformationHandler.addInformationPage(filter, page); + } + + private static ItemStack getItemStackFromIMC(IMCMessage message, String logAction) { + final NBTTagCompound tag = message.getNBTValue(); + final String itemName = tag.getString("itemName"); + final String nbtInfo = tag.hasKey("nbtInfo") ? tag.getString("nbtInfo") : null; + + NEIClientConfig.logger.info("Processing {} for item `{}` from {}", logAction, itemName, message.getSender()); + final ItemStack itemStack = NEIServerUtils.getModdedItem(itemName, nbtInfo); + + if (itemStack == null) { + NEIClientConfig.logger.warn("Cannot find item `{}`!", itemName); + } + + return itemStack; } private static void logInvalidMessage(FMLInterModComms.IMCMessage message, String type) { diff --git a/src/main/java/codechicken/nei/recipe/InformationHandler.java b/src/main/java/codechicken/nei/recipe/InformationHandler.java new file mode 100644 index 00000000..d59b7f5b --- /dev/null +++ b/src/main/java/codechicken/nei/recipe/InformationHandler.java @@ -0,0 +1,150 @@ +package codechicken.nei.recipe; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.StatCollector; + +import codechicken.nei.ClientHandler; +import codechicken.nei.NEIClientConfig; +import codechicken.nei.NEIClientUtils; +import codechicken.nei.PositionedStack; +import codechicken.nei.api.ItemFilter; +import codechicken.nei.util.ItemStackFilterParser; + +public class InformationHandler extends TemplateRecipeHandler { + + private static final List ITEM_INFO = new ArrayList<>(); + + public static void addInformationPage(String filter, String description) { + if (filter.isEmpty() || description.isEmpty()) return; + ITEM_INFO.add(new InformationPage(filter, description)); + } + + public static void populateStacks(ItemStack stack) { + for (InformationPage page : ITEM_INFO) { + page.addItem(stack); + } + } + + @Override + public void drawExtras(int recipe) { + CachedInfoPage page = (CachedInfoPage) this.arecipes.get(recipe); + drawWrappedText(StatCollector.translateToLocal(page.getPage().info).replace("\\n", "\n"), 4, 24); + } + + private void drawWrappedText(String text, int x, int y) { + FontRenderer font = Minecraft.getMinecraft().fontRenderer; + List lines = font.listFormattedStringToWidth(text, 156); + for (String line : lines) { + font.drawString(line, x, y, 0); + y += 10; + } + } + + public static void clearCache() { + for (InformationPage page : ITEM_INFO) { + page.items.clear(); + } + } + + @Override + public String getRecipeName() { + return NEIClientUtils.translate("recipe.information"); + } + + @Override + public String getOverlayIdentifier() { + return "information"; + } + + @Override + public void loadCraftingRecipes(ItemStack result) { + for (InformationPage page : ITEM_INFO) { + if (page.filter.matches(result)) { + arecipes.add(new CachedInfoPage(page)); + } + } + } + + @Override + public void loadUsageRecipes(ItemStack ingredient) { + loadCraftingRecipes(ingredient); + } + + @Override + public String getGuiTexture() { + return "nei:textures/gui/recipebg.png"; + } + + private class CachedInfoPage extends CachedRecipe { + + private final InformationPage page; + private final PositionedStack stack; + + public CachedInfoPage(InformationPage page) { + this.page = page; + stack = new PositionedStack(page.items, 75, 2); + } + + @Override + public PositionedStack getResult() { + return null; + } + + @Override + public List getIngredients() { + return getCycledIngredients(cycleticks / 20, Collections.singletonList(this.stack)); + } + + public InformationPage getPage() { + return page; + } + } + + private static class InformationPage { + + final List items = new ArrayList<>(); + final String info; + final ItemFilter filter; + + public InformationPage(String filter, String info) { + this.filter = ItemStackFilterParser.parse(filter.trim()); + this.info = info; + } + + /** + * Adds the item stack if it matches the filter. + */ + public void addItem(ItemStack stack) { + if (filter.matches(stack)) items.add(stack); + } + } + + public static void load() { + ClientHandler.loadSettingsFile( + "informationpages.cfg", + lines -> parseFile(lines.collect(Collectors.toCollection(ArrayList::new)))); + } + + private static void parseFile(List lines) { + for (String rawLine : lines) { + String line = rawLine.trim(); + + int sepIndex = line.indexOf('='); + if (sepIndex == -1) { + NEIClientConfig.logger.warn("[NEI Info] Invalid line (no '=') in config: {}", line); + continue; + } + + String filter = line.substring(0, sepIndex).trim(); + String description = line.substring(sepIndex + 1).trim(); + addInformationPage(filter, description); + } + } +} diff --git a/src/main/java/codechicken/nei/recipe/RecipeInfo.java b/src/main/java/codechicken/nei/recipe/RecipeInfo.java index e5703230..c1c33020 100644 --- a/src/main/java/codechicken/nei/recipe/RecipeInfo.java +++ b/src/main/java/codechicken/nei/recipe/RecipeInfo.java @@ -101,6 +101,8 @@ public class RecipeInfo { API.registerUsageHandler(new BrewingRecipeHandler()); API.registerRecipeHandler(new FuelRecipeHandler()); API.registerUsageHandler(new FuelRecipeHandler()); + API.registerRecipeHandler(new InformationHandler()); + API.registerUsageHandler(new InformationHandler()); API.registerGuiOverlay(GuiCrafting.class, "crafting"); API.registerGuiOverlay(GuiInventory.class, "crafting2x2", 63, 20); diff --git a/src/main/resources/assets/nei/cfg/hiddenitems.cfg b/src/main/resources/assets/nei/cfg/hiddenitems.cfg index 262fa7c5..ce0cd6f8 100644 --- a/src/main/resources/assets/nei/cfg/hiddenitems.cfg +++ b/src/main/resources/assets/nei/cfg/hiddenitems.cfg @@ -1,18 +1,6 @@ # Hidden Items Filters # -# parts: -# modname:itemid - identify -# $orename - ore dictionary -# tag.color=red - tag -# 0 or 0-12 - damage -# -# modifiers: -# ! - logical not. exclude items that match the following expression (!minecraft:portal) -# r/.../ - standard java regex (r/^m\w{6}ft$/ = minecraft) -# , - logical or in token (minecraft:potion 16384-16462,!16386) -# | - logical or multi-item search (wrench|hammer) -# -# example: minecraft:potion 16384-16462,!16386 | $oreiron | tag.color=red +# Uses the same filter format at collapsibleitems.cfg # minecraft:portal minecraft:end_portal diff --git a/src/main/resources/assets/nei/cfg/informationpages.cfg b/src/main/resources/assets/nei/cfg/informationpages.cfg new file mode 100644 index 00000000..7abf966b --- /dev/null +++ b/src/main/resources/assets/nei/cfg/informationpages.cfg @@ -0,0 +1,28 @@ +# NEI Item Information Config +# +# One page per line, in the format: +# filter = description text +# +# The filter uses the same format as collapsibleitems.cfg. +# +# You can repeat the same filter on multiple lines to add extra pages. +# Use \n to insert line breaks inside the description. Use it multiple times for a larger gap between lines. +# Using a translation key for the description will automatically translate it. +# Only the first '=' after the filter is treated as the separator; feel free to put them in your descriptions. +# Spaces before and after '=' are removed from the description and are optional. +# The same item can match filters for multiple pages, unlike collapsibleitems.cfg only putting them in the first matching group. +# +# Empty lines and lines starting with # are ignored. +# +# Examples: +# # Jungle Wood (meta 3) +# minecraft:log 3 = Exotic wood found in tropical climates. +# +# # Diamond Sword (two pages of information) +# minecraft:diamond_sword = A powerful melee weapon. Does 8 damage total. +# minecraft:diamond_sword = Can be enchanted for extra effects. +# +# # Two different ore dictionary tags. Iron ingots match both. +# $iron = Iron is a very versatile material. It can be found below sea level in the Overworld. +# $ingot = Ingots are the refined form of metal or clay. +# diff --git a/src/main/resources/assets/nei/csv/handlers.csv b/src/main/resources/assets/nei/csv/handlers.csv index ba9096d2..10e9d557 100644 --- a/src/main/resources/assets/nei/csv/handlers.csv +++ b/src/main/resources/assets/nei/csv/handlers.csv @@ -5,6 +5,7 @@ codechicken.nei.recipe.FurnaceRecipeHandler,Minecraft,minecraft:furnace,,minecra codechicken.nei.recipe.ShapedRecipeHandler,Minecraft,minecraft:crafting_table,,minecraft,FALSE,,,65,166,5,,,,,, codechicken.nei.recipe.ShapelessRecipeHandler,Minecraft,minecraft:crafting_table,,minecraft,FALSE,,,65,166,5,,,,,, codechicken.nei.recipe.FuelRecipeHandler,Minecraft,,,minecraft,FALSE,,,65,166,5,nei:textures/nei_tabbed_sprites.png,80,0,14,14, +codechicken.nei.recipe.InformationHandler,Not Enough Items,,,nei,FALSE,,,65,166,1,nei:textures/nei_tabbed_sprites.png,96,0,16,16, advsolar.client.nei.MTRecipeHandler,Advanced Solar Panels,AdvancedSolarPanel:BlockMolecularTransformer,,AdvancedSolarPanel,TRUE,,,68,,5,,,,,, appeng.integration.modules.NEIHelpers.NEIAEShapedRecipeHandler,Applied Energistics 2,appliedenergistics2:item.ItemMultiPart:380,,appliedenergistics2,TRUE,,,65,,5,,,,,, appeng.integration.modules.NEIHelpers.NEIAEShapelessRecipeHandler,Applied Energistics 2,appliedenergistics2:item.ItemMultiPart:380,,appliedenergistics2,TRUE,,,65,,5,,,,,, diff --git a/src/main/resources/assets/nei/lang/en_US.lang b/src/main/resources/assets/nei/lang/en_US.lang index 9b81b45e..f5ea555d 100644 --- a/src/main/resources/assets/nei/lang/en_US.lang +++ b/src/main/resources/assets/nei/lang/en_US.lang @@ -513,6 +513,8 @@ nei.recipe.favorite=Favorite nei.recipe.favorite.bookmark_recipe=Save Recipe Tree nei.recipe.favorite.change_result_index=Change Output +nei.recipe.information=Information + nei.itemsort.minecraft=Minecraft nei.itemsort.minecraft.tip=Sorts items from vanilla minecraft first nei.itemsort.mod=Mod diff --git a/src/main/resources/assets/nei/textures/nei_tabbed_sprites.png b/src/main/resources/assets/nei/textures/nei_tabbed_sprites.png index ec3c6e42..e752d943 100644 Binary files a/src/main/resources/assets/nei/textures/nei_tabbed_sprites.png and b/src/main/resources/assets/nei/textures/nei_tabbed_sprites.png differ