mirror of
https://github.com/GTNewHorizons/NotEnoughItems.git
synced 2026-11-15 01:34:42 +00:00
Compare commits
6
Commits
mu
..
2.8.172-GTNH
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dc6be839b | ||
|
|
030795e3c7 | ||
|
|
897a22ad17 | ||
|
|
5faed21218 | ||
|
|
aa9da2e0a0 | ||
|
|
e9a3a494d5 |
No files matched your search
@@ -12,7 +12,6 @@ dependencies {
|
||||
|
||||
compileOnly('maven.modrinth:forge-essentials:7.5.1')
|
||||
compileOnly('com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.14-alpha:dev')
|
||||
compileOnly('com.ruling_0:MaterialLib:0.24.0-mu:dev')
|
||||
|
||||
antlr('org.antlr:antlr4:4.13.2')
|
||||
shadowImplementation('org.antlr:antlr4-runtime:4.13.2')
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Add any additional repositories for your dependencies here
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
name = "Modrinth"
|
||||
url = "https://api.modrinth.com/maven"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,6 @@ 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;
|
||||
@@ -37,7 +36,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;
|
||||
@@ -1026,20 +1024,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();
|
||||
|
||||
@@ -14,13 +14,11 @@ import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import codechicken.core.CommonUtils;
|
||||
import codechicken.core.launch.CodeChickenCorePlugin;
|
||||
import codechicken.nei.api.API;
|
||||
import codechicken.nei.api.IConfigureNEI;
|
||||
import codechicken.nei.asm.NEICorePlugin;
|
||||
import codechicken.nei.config.IMCHandler;
|
||||
import codechicken.nei.guihook.HideousLinkedList;
|
||||
import codechicken.nei.recipe.GuiRecipeTab;
|
||||
import codechicken.nei.recipe.stackinfo.MaterialLibStackStringifyHandler;
|
||||
import cpw.mods.fml.client.FMLFileResourcePack;
|
||||
import cpw.mods.fml.client.FMLFolderResourcePack;
|
||||
import cpw.mods.fml.common.DummyModContainer;
|
||||
@@ -116,10 +114,6 @@ public class NEIModContainer extends DummyModContainer {
|
||||
IMCForNEI.IMCSender();
|
||||
}
|
||||
ServerHandler.load();
|
||||
|
||||
if (Loader.isModLoaded("materiallib")) {
|
||||
API.registerStackStringifyHandler(new MaterialLibStackStringifyHandler());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -48,15 +48,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
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -542,22 +541,14 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
final AtomicInteger resolved = new AtomicInteger();
|
||||
final AtomicInteger invalid = new AtomicInteger();
|
||||
|
||||
Stream.of(dir.listFiles()).filter(file -> !file.isDirectory()).forEach(file -> {
|
||||
ClientHandler.loadSettingsFile(
|
||||
"subsets/" + file.getName(),
|
||||
lines -> parseFile(file.getName(), lines.collect(Collectors.toList()), resolved, invalid));
|
||||
lines -> parseFile(file.getName(), lines.collect(Collectors.toList())));
|
||||
});
|
||||
|
||||
if (resolved.get() + invalid.get() > 0) {
|
||||
NEIClientConfig.logger.info("Resolved {} MaterialLib entries ({} invalid)", resolved.get(), invalid.get());
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseFile(String resource, List<String> itemStrings, AtomicInteger resolved,
|
||||
AtomicInteger invalid) {
|
||||
private static void parseFile(String resource, List<String> itemStrings) {
|
||||
final JsonParser parser = new JsonParser();
|
||||
final int dotIndex = resource.lastIndexOf('.');
|
||||
final String subsetNamespace = dotIndex > 0 ? resource.substring(0, dotIndex) : resource;
|
||||
@@ -587,13 +578,7 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
|
||||
final NBTBase nbt = NBTJson.toNbt(parser.parse(itemStr));
|
||||
|
||||
if (nbt instanceof NBTTagCompound tag) {
|
||||
final ItemStack stack = StackInfo.loadFromNBT(tag);
|
||||
|
||||
if (tag.getString("strId").startsWith(StackInfo.MATERIALLIB_PREFIX)) {
|
||||
(stack != null ? resolved : invalid).incrementAndGet();
|
||||
}
|
||||
|
||||
((ItemStackSet) processedTag.filter).add(stack);
|
||||
((ItemStackSet) processedTag.filter).add(StackInfo.loadFromNBT(tag));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected NBTTagCompound but got " + nbt.getClass().getSimpleName());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -162,6 +162,19 @@ public class WidgetContainer extends Widget {
|
||||
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -681,6 +682,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 +702,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 +727,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 +754,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() {
|
||||
|
||||
@@ -29,6 +29,11 @@ public class RecipeItemInputHandler implements IContainerInputHandler, IContaine
|
||||
|
||||
@Override
|
||||
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHotkeyPressed(GuiContainer gui) {
|
||||
return ShortcutInputHandler.handleKeyEvent(GuiContainerManager.getStackMouseOver(gui));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,6 @@ import codechicken.nei.util.NBTHelper;
|
||||
|
||||
public class StackInfo {
|
||||
|
||||
/// Prefix of the MaterialLib reference form `ml:<MaterialName>:<shapeToken>`, which config entries carry in
|
||||
/// place of an id and metadata.
|
||||
public static final String MATERIALLIB_PREFIX = "ml:";
|
||||
|
||||
private static final FluidStack NULL_FLUID = new FluidStack(FluidRegistry.WATER, 0);
|
||||
public static final List<IStackStringifyHandler> stackStringifyHandlers = new ArrayList<>();
|
||||
private static final Map<String, HashMap<String, String[]>> guidfilters = new HashMap<>();
|
||||
|
||||
@@ -287,6 +287,11 @@ public abstract class TemplateRecipeHandler implements ICraftingHandler, IUsageH
|
||||
|
||||
@Override
|
||||
public boolean lastKeyTyped(GuiContainer gui, char keyChar, int keyCode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHotkeyPressed(GuiContainer gui) {
|
||||
if (!canHandle(gui)) return false;
|
||||
|
||||
if (KeyManager.isKeyDown("recipe.recipe")) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package codechicken.nei.recipe.stackinfo;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import com.ruling_0.materiallib.api.StackResolver;
|
||||
|
||||
import codechicken.nei.api.IStackStringifyHandler;
|
||||
import codechicken.nei.recipe.StackInfo;
|
||||
|
||||
/// Resolves config entries written in the MaterialLib reference form `ml:<MaterialName>:<shapeToken>` (see
|
||||
/// [StackInfo#MATERIALLIB_PREFIX]) through [StackResolver], so an entry keeps naming the same item across sessions
|
||||
/// that renumber item metadata. The resolved stack carries the metadata MaterialLib currently assigns, ignoring any
|
||||
/// `Damage` on the entry. Entries in any other form are left to the other handlers.
|
||||
///
|
||||
/// Registered only behind a `Loader.isModLoaded("materiallib")` gate, so this class never classloads without
|
||||
/// MaterialLib present.
|
||||
public class MaterialLibStackStringifyHandler implements IStackStringifyHandler {
|
||||
|
||||
@Override
|
||||
public ItemStack convertNBTToItemStack(NBTTagCompound nbtTag) {
|
||||
final String strId = nbtTag.getString("strId");
|
||||
|
||||
if (!strId.startsWith(StackInfo.MATERIALLIB_PREFIX)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String[] parts = strId.split(":");
|
||||
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ItemStack stack = StackResolver.getStack(parts[1], parts[2], nbtTag.getInteger("Count"));
|
||||
|
||||
if (stack != null && nbtTag.hasKey("tag")) {
|
||||
stack.setTagCompound((NBTTagCompound) nbtTag.getCompoundTag("tag").copy());
|
||||
}
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -611,6 +612,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 +639,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
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package codechicken.nei;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import codechicken.nei.api.IConfigureNEI;
|
||||
|
||||
class PluginLoaderTest {
|
||||
|
||||
@Test
|
||||
void loadsPluginsInParallelAndLogsFailuresWithTheGameClassLoader() throws Exception {
|
||||
ClassLoader workerClassLoader = new ClassLoader(null) {};
|
||||
Set<Thread> workers = ConcurrentHashMap.newKeySet();
|
||||
ForkJoinPool pool = new ForkJoinPool(2, owner -> {
|
||||
ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(owner);
|
||||
worker.setContextClassLoader(workerClassLoader);
|
||||
workers.add(worker);
|
||||
return worker;
|
||||
}, null, false);
|
||||
Logger logger = mock(Logger.class);
|
||||
doAnswer(invocation -> {
|
||||
assertSame(IConfigureNEI.class.getClassLoader(), Thread.currentThread().getContextClassLoader());
|
||||
return null;
|
||||
}).when(logger).error(eq("Failed to Load {}"), anyString(), any(Throwable.class));
|
||||
|
||||
List<Class<?>> plugins = new ArrayList<>();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
plugins.add(HealthyPlugin.class);
|
||||
plugins.add(ConstructorFailurePlugin.class);
|
||||
plugins.add(ConfigurationFailurePlugin.class);
|
||||
}
|
||||
List<IConfigureNEI> loaded = new CopyOnWriteArrayList<>();
|
||||
|
||||
try {
|
||||
pool.submit(() -> PluginLoader.load(plugins, true, loaded, logger)).get(10, TimeUnit.SECONDS);
|
||||
|
||||
assertEquals(16, loaded.size());
|
||||
verify(logger, times(32)).error(eq("Failed to Load {}"), anyString(), any(Throwable.class));
|
||||
workers.forEach(worker -> assertSame(workerClassLoader, worker.getContextClassLoader()));
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HealthyPlugin implements IConfigureNEI {
|
||||
|
||||
public HealthyPlugin() {
|
||||
checkClassLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig() {
|
||||
checkClassLoader();
|
||||
}
|
||||
|
||||
private static void checkClassLoader() {
|
||||
if (Thread.currentThread().getContextClassLoader() != IConfigureNEI.class.getClassLoader()) {
|
||||
throw new IllegalStateException("Plugin cannot see the game classloader");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Test plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConstructorFailurePlugin extends HealthyPlugin {
|
||||
|
||||
public ConstructorFailurePlugin() {
|
||||
throw new IllegalStateException("Constructor failed");
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConfigurationFailurePlugin extends HealthyPlugin {
|
||||
|
||||
@Override
|
||||
public void loadConfig() {
|
||||
super.loadConfig();
|
||||
throw new IllegalStateException("Configuration failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user