Compare commits

...
9 Commits
51 changed files with 927 additions and 269 deletions

No files matched your search

@@ -574,7 +574,8 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
} else if (this.sortableGroup != null) {
final int overRowIndex = Math.min(grid.getRowIndex(mousey), grid.getRows() - 1);
if (overRowIndex >= 0 && this.sortableGroup.groupId != this.grid.getRowGroupId(overRowIndex)) {
if (overRowIndex >= 0 && !isOverNamespaceControls(mousex, mousey)
&& !this.sortableGroup.matches(this.grid, this.grid.getRowGroupId(overRowIndex))) {
this.grid.moveGroup(this.sortableGroup, overRowIndex);
}
@@ -1022,14 +1023,18 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
this.mouseDownSlot = -1;
}
@Override
public boolean onMouseWheel(int shift, int mousex, int mousey) {
if ((!inEditingState() || this.sortableGroup != null) && new Rectangle4i(
private boolean isOverNamespaceControls(int mousex, int mousey) {
return new Rectangle4i(
namespacePrev.x,
namespacePrev.y,
namespaceNext.x + namespaceNext.w - namespacePrev.x,
namespacePrev.h).contains(mousex, mousey)) {
namespacePrev.h).contains(mousex, mousey);
}
@Override
public boolean onMouseWheel(int shift, int mousex, int mousey) {
if ((!inEditingState() || this.sortableGroup != null) && isOverNamespaceControls(mousex, mousey)) {
if (shift > 0) {
this.storage.prevNamespace();
@@ -1108,7 +1113,7 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
final int overRowIndex = Math.min(grid.getRowIndex(mousey), grid.getRows() - 1);
if (this.sortableGroup != null && overRowIndex >= 0
&& this.sortableGroup.groupId != this.grid.getRowGroupId(overRowIndex)) {
&& !this.sortableGroup.matches(this.grid, this.grid.getRowGroupId(overRowIndex))) {
this.grid.moveGroup(this.sortableGroup, overRowIndex);
}
+18 -22
View File
@@ -7,40 +7,36 @@ import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
public class GuiNEIButton extends GuiButton {
protected static final ResourceLocation guiTex = new ResourceLocation("textures/gui/widgets.png");
private static final DrawableResource[] BACKGROUNDS = new DrawableResource[3];
static {
for (int state = 0; state < BACKGROUNDS.length; state++) {
BACKGROUNDS[state] = new DrawableBuilder(guiTex, 0, 46 + state * 20, 200, 20).setSlices(2, 2, 2, 2).build();
}
}
public GuiNEIButton(int i, int j, int k, int l, int i1, String s) {
super(i, j, k, l, i1, s);
}
protected DrawableResource getBackground(int mouseX, int mouseY, boolean mouseOver) {
return BACKGROUNDS[getHoverState(mouseOver)];
}
public void drawButton(Minecraft minecraft, int x, int y) {
if (!visible) return;
minecraft.renderEngine.bindTexture(guiTex);
GL11.glColor4f(1, 1, 1, 1);
boolean mouseOver = x >= xPosition && y >= yPosition && x < xPosition + width && y < yPosition + height;
int k = getHoverState(mouseOver);
drawTexturedModalRect(xPosition, yPosition, 0, 46 + k * 20, width / 2, height / 2); // top left
drawTexturedModalRect(xPosition + width / 2, yPosition, 200 - width / 2, 46 + k * 20, width / 2, height / 2); // top
// right
drawTexturedModalRect(
xPosition,
yPosition + height / 2,
0,
46 + k * 20 + 20 - height / 2,
width / 2,
height / 2); // bottom left
drawTexturedModalRect(
xPosition + width / 2,
yPosition + height / 2,
200 - width / 2,
46 + k * 20 + 20 - height / 2,
width / 2,
height / 2); // bottom right
mouseDragged(minecraft, x, y);
GL11.glColor4f(1, 1, 1, 1);
getBackground(x, y, mouseOver).draw(xPosition, yPosition, width, height);
mouseDragged(minecraft, x, y);
drawContent(minecraft, x, y, mouseOver);
}
@@ -55,7 +51,7 @@ public class GuiNEIButton extends GuiButton {
return ColorUtils.buttonLabelNormal.getColor();
}
protected void drawContent(Minecraft minecraft, int y, int x, boolean mouseOver) {
protected void drawContent(Minecraft minecraft, int x, int y, boolean mouseOver) {
FontRenderer fontrenderer = minecraft.fontRenderer;
int color = getTextColour(mouseOver);
@@ -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;
@@ -61,6 +61,9 @@ public class NEICPH implements IClientPacketHandler {
case S2C.CACHE_BOOKMARK_DATA:
handleCacheBookmarkData(packet);
break;
case S2C.SYNC:
ServerSync.onReply(packet.readInt());
break;
}
}
@@ -37,7 +37,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;
@@ -1030,16 +1029,7 @@ public class NEIClientConfig {
? 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(stream, NEIModContainer.plugins, logger);
KeyManager.requestOptionsReload();
RecipeCatalysts.loadCatalystInfo();
@@ -92,9 +92,17 @@ public class NEISPH implements IServerPacketHandler {
case C2S.SEND_CHAT_ITEM_LINK:
handleSendChatItemLink(sender, packet);
break;
case C2S.SYNC:
handleSync(sender, packet.readInt());
break;
}
}
private void handleSync(EntityPlayerMP sender, int id) {
sender.sendContainerAndContentsToPlayer(sender.openContainer, sender.openContainer.getInventory());
new PacketCustom(channel, S2C.SYNC).writeInt(id).sendToPlayer(sender);
}
private void handleSendChatItemLink(EntityPlayerMP sender, PacketCustom packet) {
final NBTTagCompound payloadTag = packet.readNBTTagCompound();
NEIServerUtils.sendChatItemLink(sender, payloadTag);
@@ -24,6 +24,7 @@ public final class PacketIDs {
public static final int REQUEST_POTION_GUI = 24;
public static final int SET_DUMMY_SLOT = 25;
public static final int SEND_CHAT_ITEM_LINK = 26;
public static final int SYNC = 27;
}
public static final class S2C {
@@ -38,5 +39,6 @@ public final class PacketIDs {
public static final int SET_CREATIVE_PLUS_MODE = 23;
public static final int OPEN_POTION_GUI = 24;
public static final int CACHE_BOOKMARK_DATA = 25;
public static final int SYNC = 26;
}
}
@@ -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,38 @@
package codechicken.nei;
import java.util.Collection;
import java.util.stream.Stream;
import org.apache.logging.log4j.Logger;
import codechicken.nei.api.IConfigureNEI;
final class PluginLoader {
private PluginLoader() {}
static void load(Stream<Class<?>> plugins, Collection<IConfigureNEI> loadedPlugins, Logger logger) {
final ClassLoader classLoader = IConfigureNEI.class.getClassLoader();
plugins.forEach(clazz -> {
final Thread thread = Thread.currentThread();
final ClassLoader previousClassLoader = thread.getContextClassLoader();
// Common-pool workers may use the system loader, which cannot see FML-only classes. Keep FML's loader
// through error logging too: old Log4j can otherwise throw while resolving a plugin's stack frames.
try {
thread.setContextClassLoader(classLoader);
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);
}
} finally {
thread.setContextClassLoader(previousClassLoader);
}
});
}
}
@@ -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
@@ -0,0 +1,36 @@
package codechicken.nei;
import codechicken.lib.packet.PacketCustom;
import codechicken.nei.PacketIDs.C2S;
/**
* Waits until the server has processed everything the client sent before. The server handles slot clicks and NEI
* packets in the order they came and answers after it has sent the slot updates, so once the answer is here the client
* container holds what the server computed.
*/
public final class ServerSync {
private static int lastId;
private static volatile int awaiting;
private ServerSync() {}
public static void request() {
if (!NEIClientConfig.hasSMPCounterPart()) {
awaiting = 0;
return;
}
awaiting = ++lastId;
new PacketCustom(NEICPH.channel, C2S.SYNC).writeInt(awaiting).sendToServer();
}
public static boolean pending() {
return awaiting != 0;
}
static void onReply(int id) {
if (id == awaiting) awaiting = 0;
}
}
@@ -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;
@@ -452,8 +452,9 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
if (!this.isEmpty() && this.gridGenerator.maxAbsoluteSlotIndex > 0) {
final GroupingItem groupingItem = ItemPanels.bookmarkPanel.groupingItem;
final int sortableGroupId = ItemPanels.bookmarkPanel.sortableGroup != null
? ItemPanels.bookmarkPanel.sortableGroup.groupId
: -3;
&& ItemPanels.bookmarkPanel.sortableGroup.grid == this
? ItemPanels.bookmarkPanel.sortableGroup.groupId
: -3;
final int rowShift = this.page * this.rows;
Map<Integer, Integer> groups = this.gridGenerator.rowToGroupId;
@@ -795,8 +796,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
this.bookmarkItems.add(item);
}
sortableGroup.grid = this;
sortableGroup.groupId = groupId;
sortableGroup.update(this, groupId);
this.onItemsChanged();
this.getMask();// force generate grid
overGroupId = getRowGroupId(overRowIndex);
@@ -1285,7 +1285,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
for (BookmarksGridSlot slot : getMask()) {
Rectangle4i rect = getSlotRect(slot.slotIndex);
if (sortableGroup != null && slot.getGroupId() == sortableGroup.groupId) {
if (sortableGroup != null && sortableGroup.matches(this, slot.getGroupId())) {
NEIClientUtils.drawRect(rect.x, rect.y, rect.w, rect.h, DRAG_COLOR);
} else if (sortableItem != null && sortableItem.items.contains(slot.itemIndex)) {
NEIClientUtils.drawRect(rect.x, rect.y, rect.w, rect.h, DRAG_COLOR);
@@ -1314,7 +1314,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
final SortableItem sortableItem = LayoutManager.bookmarkPanel.sortableItem;
for (BookmarksGridSlot slot : getMask()) {
if ((sortableGroup == null || slot.getGroupId() != sortableGroup.groupId)
if ((sortableGroup == null || !sortableGroup.matches(this, slot.getGroupId()))
&& (sortableItem == null || !sortableItem.items.contains(slot.itemIndex))) {
slot.afterDraw(getSlotRect(slot.slotIndex), mouseContext);
}
@@ -1331,7 +1331,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
for (BookmarksGridSlot slot : getMask()) {
if ((sortableGroup == null || slot.getGroupId() != sortableGroup.groupId)
if ((sortableGroup == null || !sortableGroup.matches(this, slot.getGroupId()))
&& (sortableItem == null || !sortableItem.items.contains(slot.itemIndex))) {
final BookmarkItem item = slot.getBookmarkItem();
@@ -340,7 +340,8 @@ public class BookmarkGridGenerator {
0,
chainItem.getItem(),
null,
group);
group,
this.grid);
} else {
return new BookmarksGridSlot(
slotIndex,
@@ -350,7 +351,8 @@ public class BookmarkGridGenerator {
chainItem.getCalculatedMultiplier(),
chainItem.getItem(),
chainItem.calculatedType,
group);
group,
this.grid);
}
}
@@ -23,6 +23,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
import codechicken.nei.ItemPanels;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.util.NBTJson;
@@ -79,10 +80,21 @@ public class BookmarkStorage {
public int getNamespaceSize() {
if (this.namespaces.get(this.namespaces.size() - 1).size() > 0) {
this.namespaces.add(new BookmarkGrid());
} else if (this.activeNamespaceIndex == this.namespaces.size() - 2 && getActiveGrid().size() == 0) {
this.namespaces.remove(this.namespaces.size() - 1);
final int lastIndex = this.namespaces.size() - 1;
final BookmarkGrid lastGrid = this.namespaces.get(lastIndex);
final SortableGroup sortableGroup = ItemPanels.bookmarkPanel.sortableGroup;
if (lastGrid.size() > 0) {
if (sortableGroup == null || sortableGroup.grid != lastGrid || !sortableGroup.aloneInGrid) {
this.namespaces.add(new BookmarkGrid());
}
} else if (lastIndex > 0 && this.activeNamespaceIndex != lastIndex) {
final BookmarkGrid prevGrid = this.namespaces.get(lastIndex - 1);
if (sortableGroup != null && sortableGroup.grid == prevGrid && sortableGroup.aloneInGrid
|| prevGrid == getActiveGrid() && prevGrid.size() == 0) {
this.namespaces.remove(lastIndex);
}
}
return this.namespaces.size();
@@ -8,7 +8,6 @@ import org.lwjgl.opengl.GL11;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
import codechicken.nei.ItemsGrid.MouseContext;
import codechicken.nei.NEIClientConfig;
@@ -65,7 +64,8 @@ public class BookmarksGridSlot extends ItemsGridSlot {
protected final int rowIndex;
public BookmarksGridSlot(int slotIndex, int itemIndex, long realMultiplier, long shiftAmount,
long calculatedMultiplier, BookmarkItem bookmarkItem, CalculatedType calculatedType, BookmarkGroup group) {
long calculatedMultiplier, BookmarkItem bookmarkItem, CalculatedType calculatedType, BookmarkGroup group,
BookmarkGrid grid) {
super(
slotIndex,
itemIndex,
@@ -96,7 +96,6 @@ public class BookmarksGridSlot extends ItemsGridSlot {
: CalculatedType.RESULT;
}
final BookmarkGrid grid = ItemPanels.bookmarkPanel.getGrid();
final BookmarkItem prevItem = grid.getCalculatedItem(
grid.gridGenerator.slotToItem.getOrDefault(grid.gridGenerator.itemToSlot.get(this.itemIndex) - 1, -1));
this.isFirstOutput = this.itemIndex >= 0 && this.bookmarkItem.type == BookmarkItemType.RESULT
@@ -13,13 +13,23 @@ public class SortableGroup {
public int groupId;
public BookmarkGrid grid;
public boolean aloneInGrid;
public int shiftX = 0;
public int shiftY = 0;
public SortableGroup(BookmarkGrid grid, int groupId) {
update(grid, groupId);
}
public void update(BookmarkGrid grid, int groupId) {
this.grid = grid;
this.groupId = groupId;
this.aloneInGrid = grid.bookmarkItems.stream().allMatch(item -> item.groupId == groupId);
}
public boolean matches(BookmarkGrid grid, int groupId) {
return this.grid == grid && this.groupId == groupId;
}
public void postDraw(int mousex, int mousey) {
@@ -2,6 +2,8 @@ package codechicken.nei.bookmark.tree;
import java.awt.geom.Point2D;
import org.lwjgl.input.Mouse;
class Camera {
private static final float ZOOM_MIN = 0.1f;
@@ -39,23 +41,34 @@ class Camera {
&& y < this.widgetY + this.widgetH;
}
public void startDrag(int mouseX, int mouseY) {
public void startDrag(Point2D mouse) {
this.dragging = true;
this.lastDragX = mouseX;
this.lastDragY = mouseY;
this.lastDragX = (float) mouse.getX();
this.lastDragY = (float) mouse.getY();
}
public void drag(int mouseX, int mouseY) {
public void drag(Point2D mouse) {
if (!this.dragging) {
return;
}
final float mouseX = (float) mouse.getX();
final float mouseY = (float) mouse.getY();
this.scrollX -= (mouseX - this.lastDragX) / this.zoomLevel;
this.scrollY -= (mouseY - this.lastDragY) / this.zoomLevel;
this.lastDragX = mouseX;
this.lastDragY = mouseY;
}
public void update(Point2D mouse, int treeWidth, int treeHeight) {
if (this.dragging && Mouse.isButtonDown(0)) {
drag(mouse);
}
clampToBounds(treeWidth, treeHeight);
}
public void endDrag() {
this.dragging = false;
}
@@ -79,7 +92,6 @@ class Camera {
} else {
this.scrollY -= direction * this.widgetH / (SCROLL_STEP_DIVISOR * this.zoomLevel);
}
}
public void centerOn(float worldX, float worldY) {
@@ -169,12 +169,12 @@ class CraftingTreeCanvas {
: -FIT_MARGIN;
}
public void startDrag(int mouseX, int mouseY) {
this.camera.startDrag(mouseX, mouseY);
public void startDrag(Point2D mouse) {
this.camera.startDrag(mouse);
}
public void drag(int mouseX, int mouseY) {
this.camera.drag(mouseX, mouseY);
public void drag(Point2D mouse) {
this.camera.drag(mouse);
}
public void endDrag() {
@@ -259,7 +259,7 @@ class CraftingTreeCanvas {
public void draw(Point2D mouse, Set<BookmarkItem> collapsedItems, SearchResult searchResult,
boolean useInventorySnapshot, ItemTreeSlot hoveredNode, ItemTreeSlot deleteHoverNode) {
this.camera.clampToBounds(this.treeWidth, this.treeHeight);
this.camera.update(mouse, this.treeWidth, this.treeHeight);
final float zScrollX = this.camera.scrollX * this.camera.zoomLevel;
final float zScrollY = this.camera.scrollY * this.camera.zoomLevel;
@@ -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 {
@@ -757,7 +758,7 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu
return true;
}
this.canvas.startDrag(mouseX, mouseY);
this.canvas.startDrag(getMousePosition());
}
return false;
@@ -778,7 +779,7 @@ public class GuiCraftingTree extends GuiContainer implements INEIGuiHandler, IGu
}
this.canvas.drag(mouseX, mouseY);
this.canvas.drag(getMousePosition());
}
@Override
@@ -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) {
@@ -74,7 +74,11 @@ public class IMCHandler {
tag.getInteger("imageX"),
tag.getInteger("imageY"),
tag.getInteger("imageWidth"),
tag.getInteger("imageHeight"));
tag.getInteger("imageHeight"),
tag.hasKey("imageTextureWidth") ? tag.getInteger("imageTextureWidth")
: HandlerInfo.DEFAULT_IMAGE_TEXTURE_SIZE,
tag.hasKey("imageTextureHeight") ? tag.getInteger("imageTextureHeight")
: HandlerInfo.DEFAULT_IMAGE_TEXTURE_SIZE);
}
if (!info.hasImageOrItem()) {
final String itemName = tag.getString("itemName");
@@ -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
@@ -21,11 +21,15 @@ public class DrawableBuilder {
private int sliceBottom = 0;
public DrawableBuilder(String resourceLocation, int u, int v, int width, int height) {
this(new ResourceLocation(resourceLocation), u, v, width, height);
}
public DrawableBuilder(ResourceLocation resource, int u, int v, int width, int height) {
this.u = u;
this.v = v;
this.width = width;
this.height = height;
this.resourceLocation = new ResourceLocation(resourceLocation);
this.resourceLocation = resource;
}
public DrawableBuilder setTextureSize(int width, int height) {
@@ -1,7 +1,7 @@
package codechicken.nei.drawable;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
@@ -73,6 +73,14 @@ public class DrawableResource extends Image {
return height + paddingTop + paddingBottom;
}
public int getTextureWidth() {
return this.textureWidth;
}
public int getTextureHeight() {
return this.textureHeight;
}
public void bindTexture() {
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
}
@@ -88,7 +96,10 @@ public class DrawableResource extends Image {
GL11.glEnable(GL11.GL_TEXTURE_2D);
bindTexture();
drawModalRectWithCustomSizedTexture(xOffset + maskLeft, yOffset + maskTop, maskLeft, maskTop, width, height);
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
addQuad(tessellator, xOffset + maskLeft, yOffset + maskTop, maskLeft, maskTop, width, height);
tessellator.draw();
if (!is2DTexture) GL11.glDisable(GL11.GL_TEXTURE_2D);
}
@@ -118,14 +129,30 @@ public class DrawableResource extends Image {
GL11.glEnable(GL11.GL_TEXTURE_2D);
bindTexture();
// middle parts
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
final int tileWidthSize = (tileW == tileWidthCount) ? remainderWidth : middleWidth;
final int x = xOffset + sliceLeft + tileW * middleWidth;
// border top
addQuad(tessellator, x, yOffset, sliceLeft, 0, tileWidthSize, sliceTop);
// border bottom
addQuad(
tessellator,
x,
yOffset + sliceTop + tileHeight,
sliceLeft,
sliceTop + middleHeight,
tileWidthSize,
sliceBottom);
// middle parts
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
drawModalRectWithCustomSizedTexture(
addQuad(
tessellator,
x,
yOffset + sliceTop + tileH * middleHeight,
sliceLeft,
@@ -135,114 +162,75 @@ public class DrawableResource extends Image {
}
}
// border top
if (sliceTop > 0) {
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
final int tileWidthSize = (tileW == tileWidthCount) ? remainderWidth : middleWidth;
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileW * middleWidth,
yOffset,
sliceLeft,
0,
tileWidthSize,
sliceTop);
}
}
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
final int y = yOffset + sliceTop + tileH * middleHeight;
// border bottom
if (sliceBottom > 0) {
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
final int tileWidthSize = (tileW == tileWidthCount) ? remainderWidth : middleWidth;
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileW * middleWidth,
yOffset + sliceTop + tileHeight,
sliceLeft,
sliceTop + middleHeight,
tileWidthSize,
sliceBottom);
}
}
// border left
if (sliceLeft > 0) {
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
drawModalRectWithCustomSizedTexture(
xOffset,
yOffset + sliceTop + tileH * middleHeight,
0,
sliceTop,
sliceLeft,
tileHeightSize);
}
}
// border right
if (sliceRight > 0) {
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileWidth,
yOffset + sliceTop + tileH * middleHeight,
sliceLeft + middleWidth,
sliceTop,
sliceRight,
tileHeightSize);
}
// border left
addQuad(tessellator, xOffset, y, 0, sliceTop, sliceLeft, tileHeightSize);
// border right
addQuad(
tessellator,
xOffset + sliceLeft + tileWidth,
y,
sliceLeft + middleWidth,
sliceTop,
sliceRight,
tileHeightSize);
}
// angle left-top
if (sliceLeft > 0 && sliceTop > 0) {
drawModalRectWithCustomSizedTexture(xOffset, yOffset, 0, 0, sliceLeft, sliceTop);
}
addQuad(tessellator, xOffset, yOffset, 0, 0, sliceLeft, sliceTop);
// angle right-top
if (sliceRight > 0 && sliceTop > 0) {
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileWidth,
yOffset,
sliceLeft + middleWidth,
0,
sliceRight,
sliceTop);
}
addQuad(
tessellator,
xOffset + sliceLeft + tileWidth,
yOffset,
sliceLeft + middleWidth,
0,
sliceRight,
sliceTop);
// angle left-bottom
if (sliceLeft > 0 && sliceBottom > 0) {
drawModalRectWithCustomSizedTexture(
xOffset,
yOffset + sliceTop + tileHeight,
0,
sliceTop + middleHeight,
sliceLeft,
sliceBottom);
}
addQuad(
tessellator,
xOffset,
yOffset + sliceTop + tileHeight,
0,
sliceTop + middleHeight,
sliceLeft,
sliceBottom);
// angle right-bottom
if (sliceRight > 0 && sliceBottom > 0) {
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileWidth,
yOffset + sliceTop + tileHeight,
sliceLeft + middleWidth,
sliceTop + middleHeight,
sliceRight,
sliceBottom);
}
addQuad(
tessellator,
xOffset + sliceLeft + tileWidth,
yOffset + sliceTop + tileHeight,
sliceLeft + middleWidth,
sliceTop + middleHeight,
sliceRight,
sliceBottom);
tessellator.draw();
if (!is2DTexture) GL11.glDisable(GL11.GL_TEXTURE_2D);
}
}
private void drawModalRectWithCustomSizedTexture(int xOffset, int yOffset, int u, int v, int width, int height) {
Gui.func_146110_a(
xOffset + this.paddingLeft,
yOffset + this.paddingTop,
this.x + u,
this.y + v,
width,
height,
this.textureWidth,
this.textureHeight);
private void addQuad(Tessellator tessellator, int xOffset, int yOffset, int u, int v, int width, int height) {
if (width <= 0 || height <= 0) return;
final float scaleU = 1.0F / this.textureWidth;
final float scaleV = 1.0F / this.textureHeight;
final int x = xOffset + this.paddingLeft;
final int y = yOffset + this.paddingTop;
final float u0 = (this.x + u) * scaleU;
final float u1 = (this.x + u + width) * scaleU;
final float v0 = (this.y + v) * scaleV;
final float v1 = (this.y + v + height) * scaleV;
tessellator.addVertexWithUV(x, y + height, 0.0D, u0, v1);
tessellator.addVertexWithUV(x + width, y + height, 0.0D, u1, v1);
tessellator.addVertexWithUV(x + width, y, 0.0D, u1, v0);
tessellator.addVertexWithUV(x, y, 0.0D, u0, v0);
}
}
@@ -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);
}
@@ -502,6 +504,46 @@ public class GuiContainerManager {
for (IContainerInputHandler inputhander : inputHandlers)
if (inputhander.lastKeyTyped(window, keyChar, keyID)) return true;
return onHotkeyPressed();
}
public boolean onHotkeyPressed() {
if (window instanceof IGuiHotkeyListener && ((IGuiHotkeyListener) window).onHotkeyPressed()) {
return true;
}
for (IContainerInputHandler inputhander : inputHandlers) {
if (inputhander.onHotkeyPressed(window)) {
return true;
}
}
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;
}
}
@@ -5,13 +5,18 @@ import java.util.List;
import java.util.Map;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.inventory.SlotCrafting;
import net.minecraft.item.ItemStack;
import codechicken.nei.FastTransferManager;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemStackAmount;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.NEIServerUtils;
import codechicken.nei.RestartableTask;
import codechicken.nei.ServerSync;
import codechicken.nei.bookmark.BookmarkItem;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.chain.RecipeChainIterator;
@@ -26,6 +31,7 @@ public class AutoCraftingManager {
@Override
public void execute() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
final ItemStack[] layout = snapshotLayout(guiContainer);
final List<BookmarkItem> initialItems = prepareInitialItems(math, getInventoryItems(guiContainer));
boolean processed = false;
boolean changed = false;
@@ -69,6 +75,11 @@ public class AutoCraftingManager {
} while (changed && !interrupted(guiContainer));
if (!interrupted(guiContainer)) {
restoreLayout(guiContainer, layout);
ServerSync.request();
}
} finally {
StackInfo.pauseItemDamageSound(false);
}
@@ -134,4 +145,70 @@ public class AutoCraftingManager {
return inventory;
}
private static ItemStack[] snapshotLayout(GuiContainer guiContainer) {
final List<Slot> slots = guiContainer.inventorySlots.inventorySlots;
final ItemStack[] layout = new ItemStack[slots.size()];
for (int index = 0; index < slots.size(); index++) {
final Slot slot = slots.get(index);
if (slot.getHasStack() && !(slot instanceof SlotCrafting)) {
layout[index] = slot.getStack().copy();
}
}
return layout;
}
private static void restoreLayout(GuiContainer guiContainer, ItemStack[] layout) {
final List<Slot> slots = guiContainer.inventorySlots.inventorySlots;
final EntityPlayer player = guiContainer.mc.thePlayer;
for (int index = 0; index < slots.size() && player.inventory.getItemStack() == null
&& guiContainer.mc.currentScreen == guiContainer; index++) {
final ItemStack expected = layout[index];
final Slot target = slots.get(index);
if (expected == null || inPlace(slots, layout, index)
|| !target.isItemValid(expected)
|| target.getHasStack() && !target.canTakeStack(player)) {
continue;
}
final Slot source = findMisplaced(slots, layout, expected, player);
if (source == null) continue;
FastTransferManager.clickSlot(guiContainer, source.slotNumber);
FastTransferManager.clickSlot(guiContainer, target.slotNumber);
if (player.inventory.getItemStack() != null) {
FastTransferManager.clickSlot(guiContainer, source.slotNumber);
}
}
FastTransferManager.dropHeldItem(guiContainer);
}
private static boolean inPlace(List<Slot> slots, ItemStack[] layout, int index) {
final ItemStack stack = slots.get(index).getStack();
return stack != null && layout[index] != null
&& NEIServerUtils.areStacksSameTypeCraftingWithNBT(layout[index], stack);
}
private static Slot findMisplaced(List<Slot> slots, ItemStack[] layout, ItemStack expected, EntityPlayer player) {
for (int index = 0; index < slots.size(); index++) {
final Slot slot = slots.get(index);
if (slot.getHasStack() && !(slot instanceof SlotCrafting)
&& !inPlace(slots, layout, index)
&& slot.canTakeStack(player)
&& NEIServerUtils.areStacksSameTypeCraftingWithNBT(expected, slot.getStack())) {
return slot;
}
}
return null;
}
}
@@ -1,6 +1,7 @@
package codechicken.nei.recipe;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -20,10 +21,13 @@ import codechicken.lib.inventory.InventoryUtils;
import codechicken.nei.FastTransferManager;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.ServerSync;
import codechicken.nei.api.IOverlayHandler;
public class DefaultOverlayHandler implements IOverlayHandler {
private static final long SYNC_TIMEOUT_MILLIS = 5000;
public static class DistributedIngred {
public DistributedIngred(ItemStack item) {
@@ -113,7 +117,8 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
boolean craft = false;
while (multiplier > 0) {
while (multiplier > 0 && firstGui.mc.currentScreen == firstGui
&& presenceOverlay(firstGui, handler, recipeIndex).stream().allMatch(s -> s.isPresent())) {
final int transfer = transferRecipe(firstGui, handler, recipeIndex, multiplier);
if (transfer <= 0) {
@@ -122,6 +127,10 @@ public class DefaultOverlayHandler implements IOverlayHandler {
multiplier -= transfer;
if (!craftingSlot.getHasStack() && !awaitServer(firstGui)) {
break;
}
if (craftingSlot.getHasStack() && craftingSlot.canTakeStack(firstGui.mc.thePlayer)) {
FastTransferManager.clickSlot(firstGui, craftingSlot.slotNumber, 0, 1);
@@ -139,9 +148,30 @@ public class DefaultOverlayHandler implements IOverlayHandler {
return craft;
}
private boolean awaitServer(GuiContainer gui) {
final long deadline = System.currentTimeMillis() + SYNC_TIMEOUT_MILLIS;
ServerSync.request();
while (ServerSync.pending()) {
if (gui.mc.currentScreen != gui || System.currentTimeMillis() > deadline) {
return false;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return false;
}
}
return true;
}
protected boolean clearIngredients(GuiContainer gui, IRecipeHandler handler) {
for (Slot slot : getCraftMatrixSlots(gui, handler)) {
if (gui.mc.currentScreen != gui) return false;
if (slot.canTakeStack(gui.mc.thePlayer)) {
FastTransferManager.clickSlot(gui, slot.slotNumber, 0, 1);
if (slot.getHasStack()) return false;
@@ -206,7 +236,9 @@ public class DefaultOverlayHandler implements IOverlayHandler {
protected void moveIngredients(GuiContainer gui, List<IngredientDistribution> assignedIngredients, int multiplier) {
final Slot craftingSlot = getCraftingResultSlot(gui);
for (Slot slot : gui.inventorySlots.inventorySlots) {
for (Slot slot : getSourceSlots(gui)) {
if (gui.mc.currentScreen != gui) return;
if (slot == craftingSlot || !slot.getHasStack()
|| !canMoveFrom(slot, gui)
|| !slot.canTakeStack(gui.mc.thePlayer))
@@ -240,6 +272,10 @@ public class DefaultOverlayHandler implements IOverlayHandler {
FastTransferManager.clickSlot(gui, slot.slotNumber);
}
// A right click with an empty or foreign cursor would take half of the dest stack
final ItemStack held = gui.mc.thePlayer.inventory.getItemStack();
if (held == null || !canStack(distrib.permutation, held)) break;
FastTransferManager.clickSlot(gui, dest.slotNumber, 1);
stackSize--;
}
@@ -370,7 +406,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
protected void findInventoryQuantities(GuiContainer gui, List<DistributedIngred> ingredStacks) {
for (Slot slot : gui.inventorySlots.inventorySlots) /* work out how much we have to go round */ {
for (Slot slot : getSourceSlots(gui)) /* work out how much we have to go round */ {
if (slot.getHasStack() && canMoveFrom(slot, gui)) {
final ItemStack pstack = slot.getStack();
final DistributedIngred istack = findIngred(ingredStacks, pstack);
@@ -408,6 +444,25 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
}
protected List<Slot> getSourceSlots(GuiContainer gui) {
final List<Slot> slots = new ArrayList<>(gui.inventorySlots.inventorySlots);
slots.sort(Comparator.comparingLong(slot -> getRemainingDurability(slot.getStack())));
return slots;
}
private static long getRemainingDurability(ItemStack stack) {
if (stack == null || stack.getMaxStackSize() != 1) return Long.MAX_VALUE;
final NBTTagCompound tagCompound = stack.getTagCompound();
if (tagCompound != null && tagCompound.hasKey("GT.ToolStats")) {
final NBTTagCompound toolStats = tagCompound.getCompoundTag("GT.ToolStats");
return toolStats.getLong("MaxDamage") - toolStats.getLong("Damage");
}
return stack.isItemStackDamageable() ? stack.getMaxDamage() - stack.getItemDamage() : Long.MAX_VALUE;
}
private int getGTToolDamagePerContainerCraft(ItemStack aStack) {
try {
@@ -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
@@ -177,12 +177,20 @@ public class GuiOverlayButton extends GuiRecipeButton {
hotkeys.put(
NEIClientUtils.getKeyName(NEIClientUtils.SHIFT_HASH, NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGrid"));
hotkeys.put(
NEIClientUtils.getKeyName(
NEIClientUtils.SHIFT_HASH + NEIClientUtils.CTRL_HASH,
NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGridOnce"));
}
} else if (canFillCraftingGrid()) {
hotkeys.put(
NEIMouseUtils.getKeyName(NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGrid"));
hotkeys.put(
NEIClientUtils.getKeyName(NEIClientUtils.CTRL_HASH, NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGridOnce"));
}
hotkeys.put(
@@ -329,7 +337,7 @@ public class GuiOverlayButton extends GuiRecipeButton {
if (!requireShiftForOverlayRecipe() || shift) {
if (this.canFillCraftingGrid) {
this.handlerRef.fillCraftingGrid(this.firstGui, 0);
this.handlerRef.fillCraftingGrid(this.firstGui, NEIClientUtils.controlKey() ? 1 : 0);
}
} else if (hasOverlay() && useOverlayRenderer()) {
this.handlerRef.useOverlayRenderer(this.firstGui);
@@ -338,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;
@@ -697,6 +698,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 +723,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 +750,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() {
@@ -12,6 +12,7 @@ public class HandlerInfo {
public static int DEFAULT_HEIGHT = 65;
public static int DEFAULT_WIDTH = 166;
public static final int DEFAULT_IMAGE_TEXTURE_SIZE = 256;
@Deprecated
public static int DEFAULT_MAX_PER_PAGE = 1;
@@ -70,9 +71,22 @@ public class HandlerInfo {
}
public boolean setImage(String resourceLocation, int imageX, int imageY, int imageWidth, int imageHeight) {
return setImage(
resourceLocation,
imageX,
imageY,
imageWidth,
imageHeight,
DEFAULT_IMAGE_TEXTURE_SIZE,
DEFAULT_IMAGE_TEXTURE_SIZE);
}
public boolean setImage(String resourceLocation, int imageX, int imageY, int imageWidth, int imageHeight,
int textureWidth, int textureHeight) {
if (hasImageOrItem()) return false;
this.image = new DrawableBuilder(resourceLocation, imageX, imageY, imageWidth, imageHeight).build();
this.image = new DrawableBuilder(resourceLocation, imageX, imageY, imageWidth, imageHeight)
.setTextureSize(textureWidth, textureHeight).build();
return true;
}
@@ -211,6 +225,20 @@ public class HandlerInfo {
return this;
}
public Builder setDisplayImage(ResourceLocation location, int imageX, int imageY, int imageWidth,
int imageHeight, int textureWidth, int textureHeight) {
this.info.itemStack = null;
this.info.setImage(
location.toString(),
imageX,
imageY,
imageWidth,
imageHeight,
textureWidth,
textureHeight);
return this;
}
@Deprecated
public Builder setUseCustomScroll(boolean useCustomScroll) {
this.info.setUseCustomScroll(useCustomScroll);
@@ -1,5 +1,6 @@
package codechicken.nei.recipe;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
@@ -167,6 +168,8 @@ public class RecipeHandlerRef {
return transferAlghoritmCache.computeIfAbsent(overlayHandler.getClass(), clazz -> {
try {
final Method transferRecipe = overlayHandler.getClass()
.getMethod("transferRecipe", GuiContainer.class, IRecipeHandler.class, int.class, int.class);
if (overlayHandler instanceof DefaultOverlayHandler) {
final Class<?> overlayRecipeClazz = overlayHandler.getClass()
@@ -178,14 +181,11 @@ public class RecipeHandlerRef {
boolean.class)
.getDeclaringClass();
if (overlayRecipeClazz.equals(DefaultOverlayHandler.class)) {
return true;
}
return overlayRecipeClazz.equals(DefaultOverlayHandler.class)
|| !transferRecipe.getDeclaringClass().equals(DefaultOverlayHandler.class);
}
return !overlayHandler.getClass()
.getMethod("transferRecipe", GuiContainer.class, IRecipeHandler.class, int.class, int.class)
.isDefault();
return !transferRecipe.isDefault();
} catch (NoSuchMethodException e) {}
return false;
@@ -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));
}
@@ -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,
@@ -512,6 +512,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) {
@@ -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) {
@@ -7,6 +7,7 @@ import java.util.Map;
import java.util.function.Predicate;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import codechicken.lib.gui.GuiDraw;
@@ -249,6 +250,8 @@ public class ScrollContainer extends WidgetContainer {
final boolean canScrollHorizontal = canScrollHorizontal();
if (canScrollVertical || canScrollHorizontal) {
dragScrollBars(mousex, mousey, canScrollVertical, canScrollHorizontal);
GuiHelper.useScissor(
this.x + this.paddingInlineStart,
this.y + this.paddingBlockStart,
@@ -273,6 +276,20 @@ public class ScrollContainer extends WidgetContainer {
}
private void dragScrollBars(int mousex, int mousey, boolean canScrollVertical, boolean canScrollHorizontal) {
if (!Mouse.isButtonDown(0)) {
return;
}
if (canScrollVertical) {
this.verticalScrollBar.onMouseDragged(this, mousex, mousey, 0, 0);
}
if (canScrollHorizontal) {
this.horizontalScrollBar.onMouseDragged(this, mousex, mousey, 0, 0);
}
}
protected void drawContent(int mousex, int mousey) {
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GL11.glPushMatrix();
@@ -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
@@ -706,6 +707,7 @@ nei.recipe.overlay.missing=Missing following
nei.recipe.overlay.mismatch=Mismatch Crafting Grid
nei.recipe.overlay.overlayRecipe=Overlay Recipe
nei.recipe.overlay.fillCraftingGrid=Fill Crafting Grid
nei.recipe.overlay.fillCraftingGridOnce=Fill Crafting Grid (single recipe)
nei.recipe.overlay.bookmarkRecipe=Save with Recipe
nei.recipe.overlay.bookmarkRecipeAndCount=Save with Recipe & Count
nei.recipe.overlay.sendRecipeToChat=Send Recipe to Chat
@@ -438,6 +438,7 @@ nei.recipe.overlay=отображение рецепта
nei.recipe.overlay.missing=Отсутствует следующее
nei.recipe.overlay.mismatch=Несоответствие сетки крафта
nei.recipe.overlay.fillCraftingGrid=Заполнить сетку крафта
nei.recipe.overlay.fillCraftingGridOnce=Заполнить сетку крафта (один рецепт)
nei.recipe.overlay.bookmarkRecipe=Сохранить с рецептом
nei.recipe.overlay.sendRecipeToChat=Отправить рецепт в чат
@@ -0,0 +1,134 @@
package codechicken.nei;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
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.doThrow;
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 java.util.stream.Stream;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import codechicken.nei.api.IConfigureNEI;
class PluginLoaderTest {
@ParameterizedTest
@ValueSource(booleans = { false, true })
void loadsPluginsAndLogsFailuresWithTheGameClassLoader(boolean parallel) 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(parallel ? plugins.parallelStream() : plugins.stream(), 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();
}
}
@Test
void restoresTheClassLoaderEvenIfErrorLoggingThrows() {
Thread thread = Thread.currentThread();
ClassLoader previousClassLoader = thread.getContextClassLoader();
ClassLoader workerClassLoader = new ClassLoader(null) {};
Logger logger = mock(Logger.class);
doThrow(new LinkageError("Logging failed")).when(logger)
.error(eq("Failed to Load {}"), anyString(), any(Throwable.class));
try {
thread.setContextClassLoader(workerClassLoader);
assertThrows(
LinkageError.class,
() -> PluginLoader.load(Stream.of(ConfigurationFailurePlugin.class), new ArrayList<>(), logger));
assertSame(workerClassLoader, thread.getContextClassLoader());
} finally {
thread.setContextClassLoader(previousClassLoader);
}
}
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");
}
}
}