Compare commits

...
36 Commits
Author SHA1 Message Date
slprime fab584b8c1 Merge branch 'master' into spawn-overlay-styles 2026-11-10 19:25:17 +02:00
slprime fbbeafcd79 Fix WorldClient leak after leaving the world (#1115) 2026-11-10 19:22:17 +02:00
UltraProdigy 9839d1d08f ci: run build and test on release branches 2026-11-09 01:11:38 -05:00
OrderedSet 0fc657c9d9 Clarity pass 2026-11-08 11:35:23 -08:00
OrderedSet 0e6f01f84d Make vertical radius configurable 2026-11-08 11:20:42 -08:00
OrderedSet 1478f21eb4 Improve performance another 2x 2026-11-08 10:38:34 -08:00
Martin Robertz dab939acb7 Merge branch 'master' into spawn-overlay-styles 2026-11-08 19:11:37 +01:00
OrderedSet 4d63ed13dc Add imports for move 2026-11-08 08:47:57 -08:00
OrderedSet 76b62072ab Move Mob* files to moboverlay/ 2026-11-08 08:41:37 -08:00
slprime 84d42de15f Make name optional in API.addCollapsibleGroup (#1114) 2026-11-08 18:13:37 +02:00
slprime d04e1c8e83 Add API.addCollapsibleGroup for registering collapsible item groups (#1113) 2026-11-08 17:37:44 +02:00
OrderedSet 42c47e5a89 Align images to north 2026-11-08 01:15:28 -08:00
github-actions[bot]andGitHub GTNH Actions 8d8f8370fc Optimising images contained in spawn-overlay-styles for #1111 (#1112)
Co-authored-by: GitHub GTNH Actions <[email protected]>
2026-11-08 00:47:21 -08:00
OrderedSet 753fc0da3a Clarity 2026-11-08 00:41:29 -08:00
OrderedSet ef8cac9c39 Comment quality + 1 comment accuracy fix 2026-11-08 00:34:56 -08:00
OrderedSet aa8f1c8adb Clarity rename Mob Spawn -> Mob Overlay 2026-11-08 00:23:11 -08:00
OrderedSet b29494935d Swap to cleaner GTNH logo + pufferfish. And cleanup 2026-11-08 00:18:12 -08:00
OrderedSet d6789c44d4 Use IWorldAccess to get instant updates 2026-11-07 23:47:12 -08:00
OrderedSet d07059cb5a Optimize + spotless 2026-11-07 23:33:44 -08:00
OrderedSet b7dcd0fb3d Comment cleanup 2026-11-07 23:09:54 -08:00
OrderedSet d04718c5c6 Add light level overlay variants, set default to connected 2026-11-07 22:59:28 -08:00
slprime 514af5669c Rollback fluent setters in PositionedStack added in #1106 (#1110) 2026-11-06 21:26:08 +01:00
slprime 41c2b96c88 Fix NPE in StackInfo.getItemStackWithMinimumDamage for unregistered items (#1109) 2026-11-05 18:19:48 +02:00
slprime 8e1541eab1 Remove creative tab style option for recipe tabs (#1107) 2026-11-04 16:34:56 +02:00
slprime 9411541fd8 Move RecipeTransferRectHandler out of TemplateRecipeHandler and tidy up PositionedStack (#1106) 2026-11-03 20:35:40 +02:00
slprime ae0d6c6108 Add getResultStacks/getAuxiliaryStacks to IRecipeHandler (#1105) 2026-11-03 17:33:07 +02:00
slprime aea4c2866a Stop auto-creating NEI config files, add export buttons instead (#1104) 2026-11-02 22:20:09 +02:00
slprime c1a525594c Fix ConcurrentModificationException when iterating the crafting recipe list (#1103) 2026-11-02 19:38:18 +02:00
slprimeandMartin Robertz afc8a2380b Remove per-world config override (#1094)
Co-authored-by: Martin Robertz <[email protected]>
2026-11-02 18:20:18 +02:00
slprime a0b2430458 Remove hardcoded vanilla block name overrides (#1102) 2026-11-01 21:53:37 +02:00
Armisael5 4dc6be839b Add NEI Recipe Exporting (#1100) 2026-11-01 15:25:25 +02:00
slprime 030795e3c7 Fix NEI plugin loading crash under a SecurityManager (#1099) 2026-10-29 20:59:01 +02:00
slprime 897a22ad17 Run NEI hotkeys before third-party lastKeyTyped handlers (#1097) 2026-10-28 17:37:45 +02:00
5faed21218 Revert missing item count rendering changes from #1044 (#1091)
Co-authored-by: Martin Robertz <[email protected]>
Co-authored-by: slprime <[email protected]>
2026-10-26 00:21:23 +02:00
Worive aa9da2e0a0 Fix NEI initialization aborting while logging plugin errors (#1093) 2026-10-25 17:10:38 +02:00
slprime e9a3a494d5 Add Mouse Binding (#1092) 2026-10-25 02:49:38 +03:00
112 changed files with 2745 additions and 1422 deletions

No files matched your search

+2 -2
View File
@@ -3,9 +3,9 @@ name: Build and test
on:
pull_request:
branches: [ master, main ]
branches: [ master, main, release/** ]
push:
branches: [ master, main ]
branches: [ master, main, release/** ]
jobs:
build-and-test:
+1 -1
View File
@@ -75,7 +75,7 @@ All items that match `filter` will show your information page. See `config/NEI/c
#### 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.
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. The file is not created automatically: use the Export button in the NEI options under Tools > Default Config Files to copy the defaults to `config/NEI`.
## Other items of note:
+100 -46
View File
@@ -2,8 +2,6 @@ package codechicken.nei;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
@@ -12,7 +10,8 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -37,8 +36,10 @@ import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import codechicken.core.ClassDiscoverer;
@@ -50,8 +51,13 @@ import codechicken.nei.api.IConfigureNEI;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.bookmark.tree.GuiCraftingTree;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiUsageRecipe;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.RecipeItemInputHandler;
import codechicken.nei.recipe.StackInfo;
import codechicken.nei.recipe.debug.DebugHandlerWidget;
import cpw.mods.fml.client.CustomModLoadingErrorDisplayException;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
@@ -67,6 +73,22 @@ public class ClientHandler {
private static ClientHandler instance;
private final ArrayList<EntityItem> SMPmagneticItems = new ArrayList<>();
private static final String HANDLER_ORDERING = "handlerordering.csv";
public static final List<String> DEFAULT_CONFIGS = ImmutableList.of(
"serialhandlers.cfg",
"heighthackhandlers.cfg",
"hiddenhandlers.cfg",
"enableautofocus.cfg",
"guicraftablesblacklist.cfg",
"defaultbookmarkcontainers.cfg",
"hiddenitems.cfg",
"guidfilters.cfg",
"informationpages.cfg",
"collapsibleitems.cfg",
"recipeidblacklist.cfg",
"handlers.csv",
"catalysts.csv",
HANDLER_ORDERING);
private World lastworld;
public void addSMPMagneticItem(int i, World world) {
@@ -160,17 +182,30 @@ public class ClientHandler {
StackInfo.loadGuidFilters();
}
public static void loadSettingsFile(String resource, Consumer<Stream<String>> callback) {
loadSettingsFile(resource, (file, writer) -> {
String folder = resource.substring(resource.lastIndexOf(".") + 1);
URL defaultResource = ClientHandler.class.getResource("/assets/nei/" + folder + "/" + resource);
private static URL getDefaultResource(String resource) {
final String folder = resource.substring(resource.lastIndexOf(".") + 1);
return ClientHandler.class.getResource("/assets/nei/" + folder + "/" + resource);
}
if (defaultResource != null) {
try {
IOUtils.copy(defaultResource.openStream(), writer);
} catch (IOException e) {}
}
}, callback);
public static boolean exportDefaultConfig(String resource) {
final File file = new File(NEIClientConfig.configDir, resource);
if (file.exists() && !NEIClientUtils.backupFile(file)) return false;
if (HANDLER_ORDERING.equals(resource)) {
return writeDefaultHandlerOrdering();
}
final URL url = getDefaultResource(resource);
if (url == null) return false;
try {
FileUtils.copyURLToFile(url, file);
NEIClientConfig.logger.info("Exported default '{}' to {}", resource, file);
return true;
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to export default '{}' to file {}", resource, file, e);
return false;
}
}
public static boolean loadSettingsResource(String resource, Consumer<Stream<String>> callback) {
@@ -196,30 +231,24 @@ public class ClientHandler {
return false;
}
public static boolean loadSettingsFile(String resource, BiConsumer<File, FileWriter> createDefault,
Consumer<Stream<String>> callback) {
File file = new File(NEIClientConfig.configDir, resource);
public static boolean loadSettingsFile(String resource, Consumer<Stream<String>> callback) {
final File file = new File(NEIClientConfig.configDir, resource);
final boolean fromJar = !file.exists();
if (!file.exists() && createDefault != null) {
try (FileWriter writer = new FileWriter(file)) {
NEIClientConfig.logger.info("Creating default '{}' {}", resource, file);
createDefault.accept(file, writer);
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to save default '{}' to file {}", resource, file, e);
}
}
try {
final URL url = fromJar ? getDefaultResource(resource) : file.toURI().toURL();
if (url == null) return false;
if (file.exists()) {
try (FileReader reader = new FileReader(file)) {
NEIClientConfig.logger.info("Loading '{}' file {}", resource, file);
try (InputStreamReader reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) {
NEIClientConfig.logger.info("Loading '{}' from {}", resource, fromJar ? "JAR" : file);
callback.accept(
IOUtils.readLines(reader).stream()
.map(line -> line.contains("#") ? line.substring(0, line.indexOf('#')) : line)
.map(String::trim).filter(line -> !line.isEmpty()));
return true;
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to load '{}' file {}", resource, file, e);
}
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to load '{}' file {}", resource, file, e);
}
return false;
@@ -292,26 +321,46 @@ public class ClientHandler {
}
public static void loadHandlerOrdering() {
final String COMMA_DELIMITER = ",";
final String[] defaultHandlerOrdering = {
loadSettingsFile(
HANDLER_ORDERING,
lines -> lines.map(line -> line.split(",")).filter(parts -> parts.length == 2).forEach(parts -> {
String handlerId = parts[0];
int ordering = Integer.parseInt(parts[1]);
NEIClientConfig.handlerOrdering.put(handlerId, ordering);
}));
}
private static boolean writeDefaultHandlerOrdering() {
final File file = new File(NEIClientConfig.configDir, HANDLER_ORDERING);
final List<String> lines = Lists.newArrayList(
"# Each line in this file should either be a comment (starts with '#') or an ordering.",
"# Ordering lines are <handler ID>,<ordering number>.",
"# Handlers will be sorted in order of number ascending, so smaller numbers first.",
"# Any handlers that are missing from this file will be assigned to 0.", "# Negative numbers are fine.",
"# If you delete this file, it will be regenerated with all registered handler IDs.", };
"# Any handlers that are missing from this file keep their own order, 0 for most of them.",
"# Negative numbers are fine.");
final Map<String, Integer> orders = new TreeMap<>();
final List<IRecipeHandler> handlers = new ArrayList<>();
handlers.addAll(GuiCraftingRecipe.craftinghandlers);
handlers.addAll(GuiCraftingRecipe.serialCraftingHandlers);
handlers.addAll(GuiUsageRecipe.usagehandlers);
handlers.addAll(GuiUsageRecipe.serialUsageHandlers);
loadSettingsFile("handlerordering.csv", (file, writer) -> {
List<String> toWrite = Lists.newArrayList(defaultHandlerOrdering);
GuiRecipeTab.handlerMap.keySet().stream().sorted()
.forEach(handlerId -> toWrite.add(String.format("%s" + COMMA_DELIMITER + "0", handlerId)));
try {
IOUtils.writeLines(toWrite, "\n", writer);
} catch (IOException e) {}
}, lines -> lines.map(line -> line.split(COMMA_DELIMITER)).filter(parts -> parts.length == 2).forEach(parts -> {
String handlerId = parts[0];
int ordering = Integer.parseInt(parts[1]);
NEIClientConfig.handlerOrdering.put(handlerId, ordering);
}));
for (IRecipeHandler handler : handlers) {
orders.putIfAbsent(
NEIClientConfig.HANDLER_ID_FUNCTION.apply(handler),
NEIClientConfig.getHandlerOrder(handler));
}
orders.forEach((handlerId, order) -> lines.add(handlerId + "," + order));
try {
NEIClientConfig.logger.info("Creating default '{}' {}", HANDLER_ORDERING, file);
FileUtils.writeLines(file, StandardCharsets.UTF_8.name(), lines, "\n");
return true;
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to save default '{}' to file {}", HANDLER_ORDERING, file, e);
return false;
}
}
public static void loadPluginsList() {
@@ -398,6 +447,11 @@ public class ClientHandler {
if (event.world == mc.theWorld) {
NEIClientConfig.unloadWorld();
ItemMobSpawner.clearEntityReferences();
RecipeItemInputHandler.instance.clearTooltipCache();
RecipeHandlerRef.clearCache();
ItemPanels.itemPanel.craftablesPanel.clearGuiReferences();
DebugHandlerWidget.instance.clearGuiReferences();
NEIController.manager = null;
}
}
@@ -7,8 +7,11 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTBase.NBTPrimitive;
@@ -35,6 +38,7 @@ public class CollapsibleItems {
public ItemFilter filter;
public boolean expanded = false;
public String displayName = "";
public String unlocalizedName;
public void setFilter(String filter) {
this.filter = ItemStackFilterParser.parse(filter.trim());
@@ -46,15 +50,38 @@ public class CollapsibleItems {
this.guid = guid;
}
public String getDisplayName() {
if (this.unlocalizedName != null && !this.unlocalizedName.isEmpty()) {
final String translated = StatCollector.translateToLocal(this.unlocalizedName);
if (!translated.equals(this.unlocalizedName)) {
return translated;
}
}
return this.displayName;
}
public boolean matches(ItemStack stack) {
return this.filter.matches(stack);
}
}
public static class ApiGroup {
private String unlocalizedName;
public ApiGroup setName(String unlocalizedName) {
this.unlocalizedName = unlocalizedName;
return this;
}
}
private static final String STATE_KEY = "collapsibleitems";
protected static File statesFile;
protected static final List<GroupItem> groups = new ArrayList<>();
protected static final List<Supplier<GroupItem>> apiGroups = new CopyOnWriteArrayList<>();
protected static final Map<ItemStack, Integer> cache = new ConcurrentHashMap<>();
private CollapsibleItems() {}
@@ -77,11 +104,57 @@ public class CollapsibleItems {
ClientHandler.loadSettingsFile(
"collapsibleitems.cfg",
lines -> parseFile(lines.collect(Collectors.toCollection(ArrayList::new))));
for (Supplier<GroupItem> apiGroup : CollapsibleItems.apiGroups) {
try {
addGroup(apiGroup.get());
} catch (Exception e) {
NEIClientConfig.logger.error("Failed to add API group", e);
}
}
}
loadStates();
}
public static ApiGroup registerGroup(String filter) {
final ApiGroup apiGroup = new ApiGroup();
CollapsibleItems.apiGroups.add(() -> {
GroupItem group = new GroupItem();
group.setFilter(filter);
group.unlocalizedName = apiGroup.unlocalizedName;
return group;
});
return apiGroup;
}
public static ApiGroup registerGroup(Iterable<ItemStack> items) {
final ApiGroup apiGroup = new ApiGroup();
final ItemStackSet filter = new ItemStackSet();
for (ItemStack stack : items) {
if (stack != null) filter.add(stack.copy());
}
if (filter.isEmpty()) return apiGroup;
final String key = filter.keys().stream()
.map(stack -> Item.itemRegistry.getNameForObject(stack.getItem()) + ":" + stack.getItemDamage())
.sorted().collect(Collectors.joining(","));
final String guid = UUID.nameUUIDFromBytes(key.getBytes()).toString();
CollapsibleItems.apiGroups.add(() -> {
GroupItem group = new GroupItem();
group.setFilter(filter, guid);
group.unlocalizedName = apiGroup.unlocalizedName;
return group;
});
return apiGroup;
}
private static void parseFile(List<String> itemStrings) {
final JsonParser parser = new JsonParser();
GroupItem group = new GroupItem();
@@ -92,17 +165,8 @@ public class CollapsibleItems {
if (itemStr.startsWith("; ")) {
JsonObject settings = parser.parse(itemStr.substring(2)).getAsJsonObject();
if (settings.get("displayName") != null) {
group.displayName = settings.get("displayName").getAsString();
}
if (settings.get("unlocalizedName") != null) {
String unlocalizedName = settings.get("unlocalizedName").getAsString();
String displayName = StatCollector.translateToLocal(unlocalizedName);
if (!displayName.equals(unlocalizedName)) {
group.displayName = displayName;
}
group.unlocalizedName = settings.get("unlocalizedName").getAsString();
}
if (settings.get("expanded") != null) {
@@ -176,7 +240,7 @@ public class CollapsibleItems {
public static String getDisplayName(int groupIndex) {
if (groupIndex < CollapsibleItems.groups.size()) {
return CollapsibleItems.groups.get(groupIndex).displayName;
return CollapsibleItems.groups.get(groupIndex).getDisplayName();
}
return null;
@@ -9,7 +9,6 @@ import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -24,7 +23,6 @@ import net.minecraft.nbt.NBTTagCompound;
import org.apache.commons.io.IOUtils;
import com.google.common.io.Files;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
@@ -34,7 +32,6 @@ import codechicken.nei.api.API;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.ICraftingHandler;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.StackInfo;
@@ -95,7 +92,7 @@ public class FavoriteRecipes {
for (int i = 0; i < handler.numRecipes(); i++) {
RecipeHandlerRef ref = null;
for (PositionedStack result : getOutputs(handler, i)) {
for (PositionedStack result : handler.getResultStacks(i)) {
ItemStack stack = StackInfo.getItemStackWithMinimumDamage(result.items);
final FastKey key = new FastKey(stack);
@@ -130,11 +127,6 @@ public class FavoriteRecipes {
updateSubsetVisibility();
}
protected List<PositionedStack> getOutputs(IRecipeHandler handler, int recipeIndex) {
final PositionedStack pStackResult = handler.getResultStack(recipeIndex);
return pStackResult != null ? Collections.singletonList(pStackResult) : handler.getOtherStacks(recipeIndex);
}
};
public static final String FAVORITE_ITEM = "FavoriteItem";
@@ -244,14 +236,7 @@ public class FavoriteRecipes {
if (hasErrors) {
try {
Files.copy(
favoriteFile,
new File(favoriteFile.getAbsolutePath() + ".backup-" + System.currentTimeMillis()));
NEIClientConfig.logger.info("Backed up invalid favorite file to {}", favoriteFile);
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to backup invalid favorite file to {}", favoriteFile, e);
}
NEIClientUtils.backupFile(favoriteFile);
save();
}
@@ -13,7 +13,6 @@ import java.util.stream.IntStream;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fluids.FluidContainerRegistry;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
@@ -24,6 +23,7 @@ import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.ICraftingHandler;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.Recipe;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeInfo;
import codechicken.nei.recipe.StackInfo;
@@ -137,7 +137,11 @@ public class ItemCraftablesPanel
@Override
public void execute() {
pendingResult.set(generateCraftables(ItemCraftablesPanel.this.pendingSnapshot));
final CraftablesSnapshot snapshot = ItemCraftablesPanel.this.pendingSnapshot;
if (snapshot != null) {
pendingResult.set(generateCraftables(snapshot));
}
}
};
@@ -298,6 +302,11 @@ public class ItemCraftablesPanel
}
}
public void clearGuiReferences() {
this.lastGuiContainer = null;
this.pendingSnapshot = null;
}
private GuiContainer getGuiContainer() {
final GuiContainer firstGui = NEIClientUtils.getGuiContainer();
return (firstGui instanceof GuiRecipe<?>gui) ? gui.firstGui : firstGui;
@@ -378,48 +387,19 @@ public class ItemCraftablesPanel
}
private static ItemStack getCheapResultItem(IRecipeHandler handler, int recipeIndex) {
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
if (resultStack != null) {
return resultStack.item;
}
final List<PositionedStack> otherStacks = handler.getOtherStacks(recipeIndex);
for (PositionedStack otherStack : otherStacks) {
if (!FluidContainerRegistry.isContainer(otherStack.items[0])
|| StackInfo.getFluid(otherStack.items[0]) != null) {
return otherStack.item;
}
}
return otherStacks.isEmpty() ? null : otherStacks.get(0).item;
final PositionedStack result = Recipe
.getMainResult(handler.getResultStacks(recipeIndex), pStack -> pStack.items[0]);
return result != null ? result.item : null;
}
private static ItemStack getCheapResult(IRecipeHandler handler, int recipeIndex) {
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
final List<PositionedStack> results = resultStack != null ? Collections.singletonList(resultStack)
: handler.getOtherStacks(recipeIndex);
final List<PositionedStack> results = handler.getResultStacks(recipeIndex);
if (results.isEmpty()) {
return null;
}
ItemStack stack = null;
if (results.size() > 1) {
for (PositionedStack result : results) {
if (!FluidContainerRegistry.isContainer(result.item) || StackInfo.getFluid(result.item) != null) {
stack = result.item;
break;
}
}
}
if (stack == null) {
stack = results.get(0).item;
}
final ItemStack stack = Recipe.getMainResult(results, pStack -> pStack.items[0]).item;
int stackSize = 0;
for (PositionedStack result : results) {
@@ -207,6 +207,33 @@ public class ItemList {
return filters;
}
public static ItemStack[] expandPermutations(ItemStack[] items) {
List<ItemStack> stacks = new ArrayList<>();
for (ItemStack item : items) {
if (item == null || item.getItem() == null) continue;
if (item.getItemDamage() == Short.MAX_VALUE) {
List<ItemStack> permutations = itemMap.get(item.getItem());
if (!permutations.isEmpty()) {
for (ItemStack stack : permutations) {
ItemStack toAdd = stack.copy();
toAdd.stackSize = item.stackSize;
stacks.add(toAdd);
}
} else {
ItemStack base = new ItemStack(item.getItem(), item.stackSize);
base.stackTagCompound = item.stackTagCompound;
stacks.add(base);
}
continue;
}
stacks.add(item.copy());
}
return stacks.toArray(new ItemStack[0]);
}
public static ForkJoinPool forkJoinPool;
static {
@@ -36,7 +36,6 @@ import cpw.mods.fml.common.FMLLog;
public class ItemListLoader {
private static final HashSet<Item> erroredItems = new HashSet<>();
private static final HashSet<String> stackTraces = new HashSet<>();
private static Map<ItemStack, Integer> ordering = new HashMap<>();
private ItemListLoader() {}
@@ -60,11 +59,10 @@ public class ItemListLoader {
} catch (TimeoutException t) {
throw t;
} catch (Throwable t) {
NEIServerUtils.logOnce(
t,
stackTraces,
NEIClientUtils.errorOnce(
"ItemListLoader: " + item.toString(),
"Ommiting " + item + ":" + damage + " " + item.getClass().getSimpleName(),
item.toString());
t);
}
}
}
@@ -118,14 +118,7 @@ public class ItemSorter implements Comparator<ItemStack> {
return name1.compareTo(name2);
});
tag.getTag("inventory.itemsort").setDefaultValue(getSaveString(list));
API.addOption(new OptionOpenGui("inventory.itemsort", GuiItemSorter.class) {
@Override
public void useGlobals() {
super.useGlobals();
list = fromSaveString(activeTag().getValue());
}
});
API.addOption(new OptionOpenGui("inventory.itemsort", GuiItemSorter.class));
}
public static String getSaveString(List<SortEntry> list) {
@@ -219,6 +219,11 @@ public class ItemZoom extends Widget implements IContainerInputHandler {
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyID) {
return false;
}
@Override
public boolean onHotkeyPressed(GuiContainer gui) {
if (KeyManager.isHashDown("itemzoom.toggle", NEIClientUtils.SHIFT_HASH)) {
NEIClientConfig.getSetting("inventory.itemzoom.enabled")
+46 -4
View File
@@ -4,6 +4,7 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.StringJoiner;
import java.util.function.BooleanSupplier;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
@@ -29,6 +30,7 @@ public class KeyManager {
private static final Map<String, KeyBinding> keyBindings = new HashMap<>();
private static final Map<String, String> keyAliases = new HashMap<>();
private static volatile boolean pendingOptionsReload = false;
private static int clickedButton = -1;
static {
// Aliases for legacy keybinds
@@ -73,7 +75,8 @@ public class KeyManager {
final int keyCode = binding.getKeyCode();
if (keyCode < 0) {
return Mouse.isButtonDown(keyCode + 100);
final int button = keyCode + 100;
return Mouse.isButtonDown(button) || button == clickedButton;
} else if (keyCode > Keyboard.KEY_NONE && keyCode < Keyboard.KEYBOARD_SIZE) {
return Keyboard.isKeyDown(keyCode);
}
@@ -82,6 +85,40 @@ public class KeyManager {
return false;
}
/**
* Whether any NEI key binding is assigned to the given mouse button.
*/
public static boolean isMouseBound(int button) {
for (KeyBinding binding : keyBindings.values()) {
if (binding.getKeyCode() == button - 100) {
return true;
}
}
return false;
}
/**
* Runs a hotkey handler for a click of the given mouse button if the button is bound to any NEI key binding. The
* button is reported as down by {@link #isKeyDown} for the duration of the call, because the LWJGL button state can
* already be released when a quick click is processed.
*
* @return true if the click was consumed by the handler
*/
public static boolean handleMouseHotkey(int button, BooleanSupplier handler) {
if (!isMouseBound(button)) {
return false;
}
final int previous = clickedButton;
clickedButton = button;
try {
return handler.getAsBoolean();
} finally {
clickedButton = previous;
}
}
public static boolean isHashDown(String ident) {
return isHashDown(ident, 0);
}
@@ -107,18 +144,23 @@ public class KeyManager {
public static String getKeyName(String ident, int meta) {
final int keyCode = getKeyCode(ident);
return keyCode == Keyboard.KEY_NONE ? null : NEIKeyboardUtils.getKeyName(keyCode + meta);
return keyCode == Keyboard.KEY_NONE ? null : NEIKeyboardUtils.getKeyName(keyCode, meta);
}
public static String getKeyName(String ident, int meta, int mouseBind) {
final int keyCode = getKeyCode(ident);
return keyCode == Keyboard.KEY_NONE && mouseBind == NEIMouseUtils.MOUSE_BTN_NONE ? null
: getKeyName(keyCode + meta, mouseBind);
: getKeyName(keyCode, meta, mouseBind);
}
public static String getKeyName(int keyBind, int mouseBind) {
final int keyCode = NEIKeyboardUtils.unhash(keyBind);
return getKeyName(keyCode, keyBind - keyCode, mouseBind);
}
private static String getKeyName(int keyCode, int hash, int mouseBind) {
StringJoiner keyText = new StringJoiner(" + ");
String keyHash = keyBind == Keyboard.KEY_NONE ? "" : NEIKeyboardUtils.getKeyName(keyBind);
String keyHash = keyCode == Keyboard.KEY_NONE && hash == 0 ? "" : NEIKeyboardUtils.getKeyName(keyCode, hash);
String mouseHash = mouseBind == NEIMouseUtils.MOUSE_BTN_NONE ? "" : NEIMouseUtils.getKeyName(mouseBind);
if (!keyHash.isEmpty()) {
@@ -220,6 +220,20 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyID) {
if (isEnabled() && !isHidden()) {
for (Widget widget : controlWidgets) {
if (inputFocused == null) {
widget.lastKeyTyped(keyID, keyChar);
}
}
}
return false;
}
@Override
public boolean onHotkeyPressed(GuiContainer gui) {
if (KeyManager.isKeyDown("gui.hide")) {
toggleBooleanSetting("inventory.hidden");
return true;
@@ -234,8 +248,12 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
return true;
}
if (isEnabled() && !isHidden()) {
for (Widget widget : controlWidgets) if (inputFocused == null) widget.lastKeyTyped(keyID, keyChar);
if (isEnabled() && !isHidden() && inputFocused == null) {
for (Widget widget : controlWidgets) {
if (widget.onHotkeyPressed()) {
return true;
}
}
}
return false;
@@ -427,14 +445,14 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
if (!rightclick) {
if (Keyboard.getEventKeyState() && getIsAccessibleControlEventKey()
&& NEIClientConfig.canChangeCheatMode()) {
NEIClientConfig.cycleSetting("inventory.cheatmode", 3);
NEIClientConfig.setCheatMode((NEIClientConfig.getCheatMode() + 1) % 3);
} else {
if (Keyboard.getEventKeyState() && (Keyboard.getEventKey() == Keyboard.KEY_LSHIFT
|| Keyboard.getEventKey() == Keyboard.KEY_RSHIFT)) {
GuiRecipeTab.loadHandlerInfo();
RecipeCatalysts.loadCatalystInfo();
}
getOptionList().openGui(getGuiContainer(), false);
getOptionList().openGui(getGuiContainer());
}
return true;
}
@@ -9,14 +9,15 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.StatCollector;
import net.minecraft.world.storage.SaveFormatComparator;
import net.minecraftforge.common.MinecraftForge;
@@ -37,7 +38,6 @@ import codechicken.lib.config.ConfigTag;
import codechicken.lib.config.ConfigTagParent;
import codechicken.nei.api.API;
import codechicken.nei.api.GuiInfo;
import codechicken.nei.api.IConfigureNEI;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.api.NEIInfo;
import codechicken.nei.config.ConfigSet;
@@ -45,7 +45,9 @@ import codechicken.nei.config.GuiHighlightTips;
import codechicken.nei.config.GuiNEIOptionList;
import codechicken.nei.config.GuiOptionList;
import codechicken.nei.config.GuiPanelSettings;
import codechicken.nei.config.OptionButton;
import codechicken.nei.config.OptionCycled;
import codechicken.nei.config.OptionExportConfig;
import codechicken.nei.config.OptionGamemodes;
import codechicken.nei.config.OptionIntegerField;
import codechicken.nei.config.OptionList;
@@ -56,7 +58,8 @@ import codechicken.nei.config.OptionToggleButtonBoubs;
import codechicken.nei.config.OptionUtilities;
import codechicken.nei.config.preset.GuiPresetList;
import codechicken.nei.event.NEIConfigsLoadedEvent;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.moboverlay.MobOverlayScan;
import codechicken.nei.moboverlay.MobOverlayStyle;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.InformationHandler;
import codechicken.nei.recipe.RecipeCatalysts;
@@ -69,6 +72,7 @@ public class NEIClientConfig {
private static boolean pluginNEIConfigLoaded;
private static boolean enabledOverride;
private static String worldPath;
private static final String CHEAT_MODE_KEY = "cheatmode";
public static Logger logger = LogManager.getLogger("NotEnoughItems");
public static File configDir = new File(CommonUtils.getMinecraftDir(), "config/NEI/");
@@ -157,6 +161,16 @@ public class NEIClientConfig {
.getIntValue(-1);
API.addOption(new OptionCycled("inventory.cheatmode", 3) {
@Override
public int value() {
return getCheatMode();
}
@Override
public void setValue(int value) {
setCheatMode(value);
}
@Override
public boolean optionValid(int index) {
return getLockedMode() == -1 || getLockedMode() == index && NEIInfo.isValidMode(index);
@@ -245,10 +259,6 @@ public class NEIClientConfig {
.getBooleanValue(true);
API.addOption(new OptionToggleButton("inventory.guirecipe.cycledIngredientsTooltip", true));
tag.getTag("inventory.guirecipe.creativeTabStyle").setComment("Creative or JEI style tabs")
.getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.guirecipe.creativeTabStyle", true));
tag.getTag("inventory.guirecipe.itemPresenceOverlay").setComment("Item presence overlay on ?-hover")
.getIntValue(1);
API.addOption(new OptionCycled("inventory.guirecipe.itemPresenceOverlay", 3, true));
@@ -435,6 +445,45 @@ public class NEIClientConfig {
tag.getTag("world.overlays.lock").getBooleanValue(true);
API.addOption(new OptionToggleButton("world.overlays.lock", true));
tag.getTag("world.overlays.mobOverlayStyle")
.setComment("Mark on each block of the mob spawn overlay: " + MobOverlayStyle.formatAllNames())
.getValue(MobOverlayStyle.DEFAULT.formatName());
API.addOption(new OptionCycled("world.overlays.mobOverlayStyle", MobOverlayStyle.values().length, true) {
@Override
public int value() {
return MobOverlayStyle.parseStyle(getTag().getValue()).ordinal();
}
@Override
public void setValue(int value) {
getTag().setValue(MobOverlayStyle.values()[value].formatName());
}
@Override
public String getButtonText() {
final MobOverlayStyle style = MobOverlayStyle.parseStyle(getTag().getValue());
return StatCollector.translateToLocal(style.getLangKey());
}
});
tag.getTag("world.overlays.mobOverlayRadius")
.setComment("Blocks scanned on each side of the player by the mob spawn overlay")
.getIntValue(MobOverlayScan.DEFAULT_RADIUS);
API.addOption(
new OptionIntegerField(
"world.overlays.mobOverlayRadius",
MobOverlayScan.MIN_RADIUS,
MobOverlayScan.MAX_RADIUS));
tag.getTag("world.overlays.mobOverlayVerticalRadius")
.setComment("Blocks scanned above and below the player by the mob spawn overlay")
.getIntValue(MobOverlayScan.DEFAULT_VERTICAL_RADIUS);
API.addOption(
new OptionIntegerField(
"world.overlays.mobOverlayVerticalRadius",
MobOverlayScan.MIN_VERTICAL_RADIUS,
MobOverlayScan.MAX_VERTICAL_RADIUS));
tag.getTag("inventory.itempanelScale").getIntValue(100);
ItemsPanelGrid.updateScale();
API.addOption(new OptionIntegerField("inventory.itempanelScale", 1, 1000) {
@@ -516,29 +565,32 @@ public class NEIClientConfig {
});
tag.getTag("tools.handler_load_from_config").setComment("ADVANCED: Load handlers from config")
.getBooleanValue(false);
API.addOption(new OptionToggleButton("tools.handler_load_from_config", true) {
API.addOption(
new OptionButton(
"tools.export_configs.all",
"tools.export_configs.all",
null,
"tools.export_configs.all.tip") {
@Override
public boolean onClick(int button) {
super.onClick(button);
GuiRecipeTab.loadHandlerInfo();
return true;
}
});
@Override
public String getButtonText() {
return translateN("tools.export_configs.button");
}
tag.getTag("tools.catalyst_load_from_config").setComment("ADVANCED: Load catalysts from config")
.getBooleanValue(false);
API.addOption(new OptionToggleButton("tools.catalyst_load_from_config", true) {
@Override
public boolean onClick(int button) {
int exported = 0;
@Override
public boolean onClick(int button) {
super.onClick(button);
RecipeCatalysts.loadCatalystInfo();
return true;
}
});
for (String resource : ClientHandler.DEFAULT_CONFIGS) {
if (ClientHandler.exportDefaultConfig(resource)) exported++;
}
NEIClientUtils.printChatMessage(
new ChatComponentTranslation("nei.options.tools.export_configs.all.done", exported));
return true;
}
});
ClientHandler.DEFAULT_CONFIGS.forEach(fileName -> API.addOption(new OptionExportConfig(fileName)));
setDefaultKeyBindings();
}
@@ -547,23 +599,18 @@ public class NEIClientConfig {
OptionList.setOptionList(new OptionList("nei.options") {
@Override
public ConfigSet globalConfigSet() {
public ConfigSet configSet() {
return global;
}
@Override
public ConfigSet worldConfigSet() {
return world;
}
@Override
public OptionList configBase() {
return this;
}
@Override
public GuiOptionList getGui(GuiScreen parent, OptionList list, boolean world) {
return new GuiNEIOptionList(parent, list, world);
public GuiOptionList getGui(GuiScreen parent, OptionList list) {
return new GuiNEIOptionList(parent, list);
}
});
}
@@ -930,15 +977,15 @@ public class NEIClientConfig {
specificDir.mkdirs();
}
world = new ConfigSet(new File(specificDir, "NEI.dat"), new ConfigFile(new File(specificDir, "NEI.cfg")));
world = new ConfigSet(new File(specificDir, "NEI.dat"));
if (newWorld && Minecraft.getMinecraft().isSingleplayer() && getLockedMode() == -1) {
final int globalMode = global.config.getTag("inventory.cheatmode").getIntValue();
final int globalMode = getCheatMode();
final int worldMode = Math
.min(globalMode, NEIClientUtils.mc().playerController.isInCreativeMode() ? 2 : 0);
if (worldMode != globalMode) {
world.config.getTag("inventory.cheatmode").setIntValue(worldMode);
setCheatMode(worldMode);
}
}
@@ -952,9 +999,6 @@ public class NEIClientConfig {
}
private static void onWorldLoad() {
world.config.setComment(
"World based configuration of NEI.\nMost of these options can be changed ingame.\nDeleting any element will restore it to it's default value");
setWorldDefaults();
creativeInv = new ItemStack[54];
LayoutManager.searchField.setText(getSearchExpression());
@@ -1026,20 +1070,11 @@ public class NEIClientConfig {
@Override
public void run() {
final Stream<Class<?>> stream = NEIClientConfig.getBooleanSetting("loadPluginsInParallel")
? NEIClientConfig.pluginsList.parallelStream()
: NEIClientConfig.pluginsList.stream();
stream.forEach(clazz -> {
try {
IConfigureNEI config = (IConfigureNEI) clazz.getConstructor().newInstance();
config.loadConfig();
NEIModContainer.plugins.add(config);
logger.debug("Loaded {}", clazz.getName());
} catch (Throwable e) {
logger.error("Failed to Load {}", clazz.getName(), e);
}
});
PluginLoader.load(
NEIClientConfig.pluginsList,
NEIClientConfig.getBooleanSetting("loadPluginsInParallel"),
NEIModContainer.plugins,
logger);
KeyManager.requestOptionsReload();
RecipeCatalysts.loadCatalystInfo();
@@ -1069,14 +1104,8 @@ public class NEIClientConfig {
}
public static boolean isWorldSpecific(String setting) {
if (world == null) return false;
ConfigTag tag = world.config.getTag(setting, false);
return tag != null && tag.value != null;
}
public static ConfigTag getSetting(String s) {
return isWorldSpecific(s) ? world.config.getTag(s) : global.config.getTag(s);
return global.config.getTag(s);
}
public static boolean getBooleanSetting(String s) {
@@ -1165,10 +1194,6 @@ public class NEIClientConfig {
return getIntSetting("inventory.guirecipe.jeiStyleRecipeCatalyst");
}
public static boolean useCreativeTabStyle() {
return getBooleanSetting("inventory.guirecipe.creativeTabStyle");
}
public static boolean ignorePotionOverlap() {
return getBooleanSetting("inventory.bookmarks.ignorePotionOverlap");
}
@@ -1201,14 +1226,6 @@ public class NEIClientConfig {
return mainNEIConfigLoaded && pluginNEIConfigLoaded;
}
public static boolean loadHandlersFromJar() {
return !getBooleanSetting("tools.handler_load_from_config");
}
public static boolean loadCatalystsFromJar() {
return !getBooleanSetting("tools.catalyst_load_from_config");
}
public static boolean isProfileRecipeEnabled() {
return NEIClientConfig.getBooleanSetting("inventory.guirecipe.profile");
}
@@ -1222,12 +1239,18 @@ public class NEIClientConfig {
}
public static int getCheatMode() {
if (world != null && world.nbt.hasKey(CHEAT_MODE_KEY)) return world.nbt.getInteger(CHEAT_MODE_KEY);
return getIntSetting("inventory.cheatmode");
}
public static void setCheatMode(int mode) {
if (world != null) world.nbt.setInteger(CHEAT_MODE_KEY, mode);
else setIntSetting("inventory.cheatmode", mode);
}
public static boolean canChangeCheatMode() {
if (getLockedMode() != -1) {
setIntSetting("inventory.cheatmode", getLockedMode());
setCheatMode(getLockedMode());
return false;
}
@@ -9,9 +9,12 @@ import static codechicken.nei.NEIClientConfig.invCreativeMode;
import static codechicken.nei.NEIClientConfig.world;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.MessageFormat;
@@ -26,6 +29,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import net.minecraft.client.Minecraft;
@@ -64,6 +68,8 @@ import codechicken.nei.util.NEIKeyboardUtils;
public class NEIClientUtils extends NEIServerUtils {
private static final Set<String> REPORTED = ConcurrentHashMap.newKeySet();
public static class Alignment {
public final int x, y;
@@ -639,4 +645,29 @@ public class NEIClientUtils extends NEIServerUtils {
if (!isAlphaTest) GL11.glDisable(GL11.GL_ALPHA_TEST);
}
public static void errorOnce(String key, String message, Throwable error) {
if (REPORTED.add(key)) {
NEIClientConfig.logger.error(message, error);
}
}
public static void warnOnce(String key, String message) {
if (REPORTED.add(key)) {
NEIClientConfig.logger.warn(message);
}
}
public static boolean backupFile(File file) {
final File backup = new File(file.getAbsolutePath() + ".backup-" + System.currentTimeMillis());
try {
Files.copy(file.toPath(), backup.toPath());
NEIClientConfig.logger.info("Backed up file {} to {}", file, backup);
return true;
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to backup file {} to {}", file, backup, e);
return false;
}
}
}
@@ -4,12 +4,9 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
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;
@@ -509,20 +506,6 @@ public class NEIServerUtils {
return sb;
}
public static void logOnce(Throwable t, Set<String> stackTraces, String message) {
logOnce(t, stackTraces, message, "");
}
public static void logOnce(Throwable t, Set<String> stackTraces, String message, String identifier) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
String stackTrace = identifier + sw.toString();
if (!stackTraces.contains(stackTrace)) {
NEIServerConfig.logger.error(message, t);
stackTraces.add(stackTrace);
}
}
public static ItemStack getModdedItem(String itemId, String nbtString) {
int meta = -1;
ItemStack itemStack;
@@ -305,7 +305,7 @@ public abstract class PanelWidget<T extends ItemsGrid<? extends ItemsGridSlot, ?
}
@Override
public boolean handleKeyPress(int keyID, char keyChar) {
public boolean onHotkeyPressed() {
final Point mouse = GuiDraw.getMousePosition();
if (!contains(mouse.x, mouse.y)) return false;
@@ -0,0 +1,66 @@
package codechicken.nei;
import java.util.Collection;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import org.apache.logging.log4j.Logger;
import codechicken.nei.api.IConfigureNEI;
final class PluginLoader {
private PluginLoader() {}
static void load(Collection<Class<?>> plugins, boolean parallel, Collection<IConfigureNEI> loadedPlugins,
Logger logger) {
if (parallel && canSetContextClassLoader()) {
// Common pool workers use the system loader, which cannot see FML-only classes (old Log4j then throws
// while logging a plugin's stack frames), and under a SecurityManager they forbid changing it.
final ClassLoader classLoader = IConfigureNEI.class.getClassLoader();
final ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), owner -> {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(owner);
worker.setContextClassLoader(classLoader);
return worker;
}, null, false);
try {
pool.submit(() -> plugins.parallelStream().forEach(clazz -> load(clazz, loadedPlugins, logger))).join();
} finally {
pool.shutdown();
}
} else {
if (parallel) {
logger.warn("Context class loader cannot be changed, loading plugins sequentially");
}
plugins.forEach(clazz -> load(clazz, loadedPlugins, logger));
}
}
private static void load(Class<?> clazz, Collection<IConfigureNEI> loadedPlugins, Logger logger) {
try {
IConfigureNEI config = (IConfigureNEI) clazz.getConstructor().newInstance();
config.loadConfig();
loadedPlugins.add(config);
logger.debug("Loaded {}", clazz.getName());
} catch (Throwable e) {
logger.error("Failed to Load {}", clazz.getName(), e);
}
}
private static boolean canSetContextClassLoader() {
final SecurityManager securityManager = System.getSecurityManager();
if (securityManager == null) {
return true;
}
try {
securityManager.checkPermission(new RuntimePermission("setContextClassLoader"));
return true;
} catch (SecurityException e) {
return false;
}
}
}
@@ -23,6 +23,11 @@ public class PopupInputHandler implements IContainerInputHandler {
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyID) {
return false;
}
@Override
public boolean onHotkeyPressed(GuiContainer gui) {
if (KeyManager.isHashDown("gui.enchant") && canPerformAction("enchant")) {
NEICPH.sendOpenEnchantmentWindow();
@@ -14,12 +14,8 @@ import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.oredict.OreDictionary;
import org.lwjgl.opengl.GL11;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.item.FluidDrawer;
import codechicken.nei.item.FluidDrawer.FillDirection;
@@ -70,31 +66,11 @@ public class PositionedStack implements Cloneable {
public void generatePermutations() {
if (permutated) return;
List<ItemStack> stacks = new ArrayList<>();
for (ItemStack item : items) {
if (item == null || item.getItem() == null) continue;
items = ItemList.expandPermutations(items);
if (item.getItemDamage() == Short.MAX_VALUE) {
List<ItemStack> permutations = ItemList.itemMap.get(item.getItem());
if (!permutations.isEmpty()) {
for (ItemStack stack : permutations) {
ItemStack toAdd = stack.copy();
toAdd.stackSize = item.stackSize;
stacks.add(toAdd);
}
} else {
ItemStack base = new ItemStack(item.getItem(), item.stackSize);
base.stackTagCompound = item.stackTagCompound;
stacks.add(base);
}
continue;
}
stacks.add(item.copy());
if (items.length == 0) {
items = new ItemStack[] { new ItemStack(Blocks.fire) };
}
items = stacks.toArray(new ItemStack[0]);
if (items.length == 0) items = new ItemStack[] { new ItemStack(Blocks.fire) };
permutated = true;
setPermutationToRender(0);
@@ -263,7 +239,6 @@ public class PositionedStack implements Cloneable {
protected int capacity = 0;
protected boolean flowingTexture = false;
protected FillDirection fillDirection = FillDirection.AUTO;
protected DrawableResource overlay;
private ItemStack cachedFluidItem;
private FluidStack cachedFluidStack;
@@ -310,19 +285,6 @@ public class PositionedStack implements Cloneable {
return this;
}
public DrawableResource getOverlay() {
return this.overlay;
}
public Tank setOverlay(String resourceLocation, int u, int v, int width, int height) {
return setOverlay(new DrawableBuilder(resourceLocation, u, v, width, height).build());
}
public Tank setOverlay(DrawableResource overlay) {
this.overlay = overlay;
return this;
}
protected FluidStack getFluidStack() {
if (this.item != this.cachedFluidItem) {
@@ -339,7 +301,7 @@ public class PositionedStack implements Cloneable {
final List<String> customTooltip = super.getTooltip();
final FluidStack fluidStack = getFluidStack();
if (fluidStack != null) {
if (fluidStack != null && this.capacity > 0) {
tooltip.add(
EnumChatFormatting.GRAY + NEIClientUtils
.translate("recipe.fluid.tank.amount", NEIClientUtils.formatFluid(fluidStack.amount)));
@@ -370,15 +332,6 @@ public class PositionedStack implements Cloneable {
fluidStack,
this.flowingTexture,
this.fillDirection);
if (this.overlay != null) {
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_LIGHTING);
this.overlay.draw(this.relx, this.rely);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_BLEND);
}
}
}
}
@@ -89,19 +89,19 @@ public class PresetsList {
return false;
}
final PositionedStack result = handler.getResultStack(recipeIndex);
final List<PositionedStack> outputs = handler.getResultStacks(recipeIndex);
if (result != null && matchPositionedStack(result)) {
if (matchPositionedStack(outputs, true)) {
return true;
}
final List<PositionedStack> others = handler.getOtherStacks(recipeIndex);
final List<PositionedStack> auxiliaries = handler.getAuxiliaryStacks(recipeIndex);
if (!others.isEmpty() && matchPositionedStack(others, true)) {
if (matchPositionedStack(auxiliaries, true)) {
return true;
}
return result == null && others.isEmpty();
return outputs.isEmpty() && auxiliaries.isEmpty();
}
private boolean matchPositionedStack(List<PositionedStack> items, boolean dir) {
@@ -24,7 +24,13 @@ public abstract class RecipeSearchField extends TextField implements ItemFilterP
field.setMaxStringLength(maxSearchLength);
field.setCursorPositionZero();
field.setSkipRecipeTokens(true);
((SearchTextFormatter) field.getFormatter()).setContextToken("<>");
field.setFormatter(new SearchTextFormatter(SearchField.searchParser) {
@Override
protected String getContextToken() {
return this.searchParser.getRecipeContextTokens();
}
});
this.field = field;
}
@@ -48,15 +54,19 @@ public abstract class RecipeSearchField extends TextField implements ItemFilterP
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
public boolean onHotkeyPressed() {
if (focused() && KeyManager.isKeyDown("gui.getprevioussearch")) {
handleNavigateHistory(TextHistory.Direction.PREVIOUS);
return true;
}
if (focused() && KeyManager.isKeyDown("gui.getnextsearch")) {
handleNavigateHistory(TextHistory.Direction.NEXT);
return true;
}
return false;
}
@Override
@@ -227,18 +227,24 @@ public class SearchField extends TextField implements ItemFilterProvider {
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
public boolean onHotkeyPressed() {
if (isVisible() && KeyManager.isHashDown("gui.search")) {
setFocus(true);
return true;
}
if (focused() && KeyManager.isKeyDown("gui.getprevioussearch")) {
handleNavigateHistory(TextHistory.Direction.PREVIOUS);
return true;
}
if (focused() && KeyManager.isKeyDown("gui.getnextsearch")) {
handleNavigateHistory(TextHistory.Direction.NEXT);
return true;
}
return false;
}
@Override
@@ -20,6 +20,10 @@ public class SearchTextFormatter implements TextFormatter {
this.contextToken = contextToken;
}
protected String getContextToken() {
return this.contextToken;
}
@Override
public String format(String text) {
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
@@ -32,13 +36,14 @@ public class SearchTextFormatter implements TextFormatter {
return SearchExpressionUtils.visitRecipeSearchExpression(text, visitor);
} else {
final StringBuilder formattedText = new StringBuilder();
final String contextToken = getContextToken();
for (String[] orQuery : searchParser.splitByDelimiters(text, "|", false)) {
final String filterText = orQuery[1];
formattedText.append(SearchExpressionUtils.HIGHLIGHTS.OR + orQuery[0]);
for (String[] contextPart : searchParser.splitByDelimiters(filterText, this.contextToken, false)) {
for (String[] contextPart : searchParser.splitByDelimiters(filterText, contextToken, false)) {
if (!contextPart[0].isEmpty()) {
formattedText.append(
@@ -74,7 +79,11 @@ public class SearchTextFormatter implements TextFormatter {
formattedText.append(SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\"");
}
if (!token.rawText.isEmpty()) {
if (patternMode == 1 && !token.quotes && isRegexToken(token.rawText)) {
formattedText.append(SearchExpressionUtils.HIGHLIGHTS.REGEX + token.rawText.substring(0, 2))
.append(tokenColor + token.rawText.substring(2, token.rawText.length() - 1))
.append(SearchExpressionUtils.HIGHLIGHTS.REGEX + "/");
} else if (!token.rawText.isEmpty()) {
formattedText.append(tokenColor + token.rawText);
}
@@ -93,4 +102,8 @@ public class SearchTextFormatter implements TextFormatter {
return formattedText.toString();
}
}
private static boolean isRegexToken(String search) {
return search.length() >= 3 && search.regionMatches(true, 0, "r/", 0, 2) && search.endsWith("/");
}
}
@@ -194,6 +194,7 @@ public class SearchTokenParser {
if (patternMode != 3) {
return this.filtersCache.computeIfAbsent(skipRecipeTokens + ":" + filterText, _t -> {
final List<ItemFilter> searchTokens = new ArrayList<>();
final String contextTokens = skipRecipeTokens ? getRecipeContextTokens() : "";
for (String[] subQuery : splitByDelimiters(filterText, "|", false)) {
@@ -203,9 +204,10 @@ public class SearchTokenParser {
final List<ItemFilter> tokens = new ArrayList<>();
for (String[] contextQuery : splitByDelimiters(subQuery[1], skipRecipeTokens ? "<>" : "", false)) {
for (String[] contextQuery : splitByDelimiters(subQuery[1], contextTokens, false)) {
if (contextQuery[1].isEmpty() || contextQuery[0].startsWith(">")) {
if (contextQuery[1].isEmpty() || contextQuery[0].startsWith(">")
|| contextQuery[0].startsWith("^")) {
continue;
}
@@ -277,7 +279,7 @@ public class SearchTokenParser {
final List<IRecipeFilter> contextList = new ArrayList<>();
for (String[] contextQuery : splitByDelimiters(orQueryPart[1], "<>", false)) {
for (String[] contextQuery : splitByDelimiters(orQueryPart[1], getRecipeContextTokens(), false)) {
if (contextQuery[1].isEmpty()) {
continue;
@@ -436,7 +438,9 @@ public class SearchTokenParser {
final List<String[]> tokens = new ArrayList<>();
final int length = input.length();
final boolean regexTokens = NEIClientConfig.getIntSetting("inventory.search.patternMode") == 1;
boolean insideQuotes = false;
boolean insideRegex = false;
String token = "";
int lastEnd = 0;
int index = 0;
@@ -444,6 +448,15 @@ public class SearchTokenParser {
while (index < length) {
final char ch = input.charAt(index);
if (insideRegex) {
if (ch == '/' && input.charAt(index - 1) != '\\') {
insideRegex = false;
}
index++;
continue;
}
if (!insideQuotes && delimiters.indexOf(ch) >= 0
&& (!space || index == 0 || input.charAt(index - 1) == ' ' || lastEnd == index)) {
@@ -464,6 +477,9 @@ public class SearchTokenParser {
if (ch == '"' && (index == 0 || input.charAt(index - 1) != '\\')) {
insideQuotes = !insideQuotes;
} else if (regexTokens && !insideQuotes && isRegexStart(input, index)) {
insideRegex = true;
index++;
}
index++;
@@ -474,6 +490,12 @@ public class SearchTokenParser {
return tokens;
}
private static boolean isRegexStart(String input, int index) {
return Character.toLowerCase(input.charAt(index)) == 'r' && index + 1 < input.length()
&& input.charAt(index + 1) == '/'
&& (index == 0 || !Character.isLetterOrDigit(input.charAt(index - 1)));
}
public String getPrefixes() {
if (this.prefixes == null) {
final StringBuilder builder = new StringBuilder();
@@ -490,6 +512,11 @@ public class SearchTokenParser {
return this.prefixes;
}
public String getRecipeContextTokens() {
// '^' is the start anchor and the negated class in regex mode
return NEIClientConfig.getIntSetting("inventory.search.patternMode") == 2 ? "<>" : "<>^";
}
public char getRedefinedPrefix(char prefix) {
return this.prefixRedefinitions.getOrDefault(prefix, prefix);
}
@@ -29,6 +29,10 @@ public abstract class Widget {
public void lastKeyTyped(int keyID, char keyChar) {}
public boolean onHotkeyPressed() {
return false;
}
public boolean handleClickExt(int mx, int my, int button) {
return false;
}
@@ -51,6 +55,10 @@ public abstract class Widget {
return bounds().contains(px, py);
}
public boolean isVisible() {
return true;
}
public ItemStack getStackMouseOver(int mx, int my) {
return null;
}
@@ -56,7 +56,7 @@ public class WidgetContainer extends Widget {
final Rectangle4i bounds = bounds();
for (Widget widget : this.widgets) {
if (widget.bounds().intersects(bounds)) {
if (widget.isVisible() && widget.bounds().intersects(bounds)) {
widget.draw(mousex, mousey);
}
}
@@ -80,7 +80,7 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
if (widget.contains(mousex, mousey)) {
if (widget.isVisible() && widget.contains(mousex, mousey)) {
return widget;
}
}
@@ -113,7 +113,8 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
if (!widget.contains(mousex, mousey) && widget.handleClickExt(mousex, mousey, button)) {
if (widget.isVisible() && !widget.contains(mousex, mousey)
&& widget.handleClickExt(mousex, mousey, button)) {
return true;
}
}
@@ -126,7 +127,7 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
widget.mouseDragged(mousex, mousey, button, heldTime);
if (widget.isVisible()) widget.mouseDragged(mousex, mousey, button, heldTime);
}
}
@@ -135,7 +136,7 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
widget.mouseUp(mousex, mousey, button);
if (widget.isVisible()) widget.mouseUp(mousex, mousey, button);
}
}
@@ -144,7 +145,7 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
if (widget.handleKeyPress(keyCode, keyChar)) {
if (widget.isVisible() && widget.handleKeyPress(keyCode, keyChar)) {
return true;
}
}
@@ -157,11 +158,24 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
widget.lastKeyTyped(keyID, keyChar);
if (widget.isVisible()) widget.lastKeyTyped(keyID, keyChar);
}
}
@Override
public boolean onHotkeyPressed() {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
if (widget.onHotkeyPressed()) {
return true;
}
}
return false;
}
@Override
public boolean isFocused() {
return this.isFocused;
@@ -214,7 +228,7 @@ public class WidgetContainer extends Widget {
public void postDraw(int mousex, int mousey) {
for (Widget widget : this.widgets) {
widget.postDraw(mousex, mousey);
if (widget.isVisible()) widget.postDraw(mousex, mousey);
}
}
@@ -224,7 +238,7 @@ public class WidgetContainer extends Widget {
for (int index = this.widgets.size() - 1; index >= 0; index--) {
final Widget widget = this.widgets.get(index);
widget.onGuiClick(mousex, mousey);
if (widget.isVisible()) widget.onGuiClick(mousex, mousey);
}
}
@@ -240,6 +254,17 @@ public class WidgetContainer extends Widget {
return tooltip;
}
@Override
public List<String> handleItemTooltip(ItemStack itemstack, int mousex, int mousey, List<String> tooltip) {
final Widget widget = getWidgetUnderMouse(mousex, mousey);
if (widget != null) {
return widget.handleItemTooltip(itemstack, mousex, mousey, tooltip);
}
return tooltip;
}
@Override
public Map<String, String> handleHotkeys(int mousex, int mousey, Map<String, String> hotkeys) {
final Widget widget = getWidgetUnderMouse(mousex, mousey);
@@ -13,18 +13,21 @@ import net.minecraft.entity.EnumCreatureType;
import net.minecraft.entity.passive.EntityPig;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.SpawnerAnimals;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.chunk.Chunk;
import org.lwjgl.opengl.GL11;
import com.cardinalstar.cubicchunks.world.api.IMinMaxHeight;
import codechicken.lib.math.MathHelper;
import codechicken.nei.KeyManager.IKeyStateTracker;
import codechicken.nei.moboverlay.MobOverlayDisplayList;
import codechicken.nei.moboverlay.MobOverlayFace;
import codechicken.nei.moboverlay.MobOverlayScan;
import codechicken.nei.moboverlay.MobOverlayStyle;
import cpw.mods.fml.relauncher.ReflectionHelper;
public class WorldOverlayRenderer implements IKeyStateTracker {
@@ -32,8 +35,9 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
public static int chunkOverlay = 0;
public static int mobOverlay = 0;
private static byte[] mobSpawnCache;
private static long mobOverlayUpdateTime;
private static final MobOverlayScan MOB_OVERLAY_SCAN = new MobOverlayScan();
private static final MobOverlayFace MARKED_FACE = new MobOverlayFace();
private static final MobOverlayDisplayList MOB_OVERLAY_LIST = new MobOverlayDisplayList();
public static void reset() {
mobOverlay = 0;
@@ -82,51 +86,6 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
GL11.glPopAttrib();
}
private static void buildMobSpawnOverlay(Entity entity) {
if (mobSpawnCache == null) {
mobSpawnCache = new byte[33 * 33 * 33]; // 35kB
}
World world = entity.worldObj;
int worldHeight = world.getHeight();
int x1 = (int) entity.posX;
int z1 = (int) entity.posZ;
int y1 = (int) MathHelper.clip(entity.posY, 16, worldHeight - 16);
for (int i = 0; i <= 32; i++) {
int x = x1 - 16 + i;
for (int j = 0; j <= 32; j++) {
int z = z1 - 16 + j;
int bufIndex = (i * 33 + j) * 33;
Chunk chunk = world.getChunkFromBlockCoords(x, z);
BiomeGenBase biome = world.getBiomeGenForCoords(x, z);
if (biome.getSpawnableList(EnumCreatureType.monster).isEmpty() || biome.getSpawningChance() <= 0) {
mobSpawnCache[bufIndex] = -1;
continue;
}
int maxHeight = chunk.worldObj.getHeightValue(x, z);
for (int k = 0; k <= 32; k++) {
int y = y1 - 16 + k;
if (y >= worldHeight) {
mobSpawnCache[bufIndex + k] = -1;
continue;
}
if (y > maxHeight) {
mobSpawnCache[bufIndex + k] = -1;
break;
}
mobSpawnCache[bufIndex + k] = getSpawnMode(chunk, x, y, z);
}
}
}
}
private static double getOverlayYOffset(World world, int x, int y, int z) {
Block block = world.getBlock(x, y, z);
if (block instanceof BlockSnow) {
@@ -136,72 +95,97 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
return 0.02;
}
/**
* The model view matrix is relative to ({@code intOffsetX}, {@code intOffsetY}, {@code intOffsetZ}), the camera
* position cast to {@code int}.
*/
private static void renderMobSpawnOverlay(Entity entity, int intOffsetX, int intOffsetY, int intOffsetZ) {
if (mobOverlay == 0) {
if (mobSpawnCache != null) {
mobSpawnCache = null;
}
MOB_OVERLAY_SCAN.clear();
MOB_OVERLAY_LIST.delete();
return;
}
long worldTime = entity.worldObj.getTotalWorldTime();
if (mobSpawnCache == null || mobOverlayUpdateTime != worldTime) {
mobOverlayUpdateTime = worldTime;
buildMobSpawnOverlay(entity);
MOB_OVERLAY_SCAN.update(
entity,
NEIClientConfig.getIntSetting("world.overlays.mobOverlayRadius"),
NEIClientConfig.getIntSetting("world.overlays.mobOverlayVerticalRadius"));
final MobOverlayStyle style = MobOverlayStyle
.parseStyle(NEIClientConfig.getSetting("world.overlays.mobOverlayStyle").getValue());
final long worldTicks = entity.worldObj.getTotalWorldTime();
if (MOB_OVERLAY_LIST.isCompileDue(MOB_OVERLAY_SCAN.getModesVersion(), style, worldTicks)) {
compileMobOverlayList(entity.worldObj, style, worldTicks);
}
GL11.glDisable(GL11.GL_TEXTURE_2D);
beginMobOverlayDraw(style);
MOB_OVERLAY_LIST.draw(intOffsetX, intOffsetY, intOffsetZ);
endMobOverlayDraw();
}
/**
* Add a marked face to {@link #MOB_OVERLAY_LIST} for each block of {@link #MOB_OVERLAY_SCAN} where mobs can spawn.
*/
private static void compileMobOverlayList(World world, MobOverlayStyle style, long worldTicks) {
MOB_OVERLAY_LIST.beginCompile(MOB_OVERLAY_SCAN, style, worldTicks);
final float alpha = style.computeAlpha();
final Tessellator tess = Tessellator.instance;
tess.startDrawing(style.glMode);
final int span = MOB_OVERLAY_SCAN.getSpan();
for (int i = 0; i < span; i++) {
for (int j = 0; j < span; j++) {
for (int k = 0; k < MOB_OVERLAY_SCAN.getHeight(); k++) {
if (MOB_OVERLAY_SCAN.readSpawnMode(i, k, j) == 0) continue;
final double yOffset = getOverlayYOffset(
world,
MOB_OVERLAY_SCAN.getMinX() + i,
MOB_OVERLAY_SCAN.getMinY() + k,
MOB_OVERLAY_SCAN.getMinZ() + j);
MARKED_FACE.moveTo(i, k + yOffset, j);
loadNeighbourModes(i, k, j);
MARKED_FACE.applyColor(style.colouring, alpha);
style.addVertices(MARKED_FACE);
}
}
}
tess.draw();
MOB_OVERLAY_LIST.endCompile();
}
private static void beginMobOverlayDraw(MobOverlayStyle style) {
final ResourceLocation texture = style.texture;
if (texture == null) {
GL11.glDisable(GL11.GL_TEXTURE_2D);
} else {
GL11.glEnable(GL11.GL_TEXTURE_2D);
Minecraft.getMinecraft().getTextureManager().bindTexture(texture);
}
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glLineWidth(1.5F);
// the outline of `MobOverlayStyle.CONNECTED` lies in the plane of the tint. With depth writes on, the depth
// test discards most of the outline
GL11.glDepthMask(false);
}
int curSpawnMode = 2;
World world = entity.worldObj;
int x1 = (int) entity.posX - intOffsetX;
int z1 = (int) entity.posZ - intOffsetZ;
int y1 = (int) MathHelper.clip(entity.posY, 16, world.getHeight() - 16) - intOffsetY;
final Tessellator tess = Tessellator.instance;
tess.startDrawing(GL11.GL_LINES);
tess.setColorOpaque(255, 0, 0);
for (int i = 0; i <= 32; i++) {
int x = x1 - 16 + i;
for (int j = 0; j <= 32; j++) {
int z = z1 - 16 + j;
int cacheIndex = (i * 33 + j) * 33;
for (int k = 0; k <= 32; k++) {
int y = y1 - 16 + k;
int spawnMode = mobSpawnCache[cacheIndex + k];
if (spawnMode == 0) {
continue;
} else if (spawnMode == -1) {
break;
}
if (spawnMode != curSpawnMode) {
if (spawnMode == 1) {
tess.setColorOpaque(255, 255, 0);
} else {
tess.setColorOpaque(255, 0, 0);
}
curSpawnMode = spawnMode;
}
double yOff = getOverlayYOffset(world, x + intOffsetX, y + intOffsetY, z + intOffsetZ);
tess.addVertex(x, y + yOff, z);
tess.addVertex(x + 1, y + yOff, z + 1);
tess.addVertex(x + 1, y + yOff, z);
tess.addVertex(x, y + yOff, z + 1);
}
private static void loadNeighbourModes(int i, int k, int j) {
final int span = MOB_OVERLAY_SCAN.getSpan();
for (int offsetX = -1; offsetX <= 1; offsetX++) {
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
final int neighbourI = i + offsetX;
final int neighbourJ = j + offsetZ;
final boolean isInScan = neighbourI >= 0 && neighbourI < span && neighbourJ >= 0 && neighbourJ < span;
final byte spawnMode = isInScan ? MOB_OVERLAY_SCAN.readSpawnMode(neighbourI, k, neighbourJ)
: MobOverlayFace.OUTSIDE_SCAN;
MARKED_FACE.setNeighbourMode(offsetX, offsetZ, spawnMode);
}
}
}
tess.draw();
/** Undo the GL state changes of {@link #beginMobOverlayDraw}, except the texture binding. */
private static void endMobOverlayDraw() {
GL11.glDepthMask(true);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(GL11.GL_BLEND);
@@ -210,7 +194,7 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
private static final Entity dummyEntity = new EntityPig(null);
private static final AxisAlignedBB aabb = AxisAlignedBB.getBoundingBox(0, 0, 0, 0, 0, 0);
private static byte getSpawnMode(Chunk chunk, int x, int y, int z) {
public static byte getSpawnMode(Chunk chunk, int x, int y, int z) {
if (y >= chunk.worldObj.getHeight()) {
return 0;
}
@@ -19,6 +19,7 @@ import org.lwjgl.input.Keyboard;
import com.google.common.collect.Sets;
import codechicken.nei.BookmarkContainerInfo;
import codechicken.nei.CollapsibleItems;
import codechicken.nei.ItemList;
import codechicken.nei.ItemSorter;
import codechicken.nei.ItemStackSet;
@@ -341,6 +342,30 @@ public class API {
SubsetWidget.addTag(tag);
}
/**
* Registers a group of items that is collapsed into a single entry in the item panel. Groups registered here have
* the lowest priority: an item that matches a preset or a collapsibleitems.cfg group stays there. They are active
* only while the Collapsible Items option is enabled. Must be called while plugins are loading.
*
* @param filter A search string for matching items that belong to this group. Also identifies the group when its
* expanded state is saved, so changing it resets the saved state.
* @return A handle to optionally set the name shown for the group
*/
public static CollapsibleItems.ApiGroup addCollapsibleGroup(String filter) {
return CollapsibleItems.registerGroup(filter);
}
/**
* Same as {@link #addCollapsibleGroup(String)}, but the group is given as a list of item stacks. The stacks
* identify the group when its expanded state is saved, so changing them resets the saved state.
*
* @param items Item stacks that belong to this group
* @return A handle to optionally set the name shown for the group
*/
public static CollapsibleItems.ApiGroup addCollapsibleGroup(Iterable<ItemStack> items) {
return CollapsibleItems.registerGroup(items);
}
@Deprecated
public static void addSearchProvider(ISearchProvider provider) {}
@@ -59,6 +59,7 @@ import codechicken.nei.SearchTokenParser.SearchMode;
import codechicken.nei.config.ArrayDumper;
import codechicken.nei.config.HandlerDumper;
import codechicken.nei.config.ItemPanelDumper;
import codechicken.nei.config.RecipeDumper;
import codechicken.nei.config.RegistryDumper;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.item.FluidDisplayRenderer;
@@ -284,6 +285,7 @@ public class ItemInfo {
});
API.addOption(new ItemPanelDumper("tools.dump.itempanel"));
API.addOption(new HandlerDumper("tools.dump.handlers"));
API.addOption(new RecipeDumper("tools.dump.recipes"));
}
@SuppressWarnings("unchecked")
@@ -333,12 +335,6 @@ public class ItemInfo {
}
private static void addVanillaBlockProperties() {
API.setOverrideName(new ItemStack(Blocks.flowing_water), "Water Source");
API.setOverrideName(new ItemStack(Blocks.water), "Water Still");
API.setOverrideName(new ItemStack(Blocks.flowing_lava), "Lava Source");
API.setOverrideName(new ItemStack(Blocks.lava), "Lava Still");
API.setOverrideName(new ItemStack(Blocks.end_portal), "End Portal");
API.setOverrideName(new ItemStack(Blocks.end_portal_frame), "End Portal Frame");
API.hideItem(new ItemStack(Blocks.double_stone_slab, 1, OreDictionary.WILDCARD_VALUE));
API.hideItem(new ItemStack(Blocks.double_wooden_slab, 1, OreDictionary.WILDCARD_VALUE));
API.hideItem(new ItemStack(Blocks.carrots));
@@ -5,7 +5,6 @@ import java.util.LinkedList;
import net.minecraft.world.World;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.config.GuiOptionList;
import codechicken.nei.config.OptionCycled;
public class NEIInfo {
@@ -14,11 +13,7 @@ public class NEIInfo {
public static void load(World world) {
OptionCycled modeOption = (OptionCycled) NEIClientConfig.getOptionList().getOption("inventory.cheatmode");
final boolean worldConfig = NEIClientConfig.isWorldSpecific("inventory.cheatmode");
// kept alive so the WeakReference in Option.slotRef isn't GC'd before copyGlobals/cycle use it
GuiOptionList env = modeOption.parent.synthesizeEnvironment(worldConfig);
if (!modeOption.optionValid(modeOption.value())) {
modeOption.copyGlobals();
modeOption.cycle();
}
}
@@ -343,7 +343,6 @@ public abstract class ShortcutInputHandler {
if (handlerRef != null) {
final GuiFavoriteButton button = new GuiFavoriteButton(handlerRef, 0, 0);
button.saveRecipeInBookmark();
return true;
}
return true;
@@ -14,7 +14,6 @@ import net.minecraft.nbt.NBTTagCompound;
import org.apache.commons.io.IOUtils;
import com.google.common.io.Files;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -25,6 +24,7 @@ import com.google.gson.JsonSyntaxException;
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
import codechicken.nei.ItemPanels;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.util.NBTJson;
@@ -230,14 +230,7 @@ public class BookmarkStorage {
}
if (wasError) {
try {
Files.copy(
bookmarkFile,
new File(bookmarkFile.getAbsolutePath() + ".backup-" + System.currentTimeMillis()));
NEIClientConfig.logger.info("Backed up invalid bookmark file to {}", bookmarkFile);
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to backup invalid bookmark file to {}", bookmarkFile, e);
}
NEIClientUtils.backupFile(bookmarkFile);
}
for (BookmarkGrid gr : namespaces) {
@@ -58,6 +58,7 @@ import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.guihook.IContainerTooltipHandler;
import codechicken.nei.guihook.IGuiClientSide;
import codechicken.nei.guihook.IGuiHandleMouseWheel;
import codechicken.nei.guihook.IGuiHotkeyListener;
import codechicken.nei.recipe.AcceptsFollowingTooltipLineHandler;
import codechicken.nei.recipe.AutoCraftingManager;
import codechicken.nei.recipe.GuiCraftingRecipe;
@@ -77,7 +78,7 @@ import codechicken.nei.util.SlotInaccessible;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGuiClientSide, IGuiHandleMouseWheel,
IContainerTooltipHandler, IGuiContainerOverlay {
IGuiHotkeyListener, IContainerTooltipHandler, IGuiContainerOverlay {
public static class EventHandler {
@@ -911,6 +912,12 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu
return;
}
super.keyTyped(typedChar, keyCode);
}
@Override
public boolean onHotkeyPressed() {
if (this.statsPanelVisible && KeyManager.isKeyDown("recipe.recipe")) {
final Point2D mouse = getMousePosition();
final Widget widget = this.statsPanel.getWidgetUnderMouse((int) mouse.getX(), (int) mouse.getY());
@@ -918,11 +925,11 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu
if (widget != null && widget instanceof StatsPanel.ItemStatsSlot itemStatsSlot
&& itemStatsSlot.recipeId != null) {
GuiCraftingRecipe.openRecipeGui("recipeId", itemStatsSlot.recipeId.getResult(), itemStatsSlot.recipeId);
return;
return true;
}
}
super.keyTyped(typedChar, keyCode);
return false;
}
private String getHandlerTitle(String handlerName) {
@@ -13,6 +13,10 @@ public class ConfigSet {
public NBTTagCompound nbt;
public final ConfigTagParent config;
public ConfigSet(File nbtFile) {
this(nbtFile, null);
}
public ConfigSet(File nbtFile, ConfigTagParent config) {
this.nbtFile = nbtFile;
this.config = config;
@@ -110,7 +110,7 @@ public abstract class DataDumper extends Option {
}
public int getMode() {
return renderTag().getIntValue(0);
return getTag().getIntValue(0);
}
public void drawButton(int mousex, int mousey, Rectangle4i b, String text) {
@@ -29,7 +29,6 @@ public class GuiHighlightTips extends GuiScreenWidget {
private GuiCCButton toggleButton;
private final Option opt;
private final GuiOptionList parentGui;
private final boolean world;
private Point dragDown;
public GuiHighlightTips(Option opt) {
@@ -37,7 +36,6 @@ public class GuiHighlightTips extends GuiScreenWidget {
this.opt = opt;
name = opt.name;
this.parentGui = (GuiOptionList) Minecraft.getMinecraft().currentScreen;
this.world = parentGui.worldConfig();
}
@Override
@@ -63,15 +61,11 @@ public class GuiHighlightTips extends GuiScreenWidget {
}
private boolean show() {
return renderTag(name).getBooleanValue();
return getTag(name).getBooleanValue();
}
private ConfigTag getTag(String s) {
return (world ? opt.worldConfigSet() : opt.globalConfigSet()).config.getTag(s);
}
private ConfigTag renderTag(String s) {
return (world && opt.worldSpecific(s) ? opt.worldConfigSet() : opt.globalConfigSet()).config.getTag(s);
return opt.configSet().config.getTag(s);
}
@Override
@@ -95,7 +89,7 @@ public class GuiHighlightTips extends GuiScreenWidget {
}
public Point getPos() {
return new Point(renderTag(name + ".x").getIntValue(), renderTag(name + ".y").getIntValue());
return new Point(getTag(name + ".x").getIntValue(), getTag(name + ".y").getIntValue());
}
public Dimension sampleSize() // copied from HUDManager when running with the sample for this gui
@@ -161,7 +155,6 @@ public class GuiHighlightTips extends GuiScreenWidget {
}
private void setPos(Point p) {
getTag(name).setBooleanValue(show()); // duplicates global tag for the option gui if in world mode
getTag(name + ".x").setIntValue(p.x);
getTag(name + ".y").setIntValue(p.y);
}
@@ -52,7 +52,6 @@ public class GuiItemSorter extends GuiOptionPane {
public final List<SortItem> slots = new ArrayList<>();
public final List<SortEntry> list;
private final GuiOptionList parentGui;
private final boolean world;
boolean dragging;
public SortItem dragged;
@@ -62,8 +61,7 @@ public class GuiItemSorter extends GuiOptionPane {
public GuiItemSorter(Option opt) {
this.opt = opt;
this.parentGui = (GuiOptionList) Minecraft.getMinecraft().currentScreen;
this.world = parentGui.worldConfig();
list = ItemSorter.fromSaveString(renderTag(opt.configName()).getValue());
list = ItemSorter.fromSaveString(getTag(opt.configName()).getValue());
for (SortEntry e : list) slots.add(new SortItem(e));
}
@@ -122,10 +120,7 @@ public class GuiItemSorter extends GuiOptionPane {
list.add(nslot, dragged.e);
getTag(opt.configName()).setValue(ItemSorter.getSaveString(list));
if (opt.activeTag() == getTag(opt.configName())) {
ItemSorter.list = new ArrayList<>(list);
}
ItemSorter.list = new ArrayList<>(list);
ItemList.refreshItems.restart();
}
@@ -133,11 +128,7 @@ public class GuiItemSorter extends GuiOptionPane {
}
private ConfigTag getTag(String s) {
return (world ? opt.worldConfigSet() : opt.globalConfigSet()).config.getTag(s);
}
private ConfigTag renderTag(String s) {
return (world && opt.worldSpecific(s) ? opt.worldConfigSet() : opt.globalConfigSet()).config.getTag(s);
return opt.configSet().config.getTag(s);
}
public SortItem itemAt(int mx, int my) {
@@ -5,8 +5,8 @@ import net.minecraft.client.gui.GuiYesNoCallback;
public class GuiNEIOptionList extends GuiOptionList implements GuiYesNoCallback {
public GuiNEIOptionList(GuiScreen parent, OptionList optionList, boolean world) {
super(parent, optionList, world);
public GuiNEIOptionList(GuiScreen parent, OptionList optionList) {
super(parent, optionList);
}
@Override
@@ -1,7 +1,6 @@
package codechicken.nei.config;
import static codechicken.lib.gui.GuiDraw.drawMultilineTip;
import static codechicken.lib.gui.GuiDraw.drawStringC;
import static codechicken.lib.gui.GuiDraw.fontRenderer;
import static codechicken.lib.gui.GuiDraw.getMousePosition;
import static net.minecraft.util.StatCollector.translateToLocal;
@@ -10,7 +9,6 @@ import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -23,8 +21,6 @@ import org.lwjgl.opengl.GL11;
import codechicken.core.gui.GuiCCButton;
import codechicken.core.gui.GuiScreenWidget;
import codechicken.core.gui.GuiScrollSlot;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.LayoutManager;
public class GuiOptionList extends GuiScreenWidget {
@@ -66,25 +62,11 @@ public class GuiOptionList extends GuiScreenWidget {
protected void drawSlot(int slot, int x, int y, int mx, int my, float frame) {
Option o = options.get(slot);
GL11.glTranslatef(x, y, 0);
if (o.showWorldSelector() && world) drawWorldSelector(o, mx, my);
GL11.glTranslatef(24, 2, 0);
o.draw(mx - 24, my - 2, frame);
GL11.glTranslatef(-x - 24, -y - 2, 0);
}
public Rectangle4i worldButtonSize() {
return new Rectangle4i(0, 2, 20, 20);
}
private void drawWorldSelector(Option o, int mousex, int mousey) {
Rectangle4i b = worldButtonSize();
boolean set = o.hasWorldOverride();
boolean mouseover = b.contains(mousex, mousey);
GL11.glColor4f(1, 1, 1, 1);
LayoutManager.drawButtonBackground(b.x, b.y, b.w, b.h, true, !set ? 0 : mouseover ? 2 : 1);
drawStringC("W", b.x, b.y, b.w, b.h, -1);
}
@Override
public void drawBackground(float frame) {
Rectangle sbar = scrollbarBounds();
@@ -116,16 +98,7 @@ public class GuiOptionList extends GuiScreenWidget {
@Override
protected void slotClicked(int slot, int button, int mx, int my, int count) {
Option o = options.get(slot);
if (world && o.showWorldSelector() && worldButtonSize().contains(mx, my)) {
if (button == 1 && o.hasWorldOverride()) {
o.useGlobals();
Option.playClickSound();
} else if (button == 0 && !o.hasWorldOverride()) {
o.copyGlobals();
Option.playClickSound();
}
} else if (slotBounds(slot).contains(mx, my)) {
if (slotBounds(slot).contains(mx, my)) {
options.get(slot).mouseClicked(mx - 24, my - 2, button);
}
}
@@ -167,26 +140,20 @@ public class GuiOptionList extends GuiScreenWidget {
}
private List<String> handleTooltip(int slot, int mx, int my, List<String> tooltip) {
Option o = options.get(slot);
if (world && o.showWorldSelector() && worldButtonSize().contains(mx, my))
tooltip.add(translateToLocal("nei.options.wbutton.tip." + (o.hasWorldOverride() ? "1" : "0")));
if (slotBounds(slot).contains(mx, my)) return o.handleTooltip(mx - 24, my - 2, tooltip);
if (slotBounds(slot).contains(mx, my)) return options.get(slot).handleTooltip(mx - 24, my - 2, tooltip);
return tooltip;
}
}
protected final GuiScreen parent;
protected final OptionList optionList;
protected boolean world;
protected OptionScrollSlot slot;
protected GuiCCButton backButton;
protected GuiCCButton worldButton;
public GuiOptionList(GuiScreen parent, OptionList optionList, boolean world) {
public GuiOptionList(GuiScreen parent, OptionList optionList) {
this.parent = parent;
this.optionList = optionList;
this.world = world;
}
@Override
@@ -202,29 +169,18 @@ public class GuiOptionList extends GuiScreenWidget {
backButton.width = Math.min(200, width - 40);
backButton.x = (width - backButton.width) / 2;
backButton.y = height - 25;
worldButton.width = 60;
worldButton.x = width - worldButton.width - 15;
}
@Override
public void addWidgets() {
add(slot = new OptionScrollSlot());
add(backButton = new GuiCCButton(0, 0, 0, 20, translateToLocal("nei.options.back")).setActionCommand("back"));
add(worldButton = new GuiCCButton(0, 2, 0, 16, worldButtonName()).setActionCommand("world"));
}
private String worldButtonName() {
return translateToLocal("nei.options." + (world ? "world" : "global"));
}
@Override
public void actionPerformed(String ident, Object... params) {
if (ident.equals("back")) {
if (parent instanceof GuiOptionList) ((GuiOptionList) parent).world = world;
Minecraft.getMinecraft().displayGuiScreen(parent);
} else if (ident.equals("world")) {
world = !world;
worldButton.text = worldButtonName();
}
}
@@ -242,9 +198,6 @@ public class GuiOptionList extends GuiScreenWidget {
private void drawTooltip() {
List<String> tooltip = new LinkedList<>();
Point mouse = getMousePosition();
if (worldButton.pointInside(mouse.x, mouse.y))
tooltip.addAll(Arrays.asList(translateToLocal("nei.options.global.tip." + (world ? "1" : "0")).split(":")));
tooltip = slot.handleTooltip(mouse.x, mouse.y, tooltip);
drawMultilineTip(mouse.x + 12, mouse.y - 12, tooltip);
}
@@ -265,8 +218,4 @@ public class GuiOptionList extends GuiScreenWidget {
super.keyTyped(c, keycode);
}
}
public boolean worldConfig() {
return world;
}
}
@@ -55,7 +55,6 @@ public class GuiPanelSettings extends GuiScreenWidget {
private final Option opt;
private final String name;
private final boolean world;
private Rectangle4i margin;
private Point dragDown;
private String dragDir;
@@ -69,10 +68,9 @@ public class GuiPanelSettings extends GuiScreenWidget {
private PanelResizeButton rightButton = new PanelResizeButton(PANEL_SIZE, PANEL_SIZE * 2);
private PanelResizeButton bottomButton = new PanelResizeButton(PANEL_SIZE * 2, PANEL_SIZE);
public PanelPlaceholder(Option opt, String name, boolean world) {
public PanelPlaceholder(Option opt, String name) {
this.opt = opt;
this.name = name;
this.world = world;
}
public void resize(Rectangle4i margin) {
@@ -163,7 +161,7 @@ public class GuiPanelSettings extends GuiScreenWidget {
}
private ConfigTag getTag(String s) {
return (world ? opt.worldConfigSet() : opt.globalConfigSet()).config.getTag(s);
return opt.configSet().config.getTag(s);
}
protected void drawItems() {
@@ -241,9 +239,8 @@ public class GuiPanelSettings extends GuiScreenWidget {
super(176, 198);
this.parentGui = (GuiOptionList) Minecraft.getMinecraft().currentScreen;
boolean world = parentGui.worldConfig();
bookmarksPanel = new PanelPlaceholder(opt, opt.name + ".bookmarks", world);
itemsPanel = new PanelPlaceholder(opt, opt.name + ".items", world);
bookmarksPanel = new PanelPlaceholder(opt, opt.name + ".bookmarks");
itemsPanel = new PanelPlaceholder(opt, opt.name + ".items");
}
@Override
@@ -58,7 +58,7 @@ public class ItemPanelDumper extends DataDumper {
}
public int getRes() {
int i = renderTag(name + ".res").getIntValue(0);
int i = getTag(name + ".res").getIntValue(0);
int size = 16 << i;
if (size >= getSmallerScreenAxisSize() || i < 0) {
getTag(name + ".res").setIntValue(0);
@@ -95,7 +95,7 @@ public class ItemPanelDumper extends DataDumper {
}
if (resButtonSize().contains(mousex, mousey)) {
NEIClientUtils.playClickSound();
int current = renderTag(name + ".res").getIntValue(0);
int current = getTag(name + ".res").getIntValue(0);
int next;
// 31 - numberOfLeadingZeros(x) = floor(log2(x))
int max = 31 - Integer.numberOfLeadingZeros(getSmallerScreenAxisSize()) - 3;
@@ -59,12 +59,8 @@ public abstract class Option {
return elem;
}
public ConfigSet globalConfigSet() {
return assertParentOverride(parent.globalConfigSet());
}
public ConfigSet worldConfigSet() {
return assertParentOverride(parent.worldConfigSet());
public ConfigSet configSet() {
return assertParentOverride(parent.configSet());
}
public OptionList configBase() {
@@ -72,127 +68,19 @@ public abstract class Option {
}
/**
* @return true if the world config contains a tag with this name
*/
public boolean worldSpecific() {
return worldSpecific(configName());
}
/**
* @return true if the world config contains a tag with this name
*/
public boolean worldSpecific(String s) {
ConfigTag tag = worldConfigSet().config.getTag(s, false);
return tag != null && tag.value != null;
}
/**
* @return The currently config set for the gui based on the world/global button
*/
public ConfigSet configSet() {
return worldConfig() ? worldConfigSet() : globalConfigSet();
}
/**
* @return The tag to render, world if world override, otherwise global
*/
public ConfigTag renderTag() {
return renderTag(configName());
}
/**
* @return The tag to render, world if world override, otherwise global
*/
public ConfigTag renderTag(String s) {
return (worldConfig() && worldSpecific(s) ? worldConfigSet() : globalConfigSet()).config.getTag(s);
}
/**
* @return The tag with this name from the current configSet
* @return The tag with this name from the config set
*/
public ConfigTag getTag() {
return getTag(configName());
}
/**
* @return The tag with this name from the current configSet
* @return The tag with this name from the config set
*/
public ConfigTag getTag(String s) {
return configSet().config.getTag(s);
}
/**
* @return The tag currently being used ingame, world if a world override exists, otherwise global
*/
public ConfigTag activeTag() {
return activeTag(configName());
}
/**
* @return The tag currently being used ingame, world if a world override exists, otherwise global
*/
public ConfigTag activeTag(String s) {
return (worldSpecific() ? worldConfigSet() : globalConfigSet()).config.getTag(s);
}
/**
* @return true if the gui is in world config mode
*/
public boolean worldConfig() {
return getSlot().getGui().worldConfig();
}
/**
* @return true if in world mode, but this tag has no world override. Changes should not be made in this mode
*/
public boolean defaulting() {
return defaulting(configName());
}
/**
* @return true if in world mode, but this tag has no world override. Changes should not be made in this mode
*/
public boolean defaulting(String s) {
return worldConfig() && !worldSpecific(s);
}
/**
* Called when world specific is disabled for this option. Should delete all world specific overrides
*/
public void useGlobals() {
useGlobal(configName());
}
/**
* Deletes a specific named tag from the worldConfig
*/
public void useGlobal(String s) {
if (worldConfig()) worldConfigSet().config.removeTag(s);
}
/**
* Called when world specific is activated for this option. Should copy the global value into a world specific
* override
*/
public void copyGlobals() {
copyGlobal(configName(), true);
}
/**
* Copies the global value into a world specific override
*/
public void copyGlobal(String s) {
copyGlobal(s, false);
}
public void copyGlobal(String s, boolean recursive) {
if (!worldConfig()) return;
ConfigTag tag = globalConfigSet().config.getTag(s);
worldConfigSet().config.getTag(s).setValue(tag.getValue());
if (recursive) for (String s2 : tag.childTagMap().keySet()) copyGlobal(s + "." + s2);
}
public void onAdded(OptionScrollSlot slot) {
this.slotRef = new WeakReference<>(slot);
}
@@ -217,12 +105,4 @@ public abstract class Option {
public List<String> handleTooltip(int mx, int my, List<String> currenttip) {
return currenttip;
}
public boolean showWorldSelector() {
return true;
}
public boolean hasWorldOverride() {
return worldSpecific();
}
}
@@ -16,7 +16,11 @@ public class OptionCycled extends OptionButton {
}
public int value() {
return renderTag().getIntValue();
return getTag().getIntValue();
}
public void setValue(int value) {
getTag().setIntValue(value);
}
@Override
@@ -52,7 +56,7 @@ public class OptionCycled extends OptionButton {
if (next == value()) return false;
getTag().setIntValue(next);
setValue(next);
return true;
}
@@ -0,0 +1,42 @@
package codechicken.nei.config;
import net.minecraft.util.ChatComponentTranslation;
import codechicken.nei.ClientHandler;
import codechicken.nei.NEIClientUtils;
public class OptionExportConfig extends OptionButton {
private final String fileName;
public OptionExportConfig(String fileName) {
super("tools.export_configs." + fileName.substring(0, fileName.lastIndexOf('.')));
this.fileName = fileName;
}
@Override
public String getPrefix() {
return fileName;
}
@Override
public String getButtonText() {
return translateN("tools.export_configs.button");
}
@Override
public String getTooltip() {
return null;
}
@Override
public boolean onClick(int button) {
final boolean exported = ClientHandler.exportDefaultConfig(fileName);
NEIClientUtils.printChatMessage(
new ChatComponentTranslation(
exported ? "nei.options.tools.export_configs.done" : "nei.options.tools.export_configs.skipped",
fileName));
return true;
}
}
@@ -22,7 +22,7 @@ public class OptionIntegerField extends OptionTextField {
@Override
protected int getMaxInputWidth() {
if (renderTag().getValue().startsWith("0x")) {
if (getTag().getValue().startsWith("0x")) {
return GuiDraw.getStringWidth("0x" + Long.toHexString(this.max).toUpperCase()) + 32;
}
@@ -20,12 +20,7 @@ public class OptionList extends OptionButton {
}
@Override
public ConfigSet globalConfigSet() {
return null;
}
@Override
public ConfigSet worldConfigSet() {
public ConfigSet configSet() {
return null;
}
@@ -35,8 +30,8 @@ public class OptionList extends OptionButton {
}
@Override
public GuiOptionList getGui(GuiScreen parent, OptionList list, boolean world) {
return new GuiOptionList(parent, list, world);
public GuiOptionList getGui(GuiScreen parent, OptionList list) {
return new GuiOptionList(parent, list);
}
}
@@ -118,41 +113,25 @@ public class OptionList extends OptionButton {
*
* @param parent The parent screen for the back button
* @param list The option list to be displayed in the gui
* @param world true if in world config mode, false for global
*/
public GuiOptionList getGui(GuiScreen parent, OptionList list, boolean world) {
return this.parent.getGui(parent, list, world);
public GuiOptionList getGui(GuiScreen parent, OptionList list) {
return this.parent.getGui(parent, list);
}
public void openGui(GuiScreen parent, boolean world) {
Minecraft.getMinecraft().displayGuiScreen(getGui(parent, this, world));
}
/**
* Adds this option to a temporary slot and gui for internal setting manipulation.
*/
public GuiOptionList synthesizeEnvironment(boolean world) {
GuiOptionList gui = getGui(null, this, world);
gui.addWidgets();
return gui;
public void openGui(GuiScreen parent) {
Minecraft.getMinecraft().displayGuiScreen(getGui(parent, this));
}
@Override
public boolean onClick(int button) {
openGui(getSlot().getGui(), getSlot().getGui().worldConfig());
openGui(getSlot().getGui());
return true;
}
@Override
public boolean showWorldSelector() {
return false;
}
@Override
public void onAdded(OptionScrollSlot slot) {
super.onAdded(slot);
globalConfigSet().config.getTag(configName()).useBraces();
worldConfigSet().config.getTag(configName()).useBraces();
configSet().config.getTag(configName()).useBraces();
}
}
@@ -127,7 +127,7 @@ public abstract class OptionStringSet extends Option {
}
public List<String> values() {
return Arrays.asList(renderTag().getValue().replace(" ", "").split(","));
return Arrays.asList(getTag().getValue().replace(" ", "").split(","));
}
public int buttonX() {
@@ -16,8 +16,7 @@ public class OptionTextField extends Option {
@Override
public void onTextChange(String oldText) {
// don't override global if text hasn't changed
if (focused() && isValidValue(text()) && (!defaulting() || !text().equals(getTag().getValue()))) {
if (focused() && isValidValue(text())) {
getTag().setValue(text());
OptionTextField.this.onTextChange(text());
}
@@ -25,7 +24,7 @@ public class OptionTextField extends Option {
@Override
public void setFocus(boolean focus) {
if (!focus && !isValidValue(text())) setText(renderTag().getValue());
if (!focus && !isValidValue(text())) setText(getTag().getValue());
super.setFocus(focus);
}
@@ -45,7 +44,7 @@ public class OptionTextField extends Option {
@Override
public void update() {
textField.update();
if (!textField.focused()) textField.setText(renderTag().getValue());
if (!textField.focused()) textField.setText(getTag().getValue());
}
public String getPrefix() {
@@ -14,7 +14,7 @@ public class OptionToggleButton extends OptionButton {
}
public boolean state() {
return renderTag().getBooleanValue();
return getTag().getBooleanValue();
}
@Override
@@ -34,7 +34,7 @@ public class OptionToggleButtonBoubs extends OptionButton {
}
public boolean state() {
return renderTag().getBooleanValue();
return getTag().getBooleanValue();
}
@Override
@@ -0,0 +1,54 @@
package codechicken.nei.config;
import java.util.Collections;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.recipe.RecipeJsonWriter;
public class RecipeDumper extends DataDumper {
public RecipeDumper(String name) {
super(name);
}
@Override
public String[] header() {
return new String[0];
}
@Override
public Iterable<String[]> dump(int mode) {
return Collections.emptyList();
}
@Override
public String renderName() {
return translateN(name);
}
public Rectangle4i separateButtonSize() {
return dumpButtonSize();
}
public Rectangle4i combinedButtonSize() {
return modeButtonSize();
}
@Override
public void draw(int mousex, int mousey, float frame) {
drawPrefix();
drawButton(mousex, mousey, combinedButtonSize(), translateN(name + ".combined"));
drawButton(mousex, mousey, separateButtonSize(), translateN(name + ".separate"));
}
@Override
public void mouseClicked(int mousex, int mousey, int button) {
final boolean combined = combinedButtonSize().contains(mousex, mousey);
if (combined || separateButtonSize().contains(mousex, mousey)) {
NEIClientUtils.playClickSound();
RecipeJsonWriter.dumpAll(!combined);
}
}
}
@@ -79,19 +79,24 @@ public class RightPanel extends GuiWidget {
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
public boolean onHotkeyPressed() {
if (!focused() && KeyManager.isHashDown("gui.search")) {
setFocus(true);
return true;
}
if (focused() && KeyManager.isKeyDown("gui.getprevioussearch")) {
handleNavigateHistory(TextHistory.Direction.PREVIOUS);
return true;
}
if (focused() && KeyManager.isKeyDown("gui.getnextsearch")) {
handleNavigateHistory(TextHistory.Direction.NEXT);
return true;
}
return false;
}
@Override
@@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.recipe.IRecipeHandler;
@@ -30,7 +30,10 @@ public class AllMultiRecipeFilter implements IRecipeFilter {
try {
if (filter != null && !filter.matches(handler, recipeIndex)) return false;
} catch (Exception e) {
NEIClientConfig.logger.error("Exception filtering " + handler + " with " + filter, e);
NEIClientUtils.errorOnce(
"filter:" + handler.getHandlerId() + ":" + filter.getClass().getName(),
"Exception filtering " + handler.getHandlerId() + " with " + filter,
e);
}
}
return true;
@@ -1,44 +0,0 @@
package codechicken.nei.filter;
import net.minecraft.item.ItemStack;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.recipe.IRecipeHandler;
public class AllOthersRecipeFilter implements IRecipeFilter {
private final ItemFilter filter;
public AllOthersRecipeFilter(ItemFilter filter) {
this.filter = filter;
}
@Override
public boolean matches(IRecipeHandler handler, int recipeIndex) {
for (PositionedStack pStack : handler.getOtherStacks(recipeIndex)) {
if (!match(pStack)) {
return false;
}
}
return true;
}
protected boolean match(PositionedStack pStack) {
if (pStack == null) {
return false;
}
for (ItemStack stack : pStack.items) {
if (filter.matches(stack)) {
return true;
}
}
return false;
}
}
@@ -4,7 +4,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.recipe.IRecipeHandler;
@@ -30,7 +30,10 @@ public class AnyMultiRecipeFilter implements IRecipeFilter {
try {
if (filter != null && filter.matches(handler, recipeIndex)) return true;
} catch (Exception e) {
NEIClientConfig.logger.error("Exception filtering " + handler + " with " + filter, e);
NEIClientUtils.errorOnce(
"filter:" + handler.getHandlerId() + ":" + filter.getClass().getName(),
"Exception filtering " + handler.getHandlerId() + " with " + filter,
e);
}
}
@@ -1,44 +0,0 @@
package codechicken.nei.filter;
import net.minecraft.item.ItemStack;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.recipe.IRecipeHandler;
public class AnyOthersRecipeFilter implements IRecipeFilter {
private final ItemFilter filter;
public AnyOthersRecipeFilter(ItemFilter filter) {
this.filter = filter;
}
@Override
public boolean matches(IRecipeHandler handler, int recipeIndex) {
for (PositionedStack pStack : handler.getOtherStacks(recipeIndex)) {
if (match(pStack)) {
return true;
}
}
return false;
}
protected boolean match(PositionedStack pStack) {
if (pStack == null) {
return false;
}
for (ItemStack stack : pStack.items) {
if (filter.matches(stack)) {
return true;
}
}
return false;
}
}
@@ -16,7 +16,8 @@ public class RecipeFilter implements IRecipeFilter {
ANY,
INPUT,
OUTPUT;
OUTPUT,
AUXILIARY;
public static FilterContext fromChar(char c) {
switch (c) {
@@ -24,6 +25,8 @@ public class RecipeFilter implements IRecipeFilter {
return INPUT;
case '>':
return OUTPUT;
case '^':
return AUXILIARY;
default:
return ANY;
}
@@ -48,17 +51,14 @@ public class RecipeFilter implements IRecipeFilter {
return this.anyMatch;
}
if (this.context == FilterContext.ANY || this.context == FilterContext.OUTPUT) {
final PositionedStack result = handler.getResultStack(recipeIndex);
if (result != null && matchPositionedStack(result) == this.anyMatch) {
return this.anyMatch;
}
if (matchPositionedStack(handler.getOtherStacks(recipeIndex), this.anyMatch)) {
return this.anyMatch;
}
if ((this.context == FilterContext.ANY || this.context == FilterContext.OUTPUT)
&& matchPositionedStack(handler.getResultStacks(recipeIndex), this.anyMatch)) {
return this.anyMatch;
}
if ((this.context == FilterContext.ANY || this.context == FilterContext.AUXILIARY)
&& matchPositionedStack(handler.getAuxiliaryStacks(recipeIndex), this.anyMatch)) {
return this.anyMatch;
}
return !this.anyMatch;
@@ -449,6 +449,7 @@ public class GuiContainerManager {
}
private int clickHandled = 0;
private int hotkeyHandled = 0;
public final List<IContainerTooltipHandler> instanceTooltipHandlers;
public GuiContainerManager(GuiContainer screen) {
@@ -483,6 +484,7 @@ public class GuiContainerManager {
public void load() {
clickHandled = 0;
hotkeyHandled = 0;
for (IContainerObjectHandler objectHandler : objectHandlers) objectHandler.load(window);
}
@@ -499,9 +501,60 @@ public class GuiContainerManager {
public boolean lastKeyTyped(int keyID, char keyChar) {
if (keyID == 1) return false;
// hotkeys go before lastKeyTyped, otherwise a third-party handler that consumes every key blocks them
if (handleHotkeys()) return true;
for (IContainerInputHandler inputhander : inputHandlers)
if (inputhander.lastKeyTyped(window, keyChar, keyID)) return true;
return handleScreenHotkeys();
}
public boolean onHotkeyPressed() {
return handleHotkeys() || handleScreenHotkeys();
}
private boolean handleHotkeys() {
if (window instanceof IGuiHotkeyListener && ((IGuiHotkeyListener) window).onHotkeyPressed()) {
return true;
}
for (IContainerInputHandler inputhander : inputHandlers) {
if (inputhander.onHotkeyPressed(window)) {
return true;
}
}
return false;
}
private boolean handleScreenHotkeys() {
return window instanceof IGuiHotkeyListener && ((IGuiHotkeyListener) window).onScreenHotkeyPressed();
}
public boolean handleMouseHotkey() {
final int button = Mouse.getEventButton();
if (button < 0) {
return false;
}
if (!Mouse.getEventButtonState()) {
// the press has been consumed, so the release must not reach the screen either
if ((hotkeyHandled & 1 << button) == 0) {
return false;
}
hotkeyHandled &= ~(1 << button);
return true;
}
if (KeyManager.handleMouseHotkey(button, this::onHotkeyPressed)) {
hotkeyHandled |= 1 << button;
return true;
}
return false;
}
@@ -36,6 +36,18 @@ public interface IContainerInputHandler {
*/
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyID);
/**
* Called when a key or a mouse button is pressed and the event has not been handled by the first pass. Use this for
* key bindings that work globally and check them with {@link codechicken.nei.KeyManager#isKeyDown}, so they work
* the same for keyboard keys and mouse buttons.
*
* @param gui An instance of the currentscreen
* @return true to terminate further processing of this event.
*/
default boolean onHotkeyPressed(GuiContainer gui) {
return false;
}
/**
* Called when the mouse is clicked in the gui
*
@@ -0,0 +1,27 @@
package codechicken.nei.guihook;
/**
* Interface for GuiContainers to receive NEI compatible hotkey events from both keyboard keys and mouse buttons
*/
public interface IGuiHotkeyListener {
/**
* Called before the input handlers when a key or a mouse button is pressed, check key bindings with
* {@link codechicken.nei.KeyManager#isKeyDown}
*
* @return true to terminate further processing of this event.
*/
default boolean onHotkeyPressed() {
return false;
}
/**
* Called after the input handlers for screen-wide hotkeys that do not depend on the hovered object, so they never
* take priority over more specific actions
*
* @return true to terminate further processing of this event.
*/
default boolean onScreenHotkeyPressed() {
return false;
}
}
@@ -0,0 +1,65 @@
package codechicken.nei.moboverlay;
import net.minecraft.client.renderer.GLAllocation;
import org.lwjgl.opengl.GL11;
/** The mob spawn overlay quads, compiled into a GL display list. */
public final class MobOverlayDisplayList {
private int listId = -1;
private int modesVersion;
private MobOverlayStyle style;
private int minX;
private int minY;
private int minZ;
private long compiledTicks;
/**
* Compiles after a {@link MobOverlayScan#getModesVersion} change are debounced to one per {@code MIN_COMPILE_TICKS}
* ticks, because the version changes several times per second while the player moves fast, and a compile takes a
* millisecond or more.
*/
public boolean isCompileDue(int currentModesVersion, MobOverlayStyle currentStyle, long worldTicks) {
final int MIN_COMPILE_TICKS = 5;
if (listId < 0 || style != currentStyle) return true;
// after a dimension change or a server switch, `worldTicks` can be lower than `compiledTicks`
final boolean isWaitOver = worldTicks - compiledTicks >= MIN_COMPILE_TICKS || worldTicks < compiledTicks;
return modesVersion != currentModesVersion && isWaitOver;
}
/**
* Quad positions are relative to block ({@link MobOverlayScan#getMinX}, {@link MobOverlayScan#getMinY},
* {@link MobOverlayScan#getMinZ}) of {@code scan}.
*/
public void beginCompile(MobOverlayScan scan, MobOverlayStyle newStyle, long worldTicks) {
if (listId < 0) listId = GLAllocation.generateDisplayLists(1);
modesVersion = scan.getModesVersion();
style = newStyle;
minX = scan.getMinX();
minY = scan.getMinY();
minZ = scan.getMinZ();
compiledTicks = worldTicks;
GL11.glNewList(listId, GL11.GL_COMPILE);
}
public void endCompile() {
GL11.glEndList();
}
/** The model view matrix is relative to block ({@code cameraX}, {@code cameraY}, {@code cameraZ}). */
public void draw(int cameraX, int cameraY, int cameraZ) {
GL11.glPushMatrix();
GL11.glTranslated(minX - cameraX, minY - cameraY, minZ - cameraZ);
GL11.glCallList(listId);
GL11.glPopMatrix();
}
public void delete() {
if (listId < 0) return;
GLAllocation.deleteDisplayLists(listId);
listId = -1;
}
}
@@ -0,0 +1,88 @@
package codechicken.nei.moboverlay;
import net.minecraft.client.renderer.Tessellator;
/** Vertex builder for one block's top face in the mob spawn overlay. */
public final class MobOverlayFace {
public static final byte OUTSIDE_SCAN = -2;
private static final int NEIGHBOURHOOD_SIZE = 3;
private final byte[] neighbourModes = new byte[NEIGHBOURHOOD_SIZE * NEIGHBOURHOOD_SIZE];
private double x;
private double y;
private double z;
/** {@code (x, y, z)} is the north-west corner of the block's top face. */
public void moveTo(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public void setNeighbourMode(int offsetX, int offsetZ, byte spawnMode) {
neighbourModes[indexNeighbour(offsetX, offsetZ)] = spawnMode;
}
public void applyColor(MobOverlayStyle.Colouring colouring, float alpha) {
final boolean isNightOnly = neighbourModes[indexNeighbour(0, 0)] == MobOverlayScan.NIGHT_ONLY;
final int rgb = switch (colouring) {
case OVERLAY -> isNightOnly ? 0xFFFF00 : 0xFF0000;
case STICKER -> isNightOnly ? 0xFFFFFF : 0xFF7373;
};
Tessellator.instance.setColorRGBA_I(rgb, Math.round(alpha * 255));
}
public boolean isJoined(int offsetX, int offsetZ) {
final byte neighbourMode = neighbourModes[indexNeighbour(offsetX, offsetZ)];
// no outline along the edge of the scan
return neighbourMode == neighbourModes[indexNeighbour(0, 0)] || neighbourMode == OUTSIDE_SCAN;
}
public void addFullFace() {
addVertex(0, 0);
addVertex(0, 1);
addVertex(1, 1);
addVertex(1, 0);
}
/** Add one cell of a 3 x 3 grid over the face. The outer rows and columns of the grid are {@code width} wide. */
public void addCell(int offsetX, int offsetZ, double width) {
final double west = computeCellStart(offsetX, width);
final double east = 1 - computeCellStart(-offsetX, width);
final double north = computeCellStart(offsetZ, width);
final double south = 1 - computeCellStart(-offsetZ, width);
addVertex(west, north);
addVertex(west, south);
addVertex(east, south);
addVertex(east, north);
}
/** The texture is upright to a player facing north (-z). */
public void addTexturedFace() {
addTexturedVertex(0, 0);
addTexturedVertex(0, 1);
addTexturedVertex(1, 1);
addTexturedVertex(1, 0);
}
/** @param offsetX 0 at the west edge, 1 at the east edge */
public void addVertex(double offsetX, double offsetZ) {
Tessellator.instance.addVertex(x + offsetX, y, z + offsetZ);
}
private void addTexturedVertex(double offsetX, double offsetZ) {
Tessellator.instance.setTextureUV(offsetX, offsetZ);
addVertex(offsetX, offsetZ);
}
private static int indexNeighbour(int offsetX, int offsetZ) {
return (offsetX + 1) * NEIGHBOURHOOD_SIZE + offsetZ + 1;
}
private static double computeCellStart(int offset, double width) {
if (offset < 0) return 0;
return offset == 0 ? width : 1 - width;
}
}
@@ -0,0 +1,353 @@
package codechicken.nei.moboverlay;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EnumCreatureType;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.IWorldAccess;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.chunk.Chunk;
import codechicken.lib.math.MathHelper;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.WorldOverlayRenderer;
/**
* Spawn modes in a box around the player, from {@link WorldOverlayRenderer#getSpawnMode}: 0 for no spawns, 1 for night
* only and 2 for any time. The box moves with the player once the player is a few blocks from the box's centre, and
* {@link #update} scans only the blocks that enter the box. {@link #update} rescans the column of a block or light
* change on the next tick. On each tick, {@link #update} also rescans a slice of the columns, for changes with no block
* update, such as a boat that moves into the box.
*/
public final class MobOverlayScan {
public static final byte NIGHT_ONLY = 1;
public static final int MIN_RADIUS = 8;
public static final int MAX_RADIUS = 64;
public static final int DEFAULT_RADIUS = 32;
public static final int MIN_VERTICAL_RADIUS = 4;
public static final int MAX_VERTICAL_RADIUS = 64;
public static final int DEFAULT_VERTICAL_RADIUS = 16;
private static final int RECENTRE_DISTANCE = 4;
private static final int VERTICAL_RECENTRE_DISTANCE = 2;
private static final int RESCAN_TICKS = 40;
private final IWorldAccess changeListener = new ChangeListener();
private final Column column = new Column();
private int radius;
private int verticalRadius;
private int span;
private int height;
// a ring buffer over the box, indexed by `findCell()`
private byte[] modes = new byte[0];
// indexed by `findColumn(x, z)`
private boolean[] dirty = new boolean[0];
private boolean hasDirtyColumns;
private int minX;
private int minY;
private int minZ;
// the slice rescan of `update()` continues from this column, numbered `(x - minX) * span + (z - minZ)`
private int nextRescanColumn;
private int modesVersion;
private boolean hasModes;
private long scannedTick;
// world of `modes`, with `changeListener` added
private World scannedWorld;
public void clear() {
if (scannedWorld != null) scannedWorld.removeWorldAccess(changeListener);
radius = 0;
verticalRadius = 0;
span = 0;
height = 0;
modes = new byte[0];
dirty = new boolean[0];
hasModes = false;
scannedWorld = null;
}
/**
* Scan every column in this call after {@link #clear}, a change of either radius, a world change, or a move of
* {@link #getSpan} blocks or more across or {@link #getHeight} blocks or more up or down. {@code newRadius} is
* clamped between {@link #MIN_RADIUS} and {@link #MAX_RADIUS}, and {@code newVerticalRadius} between
* {@link #MIN_VERTICAL_RADIUS} and {@link #MAX_VERTICAL_RADIUS}.
*/
public void update(Entity entity, int newRadius, int newVerticalRadius) {
final World world = entity.worldObj;
final int clampedRadius = (int) MathHelper.clip(newRadius, MIN_RADIUS, MAX_RADIUS);
final int clampedVerticalRadius = (int) MathHelper
.clip(newVerticalRadius, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
if (radius != clampedRadius || verticalRadius != clampedVerticalRadius) {
resize(clampedRadius, clampedVerticalRadius);
}
final int centreX = MathHelper.floor_double(entity.posX);
final int centreY = (int) MathHelper.clip(entity.posY, verticalRadius, world.getHeight() - verticalRadius);
final int centreZ = MathHelper.floor_double(entity.posZ);
final boolean isNearBox = Math.abs(centreX - radius - minX) < span && Math.abs(centreZ - radius - minZ) < span
&& Math.abs(centreY - verticalRadius - minY) < height;
if (!(hasModes && world == scannedWorld && isNearBox)) {
if (world != scannedWorld) watchWorld(world);
scanFullBox(centreX, centreY, centreZ);
return;
}
final long worldTicks = world.getTotalWorldTime();
if (worldTicks == scannedTick) return;
scannedTick = worldTicks;
boolean isChanged = moveBox(centreX, centreY, centreZ);
if (hasDirtyColumns) isChanged |= rescanDirtyColumns();
isChanged |= rescanSlice();
if (isChanged) modesVersion++;
}
/** The version changes when a spawn mode changes or the box moves. */
public int getModesVersion() {
return modesVersion;
}
public int getSpan() {
return span;
}
public int getHeight() {
return height;
}
public int getMinX() {
return minX;
}
public int getMinY() {
return minY;
}
public int getMinZ() {
return minZ;
}
/** {@code i} and {@code j} are from 0 to {@link #getSpan}, and {@code k} is from 0 to {@link #getHeight}. */
public byte readSpawnMode(int i, int k, int j) {
return modes[findCell(minX + i, minY + k, minZ + j)];
}
private void resize(int newRadius, int newVerticalRadius) {
radius = newRadius;
verticalRadius = newVerticalRadius;
span = 2 * newRadius + 1;
height = 2 * newVerticalRadius + 1;
modes = new byte[span * span * height];
dirty = new boolean[span * span];
hasModes = false;
}
private void scanFullBox(int centreX, int centreY, int centreZ) {
final long startNanos = System.nanoTime();
minX = centreX - radius;
minY = centreY - verticalRadius;
minZ = centreZ - radius;
for (int x = minX; x < minX + span; x++) {
for (int z = minZ; z < minZ + span; z++) {
scanColumn(x, z);
}
}
hasModes = true;
modesVersion++;
scannedTick = scannedWorld.getTotalWorldTime();
NEIClientConfig.logger.info(
"moboverlay.scan.full radius={} verticalRadius={} columns={} micros={}",
radius,
verticalRadius,
span * span,
(System.nanoTime() - startNanos) / 1000);
}
private void watchWorld(World world) {
if (scannedWorld != null) scannedWorld.removeWorldAccess(changeListener);
world.addWorldAccess(changeListener);
scannedWorld = world;
}
/** @return whether the box moved */
private boolean moveBox(int centreX, int centreY, int centreZ) {
boolean isMoved = false;
final int offsetX = centreX - radius - minX;
if (Math.abs(offsetX) > RECENTRE_DISTANCE) {
final int enteringX = offsetX > 0 ? minX + span : minX + offsetX;
minX += offsetX;
for (int x = enteringX; x < enteringX + Math.abs(offsetX); x++) {
for (int z = minZ; z < minZ + span; z++) {
scanColumn(x, z);
}
}
isMoved = true;
}
final int offsetZ = centreZ - radius - minZ;
if (Math.abs(offsetZ) > RECENTRE_DISTANCE) {
final int enteringZ = offsetZ > 0 ? minZ + span : minZ + offsetZ;
minZ += offsetZ;
for (int x = minX; x < minX + span; x++) {
for (int z = enteringZ; z < enteringZ + Math.abs(offsetZ); z++) {
scanColumn(x, z);
}
}
isMoved = true;
}
final int offsetY = centreY - verticalRadius - minY;
if (Math.abs(offsetY) > VERTICAL_RECENTRE_DISTANCE) {
final int enteringY = offsetY > 0 ? minY + height : minY + offsetY;
minY += offsetY;
for (int x = minX; x < minX + span; x++) {
for (int z = minZ; z < minZ + span; z++) {
column.moveTo(x, z);
column.scan(enteringY, enteringY + Math.abs(offsetY));
}
}
isMoved = true;
}
return isMoved;
}
private boolean rescanDirtyColumns() {
hasDirtyColumns = false;
boolean isChanged = false;
for (int x = minX; x < minX + span; x++) {
for (int z = minZ; z < minZ + span; z++) {
if (dirty[findColumn(x, z)]) isChanged |= scanColumn(x, z);
}
}
return isChanged;
}
private boolean rescanSlice() {
final int columnCount = span * span;
final int sliceEnd = Math.min(columnCount, nextRescanColumn + (columnCount + RESCAN_TICKS - 1) / RESCAN_TICKS);
boolean isChanged = false;
for (; nextRescanColumn < sliceEnd; nextRescanColumn++) {
final int x = minX + nextRescanColumn / span;
final int z = minZ + nextRescanColumn % span;
isChanged |= scanColumn(x, z);
}
if (nextRescanColumn == columnCount) nextRescanColumn = 0;
return isChanged;
}
/** @return whether a spawn mode changed */
private boolean scanColumn(int x, int z) {
column.moveTo(x, z);
dirty[findColumn(x, z)] = false;
return column.scan(minY, minY + height);
}
private int findColumn(int x, int z) {
return Math.floorMod(x, span) * span + Math.floorMod(z, span);
}
private int findCell(int x, int y, int z) {
return findColumn(x, z) * height + Math.floorMod(y, height);
}
/**
* {@link #moveTo} reads the chunk, the height map and the biome of a column, and {@link #scan} reads the column's
* blocks.
*/
private final class Column {
private int x;
private int z;
private Chunk chunk;
private int topY;
private boolean canBiomeSpawn;
private void moveTo(int newX, int newZ) {
x = newX;
z = newZ;
final BiomeGenBase biome = scannedWorld.getBiomeGenForCoords(x, z);
canBiomeSpawn = !biome.getSpawnableList(EnumCreatureType.monster).isEmpty()
&& biome.getSpawningChance() > 0;
chunk = scannedWorld.getChunkFromBlockCoords(x, z);
topY = Math.min(scannedWorld.getHeight() - 1, scannedWorld.getHeightValue(x, z));
}
/** @return whether a spawn mode changed */
private boolean scan(int fromY, int toY) {
boolean isChanged = false;
for (int y = fromY; y < toY; y++) {
final byte spawnMode = canBiomeSpawn && y <= topY ? WorldOverlayRenderer.getSpawnMode(chunk, x, y, z)
: 0;
final int cell = findCell(x, y, z);
isChanged |= modes[cell] != spawnMode;
modes[cell] = spawnMode;
}
return isChanged;
}
}
/** Marks the columns of the block and light changes in {@link #scannedWorld}. */
private final class ChangeListener implements IWorldAccess {
@Override
public void markBlockForUpdate(int x, int y, int z) {
markChanged(x, y, z);
}
@Override
public void markBlockForRenderUpdate(int x, int y, int z) {
markChanged(x, y, z);
}
@Override
public void markBlockRangeForRenderUpdate(int fromX, int fromY, int fromZ, int toX, int toY, int toZ) {
for (int x = Math.max(fromX, minX); x <= Math.min(toX, minX + span - 1); x++) {
for (int z = Math.max(fromZ, minZ); z <= Math.min(toZ, minZ + span - 1); z++) {
markColumn(x, z);
}
}
}
@Override
public void playSound(String soundName, double x, double y, double z, float volume, float pitch) {}
@Override
public void playSoundToNearExcept(EntityPlayer player, String soundName, double x, double y, double z,
float volume, float pitch) {}
@Override
public void spawnParticle(String particleName, double x, double y, double z, double velocityX, double velocityY,
double velocityZ) {}
@Override
public void onEntityCreate(Entity entity) {}
@Override
public void onEntityDestroy(Entity entity) {}
@Override
public void playRecord(String recordName, int x, int y, int z) {}
@Override
public void broadcastSound(int soundId, int x, int y, int z, int data) {}
@Override
public void playAuxSFX(EntityPlayer player, int sfxType, int x, int y, int z, int data) {}
@Override
public void destroyBlockPartially(int breakerId, int x, int y, int z, int progress) {}
@Override
public void onStaticEntitiesChanged() {}
private void markChanged(int x, int y, int z) {
// spawn modes depend on the block below and the block above
final boolean isInBox = x >= minX && x < minX + span && z >= minZ && z < minZ + span;
if (isInBox && y >= minY - 1 && y <= minY + height) markColumn(x, z);
}
private void markColumn(int x, int z) {
dirty[findColumn(x, z)] = true;
hasDirtyColumns = true;
}
}
}
@@ -0,0 +1,154 @@
package codechicken.nei.moboverlay;
import java.util.Locale;
import java.util.StringJoiner;
import javax.annotation.Nullable;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
/**
* Each style is a mark drawn on every block where mobs can spawn. {@code world.overlays.mobOverlayStyle} is saved as
* {@link #formatName()}.
*/
public enum MobOverlayStyle {
/** NEI's original cross from corner to corner. */
X(GL11.GL_LINES) {
@Override
public void addVertices(MobOverlayFace face) {
face.addVertex(0, 0);
face.addVertex(1, 1);
face.addVertex(1, 0);
face.addVertex(0, 1);
}
},
/**
* A light tint, with a stronger outline around each area of one spawn mode, like a connected texture.
*/
CONNECTED(GL11.GL_QUADS) {
@Override
public void addVertices(MobOverlayFace face) {
final double OUTLINE_WIDTH = 1.0 / 16;
face.addFullFace();
face.applyColor(Colouring.OVERLAY, MARK_ALPHA);
for (int offsetX = -1; offsetX <= 1; offsetX++) {
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
// a corner cell is drawn when either edge next to the corner is outlined, and on an inside corner
final boolean isOutline = !(face.isJoined(offsetX, offsetZ) && face.isJoined(offsetX, 0)
&& face.isJoined(0, offsetZ));
if (isOutline) face.addCell(offsetX, offsetZ, OUTLINE_WIDTH);
}
}
}
},
/** A small square in the middle of the block, with no tint. */
SQUARE(GL11.GL_QUADS) {
@Override
public void addVertices(MobOverlayFace face) {
final double CENTRE = 0.5;
final double HALF_SIDE = 7.0 / 64;
face.addVertex(CENTRE - HALF_SIDE, CENTRE - HALF_SIDE);
face.addVertex(CENTRE - HALF_SIDE, CENTRE + HALF_SIDE);
face.addVertex(CENTRE + HALF_SIDE, CENTRE + HALF_SIDE);
face.addVertex(CENTRE + HALF_SIDE, CENTRE - HALF_SIDE);
}
},
/** The GTNH logo without the globe. */
GTNH_LOGO("gtnh_logo", Colouring.OVERLAY) {
@Override
public void addVertices(MobOverlayFace face) {
face.addTexturedFace();
}
},
/** The pufferfish sticker. */
PUFFERFISH("pufferfish", Colouring.STICKER) {
@Override
public void addVertices(MobOverlayFace face) {
face.addTexturedFace();
}
};
public static final MobOverlayStyle DEFAULT = CONNECTED;
private static final float TINT_ALPHA = 0.15F;
// the square and the outline cover a small part of the block and need more opacity than the tint to stay visible
private static final float MARK_ALPHA = 0.45F;
public final int glMode;
@Nullable
public final ResourceLocation texture;
public final Colouring colouring;
MobOverlayStyle(int glMode) {
this.glMode = glMode;
this.texture = null;
this.colouring = Colouring.OVERLAY;
}
MobOverlayStyle(String textureName, Colouring colouring) {
this.glMode = GL11.GL_QUADS;
this.texture = new ResourceLocation("nei", "textures/overlay/" + textureName + ".png");
this.colouring = colouring;
}
/** Case-insensitive. Returns {@link #DEFAULT} for an unknown name. */
public static MobOverlayStyle parseStyle(String rawStyle) {
for (MobOverlayStyle candidate : values()) {
if (candidate.formatName().equalsIgnoreCase(rawStyle)) return candidate;
}
return DEFAULT;
}
/** @return the {@link #formatName} of every style, separated by commas */
public static String formatAllNames() {
final StringJoiner names = new StringJoiner(", ");
for (MobOverlayStyle style : values()) {
names.add(style.formatName());
}
return names.toString();
}
public String formatName() {
return name().toLowerCase(Locale.ROOT);
}
public String getLangKey() {
return switch (this) {
case X -> "nei.options.world.overlays.mobOverlayStyle.x";
case CONNECTED -> "nei.options.world.overlays.mobOverlayStyle.connected";
case SQUARE -> "nei.options.world.overlays.mobOverlayStyle.square";
case GTNH_LOGO -> "nei.options.world.overlays.mobOverlayStyle.gtnh_logo";
case PUFFERFISH -> "nei.options.world.overlays.mobOverlayStyle.pufferfish";
};
}
public float computeAlpha() {
return switch (this) {
case CONNECTED -> TINT_ALPHA;
case SQUARE -> MARK_ALPHA;
// the alpha is in the PNGs
case X, GTNH_LOGO, PUFFERFISH -> 1.0F;
};
}
public abstract void addVertices(MobOverlayFace face);
public enum Colouring {
/** Yellow where mobs spawn only at night, red where mobs spawn at any time. */
OVERLAY,
/** The texture's colours, tinted red where mobs spawn at any time. */
STICKER
}
}
@@ -122,18 +122,25 @@ public class GuiFavoriteButton extends GuiRecipeButton {
return Recipe.of(this.handlerRef);
}
public void lastKeyTyped(char keyChar, int keyID) {
@Override
public void lastKeyTyped(char keyChar, int keyID) {}
@Override
public boolean onHotkeyPressed() {
if (KeyManager.isKeyDown("bookmark.add") && NEIClientUtils.shiftKey()) {
saveRecipeInBookmark();
return true;
}
if (KeyManager.isKeyDown("bookmark.open_crafting_tree") && NEIClientUtils.shiftKey()
&& saveRecipeInBookmark()) {
final int groupId = ItemPanels.bookmarkPanel.getGrid().getNextGroupId() - 1;
GuiCraftingTree.openCraftingTreeGui(ItemPanels.bookmarkPanel.getGrid(), groupId);
return true;
}
return false;
}
@Override
@@ -346,14 +346,21 @@ public class GuiOverlayButton extends GuiRecipeButton {
}
@Override
public void lastKeyTyped(char keyChar, int keyID) {
public void lastKeyTyped(char keyChar, int keyID) {}
@Override
public boolean onHotkeyPressed() {
if (KeyManager.isKeyDown("bookmark.add") && NEIClientUtils.shiftKey()) {
saveRecipeInBookmark();
return true;
}
if (KeyManager.isKeyDown("bookmark.chat_link") && NEIClientUtils.controlKey() && NEIClientUtils.shiftKey()) {
sendRecipeChatLink();
return true;
}
return false;
}
public void saveRecipeInBookmark() {
@@ -51,6 +51,7 @@ import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.guihook.IContainerTooltipHandler;
import codechicken.nei.guihook.IGuiClientSide;
import codechicken.nei.guihook.IGuiHandleMouseWheel;
import codechicken.nei.guihook.IGuiHotkeyListener;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.ScrollBar;
@@ -62,7 +63,7 @@ import codechicken.nei.util.EmptyContainer;
import codechicken.nei.util.SlotInaccessible;
public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer implements IGuiContainerOverlay,
IGuiClientSide, IGuiHandleMouseWheel, IContainerTooltipHandler, INEIGuiHandler {
IGuiClientSide, IGuiHandleMouseWheel, IGuiHotkeyListener, IContainerTooltipHandler, INEIGuiHandler {
private static final int BORDER_PADDING = 5;
private static final int TRANSPARENCY_BORDER = 4;
@@ -199,6 +200,22 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
"recipe.search.tooltip.output",
SearchExpressionUtils.HIGHLIGHTS.RECIPE + ">"));
final int patternMode = NEIClientConfig.getIntSetting("inventory.search.patternMode");
if (patternMode != 2) {
tooltip.add(
NEIClientUtils.translate(
"recipe.search.tooltip.auxiliary",
SearchExpressionUtils.HIGHLIGHTS.RECIPE + "^"));
}
if (patternMode == 3) {
tooltip.add(
NEIClientUtils.translate(
"recipe.search.tooltip.all",
SearchExpressionUtils.HIGHLIGHTS.RECIPE + "<&"));
}
for (ISearchParserProvider provider : SearchField.searchParser.getProviders()) {
if (provider.getSearchMode() == SearchMode.PREFIX) {
addPrefixTooltip(tooltip, provider);
@@ -221,6 +238,14 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
"recipe.search.tooltip.exact",
SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\"",
SearchExpressionUtils.HIGHLIGHTS.QUOTED + "\""));
if (patternMode == 1 || patternMode == 3) {
tooltip.add(
NEIClientUtils.translate(
"recipe.search.tooltip.regex",
SearchExpressionUtils.HIGHLIGHTS.REGEX + "r/",
SearchExpressionUtils.HIGHLIGHTS.REGEX + "/"));
}
} else {
tooltip.add(NEIClientUtils.translate("recipe.search.tooltip.shift"));
}
@@ -681,6 +706,10 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
return this.handlerPages.getRecipeIndices();
}
public List<Integer> getFilteredRecipes() {
return this.handler.getFilteredRecipes();
}
@Override
public void keyTyped(char c, int i) {
@@ -697,6 +726,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
if (GuiRecipe.searchField.isVisible() && GuiRecipe.searchField.focused()) {
GuiRecipe.searchField.lastKeyTyped(i, c);
GuiRecipe.searchField.onHotkeyPressed();
return;
}
@@ -721,7 +751,24 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
if (i == mc.gameSettings.keyBindInventory.getKeyCode()) {
this.mc.displayGuiScreen(getFirstScreenGeneral());
NEICPH.sendRequestContainer();
} else if (KeyManager.isKeyDown("recipe.back")) {
}
}
@Override
public boolean onHotkeyPressed() {
if (GuiRecipe.searchField.isVisible() && GuiRecipe.searchField.onHotkeyPressed()
|| this.recipeCatalyst.isShowWidget() && this.recipeCatalyst.onHotkeyPressed()
|| this.container.onHotkeyPressed()) {
return true;
}
return false;
}
@Override
public boolean onScreenHotkeyPressed() {
if (KeyManager.isKeyDown("recipe.back")) {
this.mc.displayGuiScreen(this.prevGui);
} else if (KeyManager.isKeyDown("recipe.prev_machine")) {
prevType();
@@ -731,7 +778,11 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
prevPage();
} else if (KeyManager.isKeyDown("recipe.next_recipe")) {
nextPage();
} else {
return false;
}
return true;
}
@Override
@@ -64,6 +64,10 @@ public abstract class GuiRecipeButton extends GuiNEIButton {
public abstract void lastKeyTyped(char keyChar, int keyID);
public boolean onHotkeyPressed() {
return false;
}
public abstract void drawItemOverlay();
public void update() {
@@ -1,15 +1,10 @@
package codechicken.nei.recipe;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -31,6 +26,7 @@ import codechicken.nei.ColorUtils;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.Widget;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.event.NEIRegisterHandlerInfosEvent;
import codechicken.nei.guihook.GuiContainerManager;
@@ -48,26 +44,29 @@ public abstract class GuiRecipeTab extends Widget {
protected final String handlerName;
protected final String handlerID;
public static final int TAB_WIDTH = 24;
public static final int TAB_HEIGHT = 24;
private static final DrawableResource selectedTabImage = new DrawableBuilder(
"nei:textures/nei_tabbed_sprites.png",
0,
16,
24,
24).build();
private static final DrawableResource unselectedTabImage = new DrawableBuilder(
"nei:textures/nei_tabbed_sprites.png",
24,
16,
24,
24).build();
private boolean selected;
public abstract int getWidth();
public abstract int getHeight();
public abstract DrawableResource getSelectedTabImage();
public abstract DrawableResource getUnselectedTabImage();
protected abstract int getForegroundIconX();
protected abstract int getForegroundIconY();
public GuiRecipeTab(IRecipeHandler handler, int x, int y) {
super();
this.x = x;
this.y = y;
this.w = getWidth();
this.h = getHeight();
this.w = TAB_WIDTH;
this.h = TAB_HEIGHT;
this.handler = handler;
this.handlerName = handler.getHandlerId();
this.selected = false;
@@ -88,21 +87,16 @@ public abstract class GuiRecipeTab extends Widget {
public void drawBackground(int mouseX, int mouseY) {
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glColor4f(1, 1, 1, 1);
final DrawableResource image;
if (selected) image = getSelectedTabImage();
else image = getUnselectedTabImage();
final int iconX = x + (w - image.getWidth()) / 2;
final int iconY = y + (h - image.getHeight()) / 2;
image.draw(iconX, iconY);
final DrawableResource image = this.selected ? selectedTabImage : unselectedTabImage;
image.draw(this.x, this.y);
}
public void drawForeground(int mouseX, int mouseY) {
final int iconX = getForegroundIconX();
final int iconY = getForegroundIconY();
final int iconX = this.x + 4;
final int iconY = this.y + 4;
final FontRenderer fontRenderer = GuiDraw.fontRenderer;
final HandlerInfo handlerInfo = getHandlerInfo(handlerName, handlerID);
final HandlerInfo handlerInfo = getHandlerInfo(this.handlerName, this.handlerID);
final DrawableResource icon = handlerInfo != null ? handlerInfo.getImage() : null;
final ItemStack itemStack = handlerInfo != null ? handlerInfo.getItemStack() : null;
@@ -117,7 +111,7 @@ public abstract class GuiRecipeTab extends Widget {
if (!isEnabled) GL11.glDisable(GL12.GL_RESCALE_NORMAL);
} else {
// Text fallback
String text = handler.getRecipeName();
String text = this.handler.getRecipeName();
if (text != null && text.length() > 0) {
if (text.length() > 2) {
text = text.substring(0, 2);
@@ -126,9 +120,10 @@ public abstract class GuiRecipeTab extends Widget {
text = "??";
}
int textCenterX = x + (int) (getWidth() / 2f);
int textCenterY = y + (int) (getHeight() / 2f) - 3;
int color = selected ? ColorUtils.buttonLabelHover.getColor() : ColorUtils.buttonLabelNormal.getColor();
int textCenterX = this.x + (int) (this.w / 2f);
int textCenterY = this.y + (int) (this.h / 2f) - 3;
int color = this.selected ? ColorUtils.buttonLabelHover.getColor()
: ColorUtils.buttonLabelNormal.getColor();
fontRenderer.drawStringWithShadow(
text,
textCenterX - (int) (fontRenderer.getStringWidth(text) / 2f),
@@ -207,41 +202,27 @@ public abstract class GuiRecipeTab extends Widget {
}
public static void loadHandlerInfo() {
final boolean fromJar = NEIClientConfig.loadHandlersFromJar();
final boolean fromJar = !NEIClientConfig.handlerFile.exists();
NEIClientConfig.logger.info("Loading handler info from " + (fromJar ? "JAR" : "Config"));
handlerMap.clear();
URL handlerUrl = GuiRecipeTab.class.getResource("/assets/nei/csv/handlers.csv");
URL url;
if (fromJar) {
url = handlerUrl;
if (url == null) {
NEIClientConfig.logger.info("Invalid URL for handlers csv.");
return;
}
} else {
File handlerFile = NEIClientConfig.handlerFile;
if (!handlerFile.exists()) {
NEIClientConfig.logger.info("Config file doesn't exist, creating");
try (FileOutputStream fileOutputStream = new FileOutputStream(handlerFile.getAbsoluteFile())) {
assert handlerUrl != null;
ReadableByteChannel readableByteChannel = Channels.newChannel(handlerUrl.openStream());
FileChannel fileChannel = fileOutputStream.getChannel();
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
try {
url = NEIClientConfig.handlerFile.toURI().toURL();
} catch (MalformedURLException e) {
NEIClientConfig.logger.info("Invalid URL for handlers csv (via config).");
e.printStackTrace();
return;
}
try {
url = fromJar ? GuiRecipeTab.class.getResource("/assets/nei/csv/handlers.csv")
: NEIClientConfig.handlerFile.toURI().toURL();
} catch (MalformedURLException e) {
NEIClientConfig.logger.info("Invalid URL for handlers csv (via config).");
e.printStackTrace();
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
if (url == null) {
NEIClientConfig.logger.info("Invalid URL for handlers csv.");
return;
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
CSVParser csvParser = CSVFormat.EXCEL.builder().setHeader().setSkipHeaderRecord(true).build().parse(reader);
for (CSVRecord record : csvParser) {
final String handler = record.get("handler");
@@ -282,13 +263,12 @@ public abstract class GuiRecipeTab extends Widget {
NEIClientConfig.logger.info("Error setting handler dimensions for " + handler);
}
final boolean allowOverflow = record.isSet("useCustomScroll")
&& Boolean.parseBoolean(record.get("useCustomScroll"));
info.setAllowOverflowX(
record.isSet("allowOverflowX") ? Boolean.parseBoolean(record.get("allowOverflowX")) : true);
info.setAllowOverflowY(
record.isSet("allowOverflowY") ? Boolean.parseBoolean(record.get("allowOverflowY"))
: allowOverflow);
final boolean allowOverflow = booleanOrDefault(record, "useCustomScroll", false);
info.setAllowOverflowX(booleanOrDefault(record, "allowOverflowX", true));
info.setAllowOverflowY(booleanOrDefault(record, "allowOverflowY", allowOverflow));
info.setShowFavoritesButton(booleanOrDefault(record, "showFavorites", true));
info.setShowOverlayButton(booleanOrDefault(record, "showOverlay", true));
info.setShowBadge(booleanOrDefault(record, "showBadge", false));
handlerMap.put(handler, info);
NEIClientConfig.logger.info("Loaded " + handler);
}
@@ -325,6 +305,11 @@ public abstract class GuiRecipeTab extends Widget {
return info;
}
private static boolean booleanOrDefault(CSVRecord record, String name, boolean defaultValue) {
final String str = record.isSet(name) ? record.get(name) : null;
return str == null || str.isEmpty() ? defaultValue : Boolean.parseBoolean(str);
}
private static int intOrDefault(String str, int defaultValue) {
if (str == null || str.equals("")) return defaultValue;
try {
@@ -1,56 +0,0 @@
package codechicken.nei.recipe;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
public abstract class GuiRecipeTabCreative extends GuiRecipeTab {
public static final int TAB_WIDTH = 28;
public static final int TAB_HEIGHT = 31;
private static final DrawableResource selectedTabImage = new DrawableBuilder(
"minecraft:textures/gui/container/creative_inventory/tabs.png",
28,
32,
28,
32).build();
private static final DrawableResource unselectedTabImage = new DrawableBuilder(
"minecraft:textures/gui/container/creative_inventory/tabs.png",
28,
0,
28,
30).build();
public GuiRecipeTabCreative(IRecipeHandler handler, int x, int y) {
super(handler, x, y);
}
@Override
public int getWidth() {
return TAB_WIDTH;
}
@Override
public int getHeight() {
return TAB_HEIGHT;
}
@Override
protected int getForegroundIconX() {
return x + (w - 16) / 2;
}
@Override
protected int getForegroundIconY() {
return y + h - 22;
}
@Override
public DrawableResource getSelectedTabImage() {
return selectedTabImage;
}
@Override
public DrawableResource getUnselectedTabImage() {
return unselectedTabImage;
}
}
@@ -1,56 +0,0 @@
package codechicken.nei.recipe;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
public abstract class GuiRecipeTabJEI extends GuiRecipeTab {
public static final int TAB_WIDTH = 24;
public static final int TAB_HEIGHT = 24;
private static final DrawableResource selectedTabImage = new DrawableBuilder(
"nei:textures/nei_tabbed_sprites.png",
0,
16,
24,
24).build();
private static final DrawableResource unselectedTabImage = new DrawableBuilder(
"nei:textures/nei_tabbed_sprites.png",
24,
16,
24,
24).build();
public GuiRecipeTabJEI(IRecipeHandler handler, int x, int y) {
super(handler, x, y);
}
@Override
public int getWidth() {
return TAB_WIDTH;
}
@Override
public int getHeight() {
return TAB_HEIGHT;
}
@Override
protected int getForegroundIconX() {
return x + 4;
}
@Override
protected int getForegroundIconY() {
return y + 4;
}
@Override
public DrawableResource getSelectedTabImage() {
return selectedTabImage;
}
@Override
public DrawableResource getUnselectedTabImage() {
return unselectedTabImage;
}
}
@@ -47,11 +47,11 @@ public class GuiRecipeTabs {
};
public static int getTabWidth() {
return NEIClientConfig.useCreativeTabStyle() ? GuiRecipeTabCreative.TAB_WIDTH : GuiRecipeTabJEI.TAB_WIDTH;
return GuiRecipeTab.TAB_WIDTH;
}
public static int getTabHeight() {
return NEIClientConfig.useCreativeTabStyle() ? GuiRecipeTabCreative.TAB_HEIGHT : GuiRecipeTabJEI.TAB_HEIGHT;
return GuiRecipeTab.TAB_HEIGHT;
}
public void update(GuiRecipe<?> guiRecipe) {
@@ -85,27 +85,15 @@ public class GuiRecipeTabs {
final IRecipeHandler handler = this.currenthandlers.get(index);
final int tabX = this.area.x + (i * tabWidth);
if (NEIClientConfig.useCreativeTabStyle()) {
tabs.add(new GuiRecipeTabCreative(handler, tabX, this.area.y) {
this.tabs.add(new GuiRecipeTab(handler, tabX, this.area.y) {
@Override
public boolean onButtonPress(boolean rightclick) {
setRecipePage(index);
return true;
}
@Override
public boolean onButtonPress(boolean rightclick) {
setRecipePage(index);
return true;
}
});
} else {
tabs.add(new GuiRecipeTabJEI(handler, tabX, this.area.y) {
@Override
public boolean onButtonPress(boolean rightclick) {
setRecipePage(index);
return true;
}
});
}
});
}
}
@@ -1,5 +1,6 @@
package codechicken.nei.recipe;
import java.util.Collections;
import java.util.List;
import net.minecraft.client.gui.inventory.GuiContainer;
@@ -77,19 +78,48 @@ public interface IRecipeHandler {
List<PositionedStack> getIngredientStacks(int recipe);
/**
* Callers should read {@link #getResultStacks(int)} and {@link #getAuxiliaryStacks(int)} instead.
*
* @param recipe The recipe index to get items for.
* @return A list of the other {@link PositionedStack}s in this recipe relative to the top left corner of your
* recipe drawing space. For example fuel in furnaces.
* @return The fuel or catalysts when {@link #getResultStack(int)} is present, otherwise all the results of the
* recipe. Relative to the top left corner of your recipe drawing space.
*/
List<PositionedStack> getOtherStacks(int recipe);
default List<PositionedStack> getOtherStacks(int recipe) {
return Collections.emptyList();
}
/**
* Callers should read {@link #getResultStacks(int)} instead.
*
* @param recipe The recipe index to get the result for.
* @return The recipe result {@link PositionedStack} relative to the top left corner of your recipe drawing space.
* @return The single result, or {@code null} when the recipe has several results and returns them from
* {@link #getOtherStacks(int)}. Relative to the top left corner of your recipe drawing space.
*/
PositionedStack getResultStack(int recipe);
default PositionedStack getResultStack(int recipe) {
return null;
}
/**
* Built from {@link #getResultStack(int)} and {@link #getOtherStacks(int)}. Don't override it, return the results
* from those methods so mods that still read them see the same results.
*
* @return The results: the result stack when present, otherwise the other stacks.
*/
default List<PositionedStack> getResultStacks(int recipe) {
final PositionedStack result = getResultStack(recipe);
return result != null ? Collections.singletonList(result) : getOtherStacks(recipe);
}
/**
* Override it only when the recipe has several results and also fuel or catalysts: the results then go to
* {@link #getOtherStacks(int)} and the catalysts can't be returned from the old methods.
*
* @return The stacks that are neither ingredients nor results (fuel, catalysts): the other stacks when a result
* stack is present.
*/
default List<PositionedStack> getAuxiliaryStacks(int recipe) {
return getResultStack(recipe) != null ? getOtherStacks(recipe) : Collections.emptyList();
}
/**
* @param recipe The recipe index to get the group id for.
@@ -7,6 +7,7 @@ import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraft.item.Item;
@@ -65,19 +66,11 @@ public class Recipe {
public static RecipeId of(IRecipeHandler handler, int recipeIndex, List<PositionedStack> ingredients) {
final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName();
PositionedStack pStackResult = handler.getResultStack(recipeIndex);
final PositionedStack result = getMainResult(
handler.getResultStacks(recipeIndex),
pStack -> pStack.items[0]);
if (pStackResult == null) {
for (PositionedStack otherStack : handler.getOtherStacks(recipeIndex)) {
if (!FluidContainerRegistry.isContainer(otherStack.items[0])
|| StackInfo.getFluid(otherStack.items[0]) != null) {
pStackResult = otherStack;
break;
}
}
}
return new RecipeId(extractItem(pStackResult), handlerName, extractIngredients(ingredients));
return new RecipeId(extractItem(result), handlerName, extractIngredients(ingredients));
}
public static RecipeId of(JsonObject json) {
@@ -404,12 +397,8 @@ public class Recipe {
ingredients.add(RecipeIngredient.of(positionedStack));
}
if (handler.getResultStack(recipeIndex) != null) {
results.add(RecipeIngredient.of(handler.getResultStack(recipeIndex)));
} else {
for (PositionedStack positionedStack : handler.getOtherStacks(recipeIndex)) {
results.add(RecipeIngredient.of(positionedStack));
}
for (PositionedStack positionedStack : handler.getResultStacks(recipeIndex)) {
results.add(RecipeIngredient.of(positionedStack));
}
return new Recipe(handlerName, ingredients, results);
@@ -494,18 +483,8 @@ public class Recipe {
return null;
}
if (stack == null && this.results.length > 1) {
for (RecipeIngredient ingr : this.results) {
if (!FluidContainerRegistry.isContainer(ingr.getItemStack())
|| StackInfo.getFluid(ingr.getItemStack()) != null) {
stack = ingr.getItemStack();
break;
}
}
}
if (stack == null) {
stack = this.results[0].getItemStack();
stack = getMainResult(getResults(), RecipeIngredient::getItemStack).getItemStack();
}
int stackSize = 0;
@@ -519,6 +498,19 @@ public class Recipe {
return StackInfo.withAmount(stack, stackSize);
}
public static <T> T getMainResult(List<T> results, Function<T, ItemStack> stackOf) {
for (T result : results) {
final ItemStack stack = stackOf.apply(result);
if (!FluidContainerRegistry.isContainer(stack) || StackInfo.getFluid(stack) != null) {
return result;
}
}
return results.isEmpty() ? null : results.get(0);
}
public RecipeId getRecipeId() {
if (this.recipeId == null) {
@@ -2,14 +2,10 @@ package codechicken.nei.recipe;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -97,43 +93,30 @@ public class RecipeCatalysts {
}
public static void loadCatalystInfo() {
final boolean fromJar = NEIClientConfig.loadCatalystsFromJar();
final URL handlerUrl = RecipeCatalysts.class.getResource("/assets/nei/csv/catalysts.csv");
final File catalystFile = NEIClientConfig.catalystFile;
final boolean fromJar = !catalystFile.exists();
final Map<String, CatalystInfoList> calculatedCatalysts = new HashMap<>();
NEIClientConfig.logger.info("Loading catalyst info from " + (fromJar ? "JAR" : "Config"));
resolveAdderQueue(calculatedCatalysts);
URL url;
if (fromJar) {
url = handlerUrl;
if (url == null) {
NEIClientConfig.logger.warn("Invalid URL for catalysts csv.");
return;
}
} else {
File catalystFile = NEIClientConfig.catalystFile;
if (!catalystFile.exists()) {
NEIClientConfig.logger.info("Config file doesn't exist, creating");
try (FileOutputStream fileOutputStream = new FileOutputStream(catalystFile.getAbsoluteFile())) {
assert handlerUrl != null;
ReadableByteChannel readableByteChannel = Channels.newChannel(handlerUrl.openStream());
FileChannel fileChannel = fileOutputStream.getChannel();
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
try {
url = NEIClientConfig.catalystFile.toURI().toURL();
} catch (MalformedURLException e) {
NEIClientConfig.logger.warn("Invalid URL for catalysts csv (via config).");
e.printStackTrace();
return;
}
final URL url;
try {
url = fromJar ? RecipeCatalysts.class.getResource("/assets/nei/csv/catalysts.csv")
: catalystFile.toURI().toURL();
} catch (MalformedURLException e) {
NEIClientConfig.logger.warn("Invalid URL for catalysts csv (via config).");
e.printStackTrace();
return;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
if (url == null) {
NEIClientConfig.logger.warn("Invalid URL for catalysts csv.");
return;
}
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
CSVParser csvParser = CSVFormat.EXCEL.builder().setHeader().setSkipHeaderRecord(true).build().parse(reader);
for (CSVRecord record : csvParser) {
final String handler = record.get("handler");
@@ -141,10 +124,6 @@ public class RecipeCatalysts {
final boolean requiresMod = Boolean.parseBoolean(record.get("modRequired"));
final String excludedModId = record.get("excludedModId");
final int priority = intOrDefault(record.get("priority"), 0);
// todo
final String minVersion = record.get("minVersion");
final String maxVersion = record.get("maxVersion");
//
final boolean forceClassName = Boolean.parseBoolean(record.get("forceClassName"));
if (requiresMod && !Loader.isModLoaded(modId)) continue;
@@ -19,16 +19,30 @@ import codechicken.nei.recipe.Recipe.RecipeId;
public class RecipeItemInputHandler implements IContainerInputHandler, IContainerTooltipHandler {
public static final RecipeItemInputHandler instance = new RecipeItemInputHandler();
protected RecipeTooltipLineHandler recipeTooltipLineHandler = null;
protected RecipeItemInputHandler() {
}
public static void load() {
RecipeItemInputHandler recipeHandler = new RecipeItemInputHandler();
GuiContainerManager.addInputHandler(recipeHandler);
GuiContainerManager.addTooltipHandler(recipeHandler);
GuiContainerManager.addInputHandler(instance);
GuiContainerManager.addTooltipHandler(instance);
}
public void clearTooltipCache() {
this.recipeTooltipLineHandler = null;
}
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) {
return false;
}
@Override
public boolean onHotkeyPressed(GuiContainer gui) {
return ShortcutInputHandler.handleKeyEvent(GuiContainerManager.getStackMouseOver(gui));
}
@@ -59,16 +73,17 @@ public class RecipeItemInputHandler implements IContainerInputHandler, IContaine
: null;
if (recipeId == null) {
recipeTooltipLineHandler = null;
} else if (recipeTooltipLineHandler == null || !recipeTooltipLineHandler.getRecipeId().equals(recipeId)) {
recipeTooltipLineHandler = new RecipeTooltipLineHandler(recipeId);
}
clearTooltipCache();
} else if (this.recipeTooltipLineHandler == null
|| !this.recipeTooltipLineHandler.getRecipeId().equals(recipeId)) {
this.recipeTooltipLineHandler = new RecipeTooltipLineHandler(recipeId);
}
if (recipeTooltipLineHandler != null) {
currenttip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(recipeTooltipLineHandler));
if (this.recipeTooltipLineHandler != null) {
currenttip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.recipeTooltipLineHandler));
}
} else {
recipeTooltipLineHandler = null;
clearTooltipCache();
}
return currenttip;
@@ -0,0 +1,303 @@
package codechicken.nei.recipe;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import net.minecraft.event.ClickEvent;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.EnumChatFormatting;
import com.google.common.base.Stopwatch;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import codechicken.core.CommonUtils;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.config.GuiItemIconDumper;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.Recipe.RecipeIngredient;
import codechicken.nei.util.NBTJson;
import cpw.mods.fml.relauncher.ReflectionHelper;
/**
* Writes recipes as JSON, one object per line, with stacks in the format of {@link StackInfo#itemStackToNBT}.
*/
public class RecipeJsonWriter {
protected static Field mEUt = null;
protected static Field mDuration = null;
protected static Field mRecipe = null;
static {
try {
final ClassLoader loader = RecipeJsonWriter.class.getClassLoader();
final Class<?> gtRecipe = ReflectionHelper
.getClass(loader, "gregtech.api.util.GTRecipe", "gregtech.api.util.GT_Recipe");
final Class<?> cachedDefaultRecipe = ReflectionHelper.getClass(
loader,
"gregtech.nei.GTNEIDefaultHandler$CachedDefaultRecipe",
"gregtech.nei.GT_NEI_DefaultHandler$CachedDefaultRecipe");
mEUt = gtRecipe.getField("mEUt");
mDuration = gtRecipe.getField("mDuration");
mRecipe = cachedDefaultRecipe.getField("mRecipe");
} catch (Exception ignored) {}
}
private static final File DUMP_DIR = new File(CommonUtils.getMinecraftDir(), "dumps/recipes");
// display names by stack NBT without Count, built once per dump
private final Map<String, String> names = new HashMap<>();
private int dumped = 0;
private int skipped = 0;
public static void dump(IRecipeHandler handler, List<Integer> recipeIndices) {
final RecipeJsonWriter dumper = new RecipeJsonWriter();
final File file = new File(DUMP_DIR, fileName(handler) + ".jsonl");
try {
final Stopwatch stopwatch = Stopwatch.createStarted();
dumper.writeFile(file, Collections.singletonList(handler), h -> recipeIndices);
NEIClientConfig.logger.info("Writing " + dumper.dumped + " recipes took " + stopwatch.stop());
dumper.printDumped(file);
} catch (Exception e) {
printFailed(e);
}
}
/**
* Loads all recipes of every crafting handler and writes them either all to {@code dumps/recipes.jsonl} or each
* handler to its own file in {@code dumps/recipes}.
*/
public static void dumpAll(boolean separate) {
final RecipeJsonWriter dumper = new RecipeJsonWriter();
try {
final Stopwatch stopwatch = Stopwatch.createStarted();
final List<ICraftingHandler> handlers = GuiCraftingRecipe.getCraftingHandlers("all");
NEIClientConfig.logger.info("Loading " + handlers.size() + " recipe handlers took " + stopwatch.stop());
stopwatch.reset().start();
if (separate) {
if (DUMP_DIR.exists()) {
for (File f : DUMP_DIR.listFiles()) if (f.isFile()) f.delete();
} else DUMP_DIR.mkdirs();
for (ICraftingHandler handler : handlers) {
final String name = fileName(handler);
File file = new File(DUMP_DIR, name + ".jsonl");
for (int i = 2; file.exists(); i++) file = new File(DUMP_DIR, name + '_' + i + ".jsonl");
dumper.writeFile(file, Collections.singletonList(handler), RecipeJsonWriter::filteredRecipes);
}
NEIClientConfig.logger.info("Writing " + dumper.dumped + " recipes took " + stopwatch.stop());
dumper.printDumped(DUMP_DIR);
} else {
final File file = new File(CommonUtils.getMinecraftDir(), "dumps/recipes.jsonl");
dumper.writeFile(file, handlers, RecipeJsonWriter::filteredRecipes);
NEIClientConfig.logger.info("Writing " + dumper.dumped + " recipes took " + stopwatch.stop());
dumper.printDumped(file);
}
} catch (Exception e) {
printFailed(e);
}
}
private static String fileName(IRecipeHandler handler) {
return GuiItemIconDumper
.cleanFileName(EnumChatFormatting.getTextWithoutFormattingCodes(handler.getRecipeName()).trim());
}
private static List<Integer> filteredRecipes(IRecipeHandler handler) {
return new SearchRecipeHandler<>(handler).getFilteredRecipes();
}
private void writeFile(File file, List<? extends IRecipeHandler> handlers,
Function<IRecipeHandler, List<Integer>> recipeIndices) throws IOException {
file.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(file, StandardCharsets.UTF_8.name())) {
for (IRecipeHandler handler : handlers) {
write(writer, handler, recipeIndices.apply(handler));
}
}
}
private void printDumped(File file) {
final Path minecraftDir = CommonUtils.getMinecraftDir().getAbsoluteFile().toPath().normalize();
final String path = minecraftDir.relativize(file.getAbsoluteFile().toPath().normalize()).toString()
.replace('\\', '/');
final ChatComponentText chatLink = new ChatComponentText(path);
chatLink.getChatStyle().setChatClickEvent(new ClickEvent(ClickEvent.Action.OPEN_FILE, file.getAbsolutePath()));
chatLink.getChatStyle().setUnderlined(true);
NEIClientUtils.printChatMessage(
new ChatComponentTranslation("nei.options.tools.dump.recipes.dumped", this.dumped, chatLink));
if (this.skipped > 0) {
NEIClientUtils.printChatMessage(
new ChatComponentTranslation("nei.options.tools.dump.recipes.skipped", this.skipped));
}
}
private static void printFailed(Exception e) {
NEIClientConfig.logger.error("Error dumping recipes", e);
NEIClientUtils.printChatMessage(
new ChatComponentTranslation("nei.options.tools.dump.recipes.failed", e.getMessage()));
}
private void write(PrintWriter writer, IRecipeHandler handler, List<Integer> recipeIndices) {
for (int recipeIndex : recipeIndices) {
final String line;
try {
line = recipeJson(handler, recipeIndex);
} catch (Exception e) {
NEIClientConfig.logger.warn("Skipping recipe {} of {}", recipeIndex, handler.getRecipeName(), e);
this.skipped++;
continue;
}
writer.println(line);
this.dumped++;
}
}
private String recipeJson(IRecipeHandler handler, int recipeIndex) throws IllegalAccessException {
final Recipe recipe = Recipe.of(handler, recipeIndex);
final JsonObject json = new JsonObject();
json.addProperty("handler", recipe.getHandlerName());
json.add("ingredients", slotsJson(recipe.getIngredients(), handler instanceof ShapedRecipeHandler));
json.add("results", slotsJson(recipe.getResults(), false));
final JsonObject gt = gtJson(handler, recipeIndex);
if (gt != null) {
json.add("gt", gt);
}
return NBTJson.toJson(json);
}
/**
* Each slot's shown stack, with {@code anyOf} listing every accepted stack. In a crafting grid, an ingredient's
* container item (an empty bucket, a damaged tool) goes under {@code returns}.
*/
private JsonArray slotsJson(List<RecipeIngredient> slots, boolean craftingGrid) {
final JsonArray array = new JsonArray();
for (RecipeIngredient slot : slots) {
final ItemStack stack = slot.getItemStack();
final JsonObject json = stackJson(stack);
if (json == null) {
continue;
}
if (slot.getChance() != PositionedStack.CHANCE_FULL) {
json.addProperty("chance", slot.getChance());
}
if (slot.getPermutations().size() > 1) {
final JsonArray anyOf = new JsonArray();
for (ItemStack permutation : slot.getPermutations()) {
final JsonObject option = stackJson(permutation);
if (option != null) {
option.remove("Count");
anyOf.add(option);
}
}
json.add("anyOf", anyOf);
}
if (craftingGrid) {
final JsonObject container = stackJson(containerItem(stack));
if (container != null) {
json.add("returns", container);
}
}
array.add(json);
}
return array;
}
private JsonObject stackJson(ItemStack stack) {
final NBTTagCompound nbTag = stack != null ? StackInfo.itemStackToNBT(stack) : null;
if (nbTag == null) {
return null;
}
final NBTBase count = nbTag.getTag("Count");
nbTag.removeTag("Count");
final String name = this.names.computeIfAbsent(
nbTag.toString(),
key -> EnumChatFormatting
.getTextWithoutFormattingCodes(GuiContainerManager.itemDisplayNameShort(stack)));
if (count != null) {
nbTag.setTag("Count", count);
}
final JsonObject json = NBTJson.toJsonObject(nbTag).getAsJsonObject();
json.addProperty("name", name);
return json;
}
private static ItemStack containerItem(ItemStack stack) {
if (!stack.getItem().hasContainerItem(stack)) {
return null;
}
final boolean isPausedItemDamageSound = StackInfo.isPausedItemDamageSound();
try {
StackInfo.pauseItemDamageSound(true);
return stack.getItem().getContainerItem(stack);
} finally {
StackInfo.pauseItemDamageSound(isPausedItemDamageSound);
}
}
private static JsonObject gtJson(IRecipeHandler handler, int recipeIndex) throws IllegalAccessException {
if (mRecipe == null || !(handler instanceof TemplateRecipeHandler templateHandler)
|| recipeIndex >= templateHandler.arecipes.size()) {
return null;
}
final Object cachedRecipe = templateHandler.arecipes.get(recipeIndex);
if (!mRecipe.getDeclaringClass().isInstance(cachedRecipe)) {
return null;
}
final Object gtRecipe = mRecipe.get(cachedRecipe);
final long eut = ((Number) mEUt.get(gtRecipe)).longValue();
final long duration = ((Number) mDuration.get(gtRecipe)).longValue();
final JsonObject gt = new JsonObject();
gt.addProperty("eut", eut);
gt.addProperty("durationTicks", duration);
gt.addProperty("totalEU", eut * duration);
return gt;
}
}
@@ -35,7 +35,6 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
protected long lastUpdate = System.currentTimeMillis();
protected String recipeName = "";
protected GuiRecipe<?> gui = null;
protected RecipeHandlerRef handlerRef = null;
protected RecipeWidget widget = null;
protected RecipeId recipeId = null;
@@ -0,0 +1,108 @@
package codechicken.nei.recipe;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.gui.inventory.GuiContainer;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.guihook.IContainerInputHandler;
import codechicken.nei.guihook.IContainerTooltipHandler;
import codechicken.nei.recipe.TemplateRecipeHandler.RecipeTransferRect;
public class RecipeTransferRectHandler implements IContainerInputHandler, IContainerTooltipHandler {
private static final Map<Class<? extends GuiContainer>, Set<RecipeTransferRect>> guiMap = new ConcurrentHashMap<>();
static {
final RecipeTransferRectHandler handler = new RecipeTransferRectHandler();
GuiContainerManager.addInputHandler(handler);
GuiContainerManager.addTooltipHandler(handler);
}
public static void registerRectsToGuis(List<Class<? extends GuiContainer>> classes,
List<RecipeTransferRect> rects) {
if (classes == null) return;
for (Class<? extends GuiContainer> clazz : classes) {
guiMap.computeIfAbsent(clazz, k -> new LinkedHashSet<>()).addAll(rects);
}
}
public boolean canHandle(GuiContainer gui) {
return guiMap.containsKey(gui.getClass());
}
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) {
return false;
}
@Override
public boolean onHotkeyPressed(GuiContainer gui) {
return false;
}
@Override
public boolean mouseClicked(GuiContainer gui, int mousex, int mousey, int button) {
if (!canHandle(gui)) return false;
if (button == 0) {
return transferRect(gui, false);
} else if (button == 1) {
return transferRect(gui, true);
}
return false;
}
private boolean transferRect(GuiContainer gui, boolean usage) {
final int[] offset = RecipeInfo.getGuiOffset(gui);
return TemplateRecipeHandler.transferRect(gui, guiMap.get(gui.getClass()), offset[0], offset[1], usage);
}
@Override
public void onKeyTyped(GuiContainer gui, char keyChar, int keyID) {}
@Override
public void onMouseClicked(GuiContainer gui, int mousex, int mousey, int button) {}
@Override
public void onMouseUp(GuiContainer gui, int mousex, int mousey, int button) {}
@Override
public boolean keyTyped(GuiContainer gui, char keyChar, int keyID) {
return false;
}
@Override
public boolean mouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {
return false;
}
@Override
public void onMouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {}
@Override
public List<String> handleTooltip(GuiContainer gui, int mousex, int mousey, List<String> currenttip) {
if (!canHandle(gui)) return currenttip;
if (GuiContainerManager.shouldShowTooltip(gui) && currenttip.size() == 0) {
int[] offset = RecipeInfo.getGuiOffset(gui);
currenttip = TemplateRecipeHandler
.transferRectTooltip(gui, guiMap.get(gui.getClass()), offset[0], offset[1], currenttip);
}
return currenttip;
}
@Override
public Map<String, String> handleHotkeys(GuiContainer gui, int mousex, int mousey, Map<String, String> hotkeys) {
return hotkeys;
}
@Override
public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, long heldTime) {}
}
@@ -43,6 +43,10 @@ public class ShapedRecipeHandler extends TemplateRecipeHandler {
}
}
protected static List<IRecipe> getCraftingRecipesSnapshot() {
return new ArrayList<>(CraftingManager.getInstance().getRecipeList());
}
public class CachedShapedRecipe extends CachedRecipe {
public ArrayList<PositionedStack> ingredients;
@@ -102,7 +106,7 @@ public class ShapedRecipeHandler extends TemplateRecipeHandler {
@Override
public void loadCraftingRecipes(String outputId, Object... results) {
if (outputId.equals("crafting") && getClass() == ShapedRecipeHandler.class) {
for (IRecipe irecipe : CraftingManager.getInstance().getRecipeList()) {
for (IRecipe irecipe : getCraftingRecipesSnapshot()) {
CachedShapedRecipe recipe = null;
if (irecipe instanceof ShapedRecipes shaped) {
@@ -117,7 +121,7 @@ public class ShapedRecipeHandler extends TemplateRecipeHandler {
}
}
} else if (outputId.equals("crafting2x2") && getClass() == ShapedRecipeHandler.class) {
for (IRecipe irecipe : CraftingManager.getInstance().getRecipeList()) {
for (IRecipe irecipe : getCraftingRecipesSnapshot()) {
if (irecipe instanceof ShapedRecipes shaped && shaped.recipeWidth <= 2 && shaped.recipeHeight <= 2) {
CachedShapedRecipe recipe = new CachedShapedRecipe(shaped);
@@ -133,7 +137,7 @@ public class ShapedRecipeHandler extends TemplateRecipeHandler {
@Override
public void loadCraftingRecipes(ItemStack result) {
for (IRecipe irecipe : CraftingManager.getInstance().getRecipeList()) {
for (IRecipe irecipe : getCraftingRecipesSnapshot()) {
if (NEIServerUtils.areStacksSameTypeCrafting(irecipe.getRecipeOutput(), result)) {
CachedShapedRecipe recipe = null;
if (irecipe instanceof ShapedRecipes) recipe = new CachedShapedRecipe((ShapedRecipes) irecipe);
@@ -149,7 +153,7 @@ public class ShapedRecipeHandler extends TemplateRecipeHandler {
@Override
public void loadUsageRecipes(ItemStack ingredient) {
for (IRecipe irecipe : CraftingManager.getInstance().getRecipeList()) {
for (IRecipe irecipe : getCraftingRecipesSnapshot()) {
CachedShapedRecipe recipe = null;
if (irecipe instanceof ShapedRecipes) recipe = new CachedShapedRecipe((ShapedRecipes) irecipe);
else if (irecipe instanceof ShapedOreRecipe) recipe = forgeShapedRecipe((ShapedOreRecipe) irecipe);
@@ -5,7 +5,6 @@ import java.util.Arrays;
import java.util.List;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.CraftingManager;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.ShapelessRecipes;
import net.minecraftforge.oredict.ShapelessOreRecipe;
@@ -83,7 +82,7 @@ public class ShapelessRecipeHandler extends ShapedRecipeHandler {
@Override
public void loadCraftingRecipes(String outputId, Object... results) {
if (outputId.equals("crafting") && getClass() == ShapelessRecipeHandler.class) {
List<IRecipe> allrecipes = CraftingManager.getInstance().getRecipeList();
List<IRecipe> allrecipes = getCraftingRecipesSnapshot();
for (IRecipe irecipe : allrecipes) {
CachedShapelessRecipe recipe = null;
@@ -98,7 +97,7 @@ public class ShapelessRecipeHandler extends ShapedRecipeHandler {
}
}
} else if (outputId.equals("crafting2x2") && getClass() == ShapelessRecipeHandler.class) {
List<IRecipe> allrecipes = CraftingManager.getInstance().getRecipeList();
List<IRecipe> allrecipes = getCraftingRecipesSnapshot();
for (IRecipe irecipe : allrecipes) {
CachedShapelessRecipe recipe = null;
@@ -119,7 +118,7 @@ public class ShapelessRecipeHandler extends ShapedRecipeHandler {
@Override
public void loadCraftingRecipes(ItemStack result) {
List<IRecipe> allrecipes = CraftingManager.getInstance().getRecipeList();
List<IRecipe> allrecipes = getCraftingRecipesSnapshot();
for (IRecipe irecipe : allrecipes) {
if (NEIServerUtils.areStacksSameTypeCrafting(irecipe.getRecipeOutput(), result)) {
CachedShapelessRecipe recipe = null;
@@ -136,7 +135,7 @@ public class ShapelessRecipeHandler extends ShapedRecipeHandler {
@Override
public void loadUsageRecipes(ItemStack ingredient) {
List<IRecipe> allrecipes = CraftingManager.getInstance().getRecipeList();
List<IRecipe> allrecipes = getCraftingRecipesSnapshot();
for (IRecipe irecipe : allrecipes) {
CachedShapelessRecipe recipe = null;
if (irecipe instanceof ShapelessRecipes) recipe = shapelessRecipe((ShapelessRecipes) irecipe);
@@ -269,20 +269,30 @@ public class StackInfo {
public static ItemStack getItemStackWithMinimumDamage(ItemStack[] stacks) {
ItemStack result = stacks[0];
if (stacks.length > 1) {
for (ItemStack stack : stacks) {
// to optimize compare recipes from permutation, an item with a minimum strId and damage is taken so as
// not to check everything permutation items
if (stack.getItem() != null && !ItemInfo.isHidden(stack)
&& (stack.getItemDamage() < result.getItemDamage()
|| stack.getItemDamage() == result.getItemDamage()
&& Item.itemRegistry.getNameForObject(stack.getItem())
.compareTo(Item.itemRegistry.getNameForObject(result.getItem())) < 0)) {
result = stack;
}
for (int i = 1; i < stacks.length; i++) {
final ItemStack stack = stacks[i];
if (stack.getItem() != null && isLess(stack, result) && !ItemInfo.isHidden(stack)) {
result = stack;
}
}
return result.copy();
}
private static boolean isLess(ItemStack stack, ItemStack other) {
if (stack.getItemDamage() != other.getItemDamage()) {
return stack.getItemDamage() < other.getItemDamage();
}
final String name = getItemName(stack);
if (name == null) return false;
final String otherName = getItemName(other);
return otherName == null || name.compareTo(otherName) < 0;
}
private static String getItemName(ItemStack stack) {
final Item item = stack.getItem();
return item == null ? null : Item.itemRegistry.getNameForObject(item);
}
}
@@ -4,13 +4,12 @@ import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -40,8 +39,6 @@ import codechicken.nei.api.IOverlayHandler;
import codechicken.nei.api.IRecipeOverlayRenderer;
import codechicken.nei.api.IStackPositioner;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.guihook.IContainerInputHandler;
import codechicken.nei.guihook.IContainerTooltipHandler;
import cpw.mods.fml.common.FMLLog;
/**
@@ -176,6 +173,16 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
return null;
}
/**
* Read only when {@link #getResult()} is {@code null} and {@link #getOtherStacks()} returns the results, see
* {@link IRecipeHandler#getAuxiliaryStacks(int)}.
*
* @return The fuel or catalysts of a recipe with several results.
*/
public List<PositionedStack> getAuxiliaries() {
return Collections.emptyList();
}
@Deprecated
public List<PositionedStack> getCycledIngredients(int cycle, List<PositionedStack> ingredients) {
return ingredients;
@@ -262,109 +269,26 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
return rect;
}
Rectangle rect;
String outputId;
Object[] results;
public boolean contains(GuiContainer gui, int offsetx, int offsety) {
final Point pos = GuiDraw.getMousePosition();
return rect.contains(pos.x - gui.guiLeft - offsetx, pos.y - gui.guiTop - offsety);
}
public boolean open(boolean usage) {
return usage ? GuiUsageRecipe.openRecipeGui(outputId, results)
: GuiCraftingRecipe.openRecipeGui(outputId, results);
}
public Rectangle rect;
public String outputId;
public Object[] results;
}
public static class RecipeTransferRectHandler implements IContainerInputHandler, IContainerTooltipHandler {
private static final Map<Class<? extends GuiContainer>, HashSet<RecipeTransferRect>> guiMap = new ConcurrentHashMap<>();
public static void registerRectsToGuis(List<Class<? extends GuiContainer>> classes,
List<RecipeTransferRect> rects) {
if (classes == null) return;
for (Class<? extends GuiContainer> clazz : classes) {
HashSet<RecipeTransferRect> set = guiMap.computeIfAbsent(clazz, k -> new HashSet<>());
set.addAll(rects);
}
}
public boolean canHandle(GuiContainer gui) {
return guiMap.containsKey(gui.getClass());
}
@Override
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) {
if (!canHandle(gui)) return false;
if (KeyManager.isKeyDown("recipe.recipe")) {
return transferRect(gui, false);
} else if (KeyManager.isKeyDown("recipe.usage")) {
return transferRect(gui, true);
}
return false;
}
@Override
public boolean mouseClicked(GuiContainer gui, int mousex, int mousey, int button) {
if (!canHandle(gui)) return false;
if (button == 0) return transferRect(gui, false);
else if (button == 1) return transferRect(gui, true);
return false;
}
private boolean transferRect(GuiContainer gui, boolean usage) {
int[] offset = RecipeInfo.getGuiOffset(gui);
return TemplateRecipeHandler.transferRect(gui, guiMap.get(gui.getClass()), offset[0], offset[1], usage);
}
@Override
public void onKeyTyped(GuiContainer gui, char keyChar, int keyID) {}
@Override
public void onMouseClicked(GuiContainer gui, int mousex, int mousey, int button) {}
@Override
public void onMouseUp(GuiContainer gui, int mousex, int mousey, int button) {}
@Override
public boolean keyTyped(GuiContainer gui, char keyChar, int keyID) {
return false;
}
@Override
public boolean mouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {
return false;
}
@Override
public void onMouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {}
@Override
public List<String> handleTooltip(GuiContainer gui, int mousex, int mousey, List<String> currenttip) {
if (!canHandle(gui)) return currenttip;
if (GuiContainerManager.shouldShowTooltip(gui) && currenttip.size() == 0) {
int[] offset = RecipeInfo.getGuiOffset(gui);
currenttip = TemplateRecipeHandler
.transferRectTooltip(gui, guiMap.get(gui.getClass()), offset[0], offset[1], currenttip);
}
return currenttip;
}
@Override
public List<String> handleItemDisplayName(GuiContainer gui, ItemStack itemstack, List<String> currenttip) {
return currenttip;
}
@Override
public List<String> handleItemTooltip(GuiContainer gui, ItemStack itemstack, int mousex, int mousey,
List<String> currenttip) {
return currenttip;
}
@Override
public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, long heldTime) {}
}
static {
GuiContainerManager.addInputHandler(new RecipeTransferRectHandler());
GuiContainerManager.addTooltipHandler(new RecipeTransferRectHandler());
/**
* @deprecated moved to {@link codechicken.nei.recipe.RecipeTransferRectHandler}
*/
@Deprecated
public static class RecipeTransferRectHandler extends codechicken.nei.recipe.RecipeTransferRectHandler {
}
/**
@@ -636,6 +560,16 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
return arecipes.get(recipe).getOtherStacks();
}
@Override
public List<PositionedStack> getAuxiliaryStacks(int recipe) {
// goes through the handler methods, subclasses may override them instead of the cached recipe ones
if (getResultStack(recipe) != null) {
return getOtherStacks(recipe);
}
return arecipes.get(recipe).getAuxiliaries();
}
public void onUpdate() {
cycleticks++;
}
@@ -704,25 +638,19 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
return transferRect(gui, transferRects, offset.x, offset.y, usage);
}
private static boolean transferRect(GuiContainer gui, Collection<RecipeTransferRect> transferRects, int offsetx,
static boolean transferRect(GuiContainer gui, Collection<RecipeTransferRect> transferRects, int offsetx,
int offsety, boolean usage) {
Point pos = GuiDraw.getMousePosition();
Point relMouse = new Point(pos.x - gui.guiLeft - offsetx, pos.y - gui.guiTop - offsety);
for (RecipeTransferRect rect : transferRects) {
if (rect.rect.contains(relMouse) && (usage ? GuiUsageRecipe.openRecipeGui(rect.outputId, rect.results)
: GuiCraftingRecipe.openRecipeGui(rect.outputId, rect.results)))
return true;
if (rect.contains(gui, offsetx, offsety) && rect.open(usage)) return true;
}
return false;
}
private static List<String> transferRectTooltip(GuiContainer gui, Collection<RecipeTransferRect> transferRects,
int offsetx, int offsety, List<String> currenttip) {
Point pos = GuiDraw.getMousePosition();
Point relMouse = new Point(pos.x - gui.guiLeft - offsetx, pos.y - gui.guiTop - offsety);
static List<String> transferRectTooltip(GuiContainer gui, Collection<RecipeTransferRect> transferRects, int offsetx,
int offsety, List<String> currenttip) {
for (RecipeTransferRect rect : transferRects) {
if (rect.rect.contains(relMouse)) {
if (rect.contains(gui, offsetx, offsety)) {
currenttip.add(NEIClientUtils.translate("recipe.tooltip"));
break;
}
@@ -3,11 +3,9 @@ package codechicken.nei.recipe.chain;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
@@ -23,7 +21,6 @@ import codechicken.nei.ItemSorter;
import codechicken.nei.ItemStackAmount;
import codechicken.nei.ItemsTooltipLineHandler;
import codechicken.nei.ItemsTooltipLineHandler.AmountRenderer;
import codechicken.nei.ItemsTooltipLineHandler.StacksAmountRenderer;
import codechicken.nei.ItemsTooltipLineHandler.TotalAmountRenderer;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
@@ -39,25 +36,6 @@ import codechicken.nei.recipe.StackInfo;
public class RecipeChainTooltipLineHandler extends CompositeTooltipLineHandler {
protected static class MissingStacksAmountRenderer extends StacksAmountRenderer {
protected static final int MISSING_COLOR = 0xFF5555;
protected final Set<NBTTagCompound> missing;
private final Map<ItemStack, Boolean> cache = new IdentityHashMap<>();
public MissingStacksAmountRenderer(Set<NBTTagCompound> missing) {
this.missing = missing;
}
@Override
protected int getColor(ItemStack stack) {
return this.cache.computeIfAbsent(stack, key -> this.missing.contains(StackInfo.itemStackToNBT(key, false)))
? MISSING_COLOR
: super.getColor(stack);
}
}
protected static class HandlerIconRenderer extends TotalAmountRenderer {
protected static final int ICON_SIZE = 16;
@@ -285,35 +263,15 @@ public class RecipeChainTooltipLineHandler extends CompositeTooltipLineHandler {
}
}
final Set<NBTTagCompound> missing = new HashSet<>();
if (this.lastShiftKey) {
for (ItemStack stack : inputs) {
missing.add(StackInfo.itemStackToNBT(stack, false));
}
}
// items with a shortage are already in the inputs list, holding the missing amount instead of the required one
final List<ItemStack> covered = new ArrayList<>();
for (ItemStack stack : available) {
if (!missing.contains(StackInfo.itemStackToNBT(stack, false))) {
covered.add(stack);
}
}
final Comparator<ItemStack> comparator = Comparator
.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null)
.thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance);
inputs.sort(comparator);
covered.sort(comparator);
available.sort(comparator);
outputs.sort(comparator);
remainder.sort(comparator);
// missing items stay on top of the list
inputs.addAll(covered);
if (this.lastShiftKey) {
outputs.addAll(remainder);
}
@@ -332,12 +290,21 @@ public class RecipeChainTooltipLineHandler extends CompositeTooltipLineHandler {
EnumChatFormatting.BLUE,
new HandlerIconRenderer(craftingHandlers));
if (this.lastShiftKey) {
addLine(
NEIClientUtils.translate("bookmark.crafting_chain.missing"),
inputs,
true,
EnumChatFormatting.RED,
new TotalAmountRenderer());
}
addLine(
NEIClientUtils.translate("bookmark.crafting_chain.input"),
inputs,
this.lastShiftKey ? available : inputs,
this.lastShiftKey,
EnumChatFormatting.YELLOW,
this.lastShiftKey ? new MissingStacksAmountRenderer(missing) : new TotalAmountRenderer());
new TotalAmountRenderer());
}
protected static void putHandlerInfo(Map<NBTTagCompound, HandlerInfo> handlers, ItemStack stack,
@@ -41,6 +41,7 @@ import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.HandlerInfo;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.RecipeJsonWriter;
import codechicken.nei.recipe.ShapedRecipeHandler;
import codechicken.nei.recipe.TemplateRecipeHandler;
import codechicken.nei.recipe.TemplateRecipeHandler.RecipeTransferRect;
@@ -272,6 +273,8 @@ public class DebugHandlerWidget extends Widget implements IContainerTooltipHandl
API.addRecipeFilter(() -> this.duplicatesOnly ? this::isDuplicate : null);
}
y = addExportWidget("ExportRecipes", y);
this.h = 10 + LINE_HEIGHT + y;
this.container.h = this.h - INLINE_PADDING * 2 - LINE_HEIGHT;
}
@@ -332,6 +335,45 @@ public class DebugHandlerWidget extends Widget implements IContainerTooltipHandl
return y + value.h + 1;
}
private int addExportWidget(String key, int y) {
final LabelWidget label = new LabelWidget(0x888888, false) {
@Override
public List<String> handleTooltip(int mx, int my, List<String> tooltip) {
if (contains(mx, my)) {
super.handleTooltip(mx, my, tooltip);
tooltip.add(
EnumChatFormatting.GRAY + NEIClientUtils.translate("debug.RecipeHandler.export.tooltip"));
}
return tooltip;
}
};
final Button value = new Button(NEIClientUtils.translate("debug.RecipeHandler.export")) {
@Override
public boolean onButtonPress(boolean rightclick) {
if (rightclick || !(NEIClientUtils.getGuiContainer() instanceof GuiRecipe<?>recipe)) return false;
RecipeJsonWriter.dump(recipe.getHandler(), recipe.getFilteredRecipes());
return true;
}
};
value.w = 58;
value.h = 16;
value.y = label.y = y;
value.x = this.w - INLINE_PADDING * 2 - value.w - 1;
label.w = value.x;
label.updateValue(NEIClientUtils.translate(TOOLTIP_PREFIX + key));
this.container.addWidget(label);
this.container.addWidget(value);
return y + value.h + 1;
}
private int addOverrideInfoWidget(String key, int y) {
final LabelWidget label = new LabelWidget(0x888888, false);
final LabelWidget value = new LabelWidget(0xffffff, true);
@@ -512,6 +554,11 @@ public class DebugHandlerWidget extends Widget implements IContainerTooltipHandl
return false;
}
@Override
public boolean onHotkeyPressed() {
return this.record != null && this.container.onHotkeyPressed();
}
@Override
public void onGuiClick(int mx, int my) {
if (this.record != null) {
@@ -686,6 +733,10 @@ public class DebugHandlerWidget extends Widget implements IContainerTooltipHandl
});
}
public void clearGuiReferences() {
this.hoveredRecipe = null;
}
public void loadHandlerInfoPatch() {
ClientHandler.loadSettingsFile("handlers.patch", lines -> {
@@ -225,15 +225,10 @@ public class RecipeGroup {
protected MemberStacks loadStacks(int member) {
final int recipeIndex = getRecipeIndex(member);
final List<PositionedStack> inputs = new ArrayList<>(this.handler.getIngredientStacks(recipeIndex));
final PositionedStack pStackResult = this.handler.getResultStack(recipeIndex);
final List<PositionedStack> others = new ArrayList<>(this.handler.getOtherStacks(recipeIndex));
if (pStackResult != null) {
return new MemberStacks(inputs, Collections.singletonList(pStackResult), others);
}
return new MemberStacks(inputs, others, Collections.emptyList());
return new MemberStacks(
new ArrayList<>(this.handler.getIngredientStacks(recipeIndex)),
new ArrayList<>(this.handler.getResultStacks(recipeIndex)),
new ArrayList<>(this.handler.getAuxiliaryStacks(recipeIndex)));
}
// re-reads the member stacks from the handler, keeps the cached instances when nothing changed
@@ -456,25 +456,6 @@ public class RecipeWidget extends Widget {
}
}
@Override
public boolean handleKeyPress(int keyID, char keyChar) {
if (NEIClientConfig.favoritesEnabled()
&& KeyManager.isHashDown("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH)) {
final Point mouse = GuiDraw.getMousePosition();
final PositionedStack hovered = getCyclingStackMouseOver(mouse.x, mouse.y);
if (hovered != null && getPermutations(hovered).size() > 1) {
FavoriteRecipes.toggleFavoriteItem(hovered.item);
setPaused(false);
return true;
}
}
return false;
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
final Point mouse = GuiDraw.getMousePosition();
@@ -490,6 +471,27 @@ public class RecipeWidget extends Widget {
}
@Override
public boolean onHotkeyPressed() {
final Point mouse = GuiDraw.getMousePosition();
if (NEIClientConfig.favoritesEnabled()
&& KeyManager.isHashDown("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH)) {
final PositionedStack hovered = getCyclingStackMouseOver(mouse.x, mouse.y);
if (hovered != null && getPermutations(hovered).size() > 1) {
FavoriteRecipes.toggleFavoriteItem(hovered.item);
setPaused(false);
return true;
}
}
return forEachButtons(
button -> button.contains(mouse.x, mouse.y) && button.onHotkeyPressed() ? true : null,
false);
}
@Override
public boolean handleClick(int mouseX, int mouseY, int button) {
@@ -2,15 +2,12 @@ package codechicken.nei.search;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import codechicken.nei.ItemList.EverythingItemFilter;
import codechicken.nei.SearchTokenParser;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.filter.AllMultiRecipeFilter;
import codechicken.nei.filter.AllOthersRecipeFilter;
import codechicken.nei.filter.AnyOthersRecipeFilter;
import codechicken.nei.filter.RecipeFilter;
import codechicken.nei.filter.RecipeFilter.FilterContext;
@@ -51,11 +48,7 @@ public class RecipeFilterVisitor extends AbstractSearchExpressionVisitor<IRecipe
case 1:
return new RecipeFilter(FilterContext.OUTPUT, !ctx.allRecipe, itemFilter);
case 2:
return getAllOrAnyFilter(
ctx.allRecipe,
itemFilter,
AnyOthersRecipeFilter::new,
AllOthersRecipeFilter::new);
return new RecipeFilter(FilterContext.AUXILIARY, !ctx.allRecipe, itemFilter);
// Doesn't support all by default
case 3:
return new RecipeFilter(FilterContext.ANY, !ctx.allRecipe, itemFilter);
@@ -64,15 +57,6 @@ public class RecipeFilterVisitor extends AbstractSearchExpressionVisitor<IRecipe
}
}
private IRecipeFilter getAllOrAnyFilter(boolean allRecipe, ItemFilter itemFilter,
Function<ItemFilter, IRecipeFilter> createAnyFilter, Function<ItemFilter, IRecipeFilter> createAllFilter) {
if (allRecipe) {
return createAllFilter.apply(itemFilter);
} else {
return createAnyFilter.apply(itemFilter);
}
}
private IRecipeFilter constructFilter(List<IRecipeFilter> filters) {
if (!filters.isEmpty()) {
// Propagate the result up
@@ -379,7 +379,7 @@ public class ItemUntranslator {
LanguageRegistryPatch.secondLanguageListCache = null;
this.processedNames.clear();
if (ClientHandler.loadSettingsFile(CONFIG_FILE, null, this::parseStream)
if (ClientHandler.loadSettingsFile(CONFIG_FILE, this::parseStream)
|| ClientHandler.loadSettingsResource(CONFIG_FILE, this::parseStream)) {
NEIClientConfig.logger.info("Untranslator loaded");
}
@@ -56,18 +56,24 @@ public class NEIKeyboardUtils {
return keyText.toString();
}
public static String getKeyName(int keyBind) {
keyBind = keyHashMap.getOrDefault(keyBind, keyBind);
StringJoiner keyText = new StringJoiner(" + ");
String hashText = getHashName(keyBind);
int keyID = unhash(keyBind);
/**
* Mouse buttons are negative key codes, so the hash can not be packed into them and is passed separately.
*/
public static String getKeyName(int keyCode, int hash) {
if (hash == 0 && keyHashMap.containsKey(keyCode)) {
hash = keyHashMap.get(keyCode);
keyCode = Keyboard.CHAR_NONE;
}
final StringJoiner keyText = new StringJoiner(" + ");
final String hashText = getHashName(hash);
if (!hashText.isEmpty()) {
keyText.add(hashText);
}
if (keyID != Keyboard.CHAR_NONE || hashText.isEmpty()) {
keyText.add(keyID < 0 ? NEIMouseUtils.getKeyName(keyID + 100) : Keyboard.getKeyName(keyID));
if (keyCode != Keyboard.CHAR_NONE || hashText.isEmpty()) {
keyText.add(keyCode < 0 ? NEIMouseUtils.getKeyName(keyCode + 100) : Keyboard.getKeyName(keyCode));
}
return keyText.toString();
@@ -121,6 +121,12 @@ RETURN
list m_handleMouseInput
ALOAD 0
GETFIELD net/minecraft/client/gui/inventory/GuiContainer.manager : Lcodechicken/nei/guihook/GuiContainerManager;
INVOKEVIRTUAL codechicken/nei/guihook/GuiContainerManager.handleMouseHotkey ()Z
IFEQ LCONT
RETURN
LCONT
ALOAD 0
INVOKESPECIAL net/minecraft/client/gui/GuiScreen.func_146274_d ()V
ALOAD 0
GETFIELD net/minecraft/client/gui/inventory/GuiContainer.manager : Lcodechicken/nei/guihook/GuiContainerManager;
@@ -52,11 +52,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=NEI volby
nei.options.back=Zpět
nei.options.world=Svět
nei.options.global=Globální
nei.options.global.tip.0=Nastavení se aplikuje pro všechny světy
nei.options.global.tip.1=Klikni na W vedle :option pro povolení :override pro tento svět
nei.options.wbutton.tip.0=Používám globální nastavení
nei.options.wbutton.tip.1=Přepisuji globální nastavení
nei.options.keys=Nastavení kláves
nei.options.keys.gui=NEI Inventář
@@ -56,11 +56,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=NEI-Optionen
nei.options.back=Zurück
nei.options.world=Welt
nei.options.global=Global
nei.options.global.tip.0=Optionen werden auf alle Welten angewandt
nei.options.global.tip.1=Klicke W neben einer:Option , um das Übergehen: für diese Welt zu aktivieren
nei.options.wbutton.tip.0=Nutzt globale Einstellungen
nei.options.wbutton.tip.1=Übergeht globale Einstellungen
nei.options.keys=Tastenbelegung
nei.options.keys.gui=NEI Inventar
+29 -14
View File
@@ -48,6 +48,7 @@ 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
nei.bookmark.crafting_chain.needed=Required Crafts
nei.bookmark.tree.title=Crafting Tree
nei.bookmark.tree.empty=No crafting chain to display
@@ -212,11 +213,6 @@ nei.options=NEI Options
nei.options.back=Back
nei.options.save=Save
nei.options.world=World
nei.options.global=Global
nei.options.global.tip.0=Options apply to all worlds
nei.options.global.tip.1=Click the W beside an:option to enable an:override for this world
nei.options.wbutton.tip.0=Using global settings
nei.options.wbutton.tip.1=Overriding global setting
nei.options.keys=KeyBindings
nei.options.keys.tip=Configure NEI key bindings in the Minecraft controls screen
@@ -416,9 +412,6 @@ nei.options.inventory.search.subsetsSearchMode=Item Subsets Search
nei.options.inventory.search.subsetsSearchMode.0=Always
nei.options.inventory.search.subsetsSearchMode.1=Prefix: %s
nei.options.inventory.search.subsetsSearchMode.2=Never
nei.options.inventory.guirecipe.creativeTabStyle=Creative or JEI tab style
nei.options.inventory.guirecipe.creativeTabStyle.true=Creative
nei.options.inventory.guirecipe.creativeTabStyle.false=JEI
nei.options.inventory.optimize_gui_overlap_computation=Optimize computation for GUI overlap
nei.options.inventory.optimize_gui_overlap_computation.true=Yes
nei.options.inventory.optimize_gui_overlap_computation.false=No
@@ -573,14 +566,24 @@ nei.options.world.overlays=Overlays Settings
nei.options.world.overlays.lock=Lock overlays between dimensions
nei.options.world.overlays.lock.true=Enabled
nei.options.world.overlays.lock.false=Disabled
nei.options.world.overlays.mobOverlayStyle=Mob Spawn Overlay Style
nei.options.world.overlays.mobOverlayStyle.x=X
nei.options.world.overlays.mobOverlayStyle.connected=Connected
nei.options.world.overlays.mobOverlayStyle.square=Square
nei.options.world.overlays.mobOverlayStyle.gtnh_logo=GTNH Logo
nei.options.world.overlays.mobOverlayStyle.pufferfish=Pufferfish
nei.options.world.overlays.mobOverlayRadius=Mob Spawn Overlay Horizontal Radius
nei.options.world.overlays.mobOverlayRadius.tip=Horizontal block radius scanned around you. Range 8-64 blocks
nei.options.world.overlays.mobOverlayVerticalRadius=Mob Spawn Overlay Vertical Radius
nei.options.world.overlays.mobOverlayVerticalRadius.tip=Blocks scanned above and below you. Range 4-64 blocks
nei.options.tools=Tools
nei.options.tools.handler_load_from_config=Load handlers from Config
nei.options.tools.handler_load_from_config.true=Config
nei.options.tools.handler_load_from_config.false=JAR
nei.options.tools.catalyst_load_from_config=Load catalysts from Config
nei.options.tools.catalyst_load_from_config.true=Config
nei.options.tools.catalyst_load_from_config.false=JAR
nei.options.tools.export_configs=Default Config Files
nei.options.tools.export_configs.button=Export
nei.options.tools.export_configs.done=%s was exported to config/NEI
nei.options.tools.export_configs.skipped=%s was not exported, see the log
nei.options.tools.export_configs.all=All files
nei.options.tools.export_configs.all.done=%s default config files were exported to config/NEI
nei.options.tools.dump=Data Dumps
nei.options.tools.dump.dump=Dump
nei.options.tools.dump.dumped=%s IDs dumped to %s
@@ -611,6 +614,12 @@ nei.options.tools.dump.handlers=NEI Handlers
nei.options.tools.dump.handlers.dumped=handlers were dumped to %s
nei.options.tools.dump.handlers.mode.0=CSV
nei.options.tools.dump.handlers.mode.1=Json
nei.options.tools.dump.recipes=All Handler Recipes
nei.options.tools.dump.recipes.combined=Combined
nei.options.tools.dump.recipes.separate=Separate
nei.options.tools.dump.recipes.dumped=%s recipes were dumped to %s
nei.options.tools.dump.recipes.skipped=%s recipes threw an error and were skipped, see the log
nei.options.tools.dump.recipes.failed=Failed to dump recipes: %s
nei.debug.RecipeHandler.title=Debug Recipe Handler
nei.debug.RecipeHandler.copy=Copy
@@ -632,6 +641,9 @@ nei.debug.RecipeHandler.DuplicatesOnly=Duplicates Only
nei.debug.RecipeHandler.AllowOverflowY=Allow Overflow Y
nei.debug.RecipeHandler.AllowOverflowX=Allow Overflow X
nei.debug.RecipeHandler.Override=Override
nei.debug.RecipeHandler.ExportRecipes=Export Recipes
nei.debug.RecipeHandler.export=Export
nei.debug.RecipeHandler.export.tooltip=Exports the currently visible recipes to JSON.
nei.debug.RecipeHandler.checkbox.on=On
nei.debug.RecipeHandler.checkbox.off=Off
nei.debug.RecipeHandler.yes=Yes
@@ -647,6 +659,8 @@ nei.recipe.search.tooltip.name=Filter Syntax
nei.recipe.search.tooltip.shift=§7Hold §6Shift§7 for more info
nei.recipe.search.tooltip.input=%s§ftext§8 - §7search in inputs
nei.recipe.search.tooltip.output=%s§ftext§8 - §7search in outputs
nei.recipe.search.tooltip.auxiliary=%s§ftext§8 - §7search in other stacks (fuel, catalysts)
nei.recipe.search.tooltip.all=%s§ftext§8 - §7all inputs must match (same with > and ^)
nei.recipe.search.tooltip.prefix=%stext§8 - §7%s
nei.recipe.search.tooltip.prefix.modName=search by mod
@@ -660,6 +674,7 @@ nei.recipe.search.tooltip.exclude=%s§ftext§8 - §7exclude items with text
nei.recipe.search.tooltip.exclude_recipe=%s§ftext§8 - §7exclude recipes with text
nei.recipe.search.tooltip.or=§fa%s§fb§8 - §7match a or b
nei.recipe.search.tooltip.exact=%s§ftext%s§8 - §7match exact phrase
nei.recipe.search.tooltip.regex=%s§ftext%s§8 - §7match regular expression
nei.recipe.shaped=Shaped Crafting
nei.recipe.shapeless=Shapeless Crafting
@@ -55,11 +55,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=NEI Valikud
nei.options.back=Tagasi
nei.options.world=Maailm
nei.options.global=Globaalne
nei.options.global.tip.0=Valikud mõjutavad kõiki maailmi
nei.options.global.tip.1=Vajuta W kõrval:valiku, et ületeh:ületeha see maailm
nei.options.wbutton.tip.0=Kasutab globaalseid seadeid
nei.options.wbutton.tip.1=Globaalsete seadmete ületegemine
nei.options.keys=Klahviseosed
nei.options.keys.gui=NEI Seljakott
@@ -53,11 +53,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=NEI Options
nei.options.back=Retour
nei.options.world=Monde
nei.options.global=Global
nei.options.global.tip.0=Les options s'appliquent à tous les mondes
nei.options.global.tip.1=Cliquez sur le "W" à coté d'une option pour l'activer pour ce monde
nei.options.wbutton.tip.0=Utilisation des paramètres globaux
nei.options.wbutton.tip.1=Annuler l'utilisation des paramètres globaux
nei.options.keys=Raccourcis clavier
nei.options.keys.gui=NEI Inventaire
@@ -56,11 +56,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=Opzioni NEI
nei.options.back=Indietro
nei.options.world=Mondo
nei.options.global=Globale
nei.options.global.tip.0=Opzioni cambiano su tutti i mondi
nei.options.global.tip.1=Clicca la W vicino a una:opzione per impostarla:solo su questo mondo
nei.options.wbutton.tip.0=Usando le opzioni globali
nei.options.wbutton.tip.1=Sovrascrivendo le opzioni globali
nei.options.keys=Controlli
nei.options.keys.gui=NEI Inventario
@@ -51,11 +51,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=NEI 옵션
nei.options.back=뒤로
nei.options.world=세계
nei.options.global=글로벌
nei.options.global.tip.0= 모든세계의 NEI의 옵션을 적용합니다
nei.options.global.tip.1= 월드의 설정을 재정의 하려면 w를 클릭
nei.options.wbutton.tip.0=글로벌로 세팅
nei.options.wbutton.tip.1=전역 설정
nei.options.keys=키 바인딩
nei.options.keys.gui=NEI 인벤토리
@@ -52,11 +52,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=Opcje NEI
nei.options.back=Wstecz
nei.options.world=Światowe
nei.options.global=Globalne
nei.options.global.tip.0=Opcje działają we wszystkich światach
nei.options.global.tip.1=Naciśnij 'W' obok opcji by włączyć nadpis dla tego świata
nei.options.wbutton.tip.0=Używanie ustawień globalnych
nei.options.wbutton.tip.1=Nadpisywanie ustawienia globalnego
nei.options.keys=Ustawienia klawiszy
nei.options.keys.gui=NEI Ekwipunek
@@ -52,11 +52,6 @@ nei.chat.chunkoverlay.3=Chunk Overlay: GT Ore Veins
nei.options=Opções do NEI
nei.options.back=Voltar
nei.options.world=Mundo
nei.options.global=Global
nei.options.global.tip.0=Opções se aplicam a todos os mundos
nei.options.global.tip.1=Clique no W do lado de uma:opção para permitir uma :ultrapassagem para esse mundo.
nei.options.wbutton.tip.0=Usando configurações globais
nei.options.wbutton.tip.1=Substituindo configurações globais
nei.options.keys=Atalhos de Teclado
nei.options.keys.gui=NEI Inventário
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more