mirror of
https://github.com/GTNewHorizons/NotEnoughItems.git
synced 2026-11-08 12:34:46 +00:00
Implement Bookmark Tree Screen (#1018)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
fda7688807
commit
3d6c1c6e19
30 files changed
+4135
-135
No files matched your search
@@ -1048,15 +1048,9 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
|
||||
final BookmarksGridSlot slot = this.grid.getSlotMouseOver(mousex, mousey);
|
||||
|
||||
if (slot != null) {
|
||||
int multiplier = 1;
|
||||
|
||||
if (NEIClientUtils.altKey()) {
|
||||
multiplier = NEIClientConfig.showItemQuantityWidget() ? NEIClientConfig.getItemQuantity() : 0;
|
||||
if (multiplier == 0) {
|
||||
multiplier = StackInfo.isFluidDisplayItem(slot.getItemStack()) ? 144
|
||||
: slot.getItemStack().getMaxStackSize();
|
||||
}
|
||||
}
|
||||
final long multiplier = NEIClientUtils.altKey()
|
||||
? NEIClientUtils.getScrollMultiplier(slot.getItemStack())
|
||||
: 1;
|
||||
|
||||
this.grid.shiftItemAmount(slot.itemIndex, multiplier * shift);
|
||||
return true;
|
||||
|
||||
@@ -48,6 +48,7 @@ import codechicken.nei.api.API;
|
||||
import codechicken.nei.api.GuiInfo;
|
||||
import codechicken.nei.api.IConfigureNEI;
|
||||
import codechicken.nei.api.ItemInfo;
|
||||
import codechicken.nei.bookmark.tree.GuiCraftingTree;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.recipe.GuiRecipeTab;
|
||||
import codechicken.nei.recipe.StackInfo;
|
||||
@@ -279,6 +280,7 @@ public class ClientHandler {
|
||||
PacketCustom.assignHandler(NEICPH.channel, new NEICPH());
|
||||
FMLCommonHandler.instance().bus().register(instance);
|
||||
MinecraftForge.EVENT_BUS.register(instance);
|
||||
MinecraftForge.EVENT_BUS.register(new GuiCraftingTree.EventHandler());
|
||||
|
||||
API.registerHighlightHandler(new DefaultHighlightHandler(), ItemInfo.Layout.HEADER);
|
||||
HUDRenderer.load();
|
||||
|
||||
@@ -83,11 +83,7 @@ public class ItemQuantityField extends TextField {
|
||||
}
|
||||
|
||||
if (amount == 0) {
|
||||
amount = NEIClientConfig.showItemQuantityWidget() ? NEIClientConfig.getItemQuantity() : 0;
|
||||
|
||||
if (amount == 0) {
|
||||
amount = StackInfo.isFluidDisplayItem(stack) ? 144 : stack.getMaxStackSize();
|
||||
}
|
||||
amount = NEIClientUtils.getScrollMultiplier(stack);
|
||||
}
|
||||
|
||||
return StackInfo.withAmount(stack, amount);
|
||||
|
||||
@@ -79,6 +79,10 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler {
|
||||
return this.activeStackIndex == -1 ? null : this.items.get(this.activeStackIndex);
|
||||
}
|
||||
|
||||
public List<ItemStack> getItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.items.isEmpty();
|
||||
}
|
||||
|
||||
@@ -873,6 +873,7 @@ public class NEIClientConfig {
|
||||
API.addKeyBind("bookmark.remove_recipe", Keyboard.KEY_A);
|
||||
API.addKeyBind("bookmark.pull_items", Keyboard.KEY_V);
|
||||
API.addKeyBind("bookmark.chat_link", Keyboard.KEY_L);
|
||||
API.addKeyBind("bookmark.open_crafting_tree", Keyboard.KEY_T);
|
||||
API.addKeyBind("bookmark.hide", Keyboard.KEY_B);
|
||||
|
||||
API.addKeyBind("itemzoom.toggle", Keyboard.KEY_Z);
|
||||
|
||||
@@ -564,6 +564,16 @@ public class NEIClientUtils extends NEIServerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static long getScrollMultiplier(ItemStack stack) {
|
||||
final long multiplier = NEIClientConfig.showItemQuantityWidget() ? NEIClientConfig.getItemQuantity() : 0;
|
||||
|
||||
if (multiplier != 0) {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
return StackInfo.itemStackToNBT(stack).hasKey("gtFluidName") ? 144 : stack.getMaxStackSize();
|
||||
}
|
||||
|
||||
public static void drawRect(double left, double top, double width, double height, Color color) {
|
||||
final boolean is2DTexture = GL11.glGetBoolean(GL11.GL_TEXTURE_2D);
|
||||
final boolean isBlend = GL11.glGetBoolean(GL11.GL_BLEND);
|
||||
@@ -606,6 +616,7 @@ public class NEIClientUtils extends NEIServerUtils {
|
||||
GuiDraw.fontRenderer.drawString(text, 0, 0, color, shadow);
|
||||
GL11.glScaled(1 / smallTextScale, 1 / smallTextScale, 1);
|
||||
GL11.glTranslated(-1 * offsetX, -1 * offsetY, 0);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
package codechicken.nei.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
import codechicken.nei.VisiblityData;
|
||||
|
||||
/**
|
||||
* Lets you just override those things you want to
|
||||
*/
|
||||
@@ -17,31 +11,6 @@ public class INEIGuiAdapter implements INEIGuiHandler {
|
||||
|
||||
static final Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]");
|
||||
|
||||
@Override
|
||||
public VisiblityData modifyVisiblity(GuiContainer gui, VisiblityData currentVisibility) {
|
||||
return currentVisibility;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Integer> getItemSpawnSlots(GuiContainer gui, ItemStack item) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TaggedInventoryArea> getInventoryAreas(GuiContainer gui) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleDragNDrop(GuiContainer gui, int mousex, int mousey, ItemStack draggedStack, int button) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hideItemPanelSlot(GuiContainer gui, int x, int y, int w, int h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String formattingText(String displayName) {
|
||||
return SPECIAL_REGEX_CHARS.matcher(EnumChatFormatting.getTextWithoutFormattingCodes(displayName))
|
||||
.replaceAll("\\\\$0");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package codechicken.nei.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
@@ -12,23 +13,29 @@ import codechicken.nei.VisiblityData;
|
||||
*/
|
||||
public interface INEIGuiHandler {
|
||||
|
||||
public VisiblityData modifyVisiblity(GuiContainer gui, VisiblityData currentVisibility);
|
||||
default VisiblityData modifyVisiblity(GuiContainer gui, VisiblityData currentVisibility) {
|
||||
return currentVisibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* NEI will give the specified item to the InventoryRange returned if the player's inventory is full. Should not
|
||||
* return null, just an empty list
|
||||
*/
|
||||
public Iterable<Integer> getItemSpawnSlots(GuiContainer gui, ItemStack item);
|
||||
default Iterable<Integer> getItemSpawnSlots(GuiContainer gui, ItemStack item) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of TaggedInventoryAreas that will be used with the savestates.
|
||||
*/
|
||||
public List<TaggedInventoryArea> getInventoryAreas(GuiContainer gui);
|
||||
default List<TaggedInventoryArea> getInventoryAreas(GuiContainer gui) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicks while an itemstack has been dragged from the item panel. Use this to set configurable slots and
|
||||
* the like. Changes made to the stackSize of the dragged stack will be kept
|
||||
*
|
||||
*
|
||||
* @param gui The current gui instance
|
||||
* @param mousex The x position of the mouse
|
||||
* @param mousey The y position of the mouse
|
||||
@@ -37,18 +44,22 @@ public interface INEIGuiHandler {
|
||||
* @return True if the drag n drop was handled. False to resume processing through other routes. The held stack will
|
||||
* be deleted if draggedStack.stackSize == 0
|
||||
*/
|
||||
public boolean handleDragNDrop(GuiContainer gui, int mousex, int mousey, ItemStack draggedStack, int button);
|
||||
default boolean handleDragNDrop(GuiContainer gui, int mousex, int mousey, ItemStack draggedStack, int button) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to prevent the item panel from drawing on top of other gui elements. This function will also be called with
|
||||
* a 1x1 size rectangle on the mouse position for determining if the given coordinate should override item panel
|
||||
* functions such as scrolling
|
||||
*
|
||||
*
|
||||
* @param x The x coordinate of the rectangle bounding the slot
|
||||
* @param y The y coordinate of the rectangle bounding the slot
|
||||
* @param w The w coordinate of the rectangle bounding the slot
|
||||
* @param h The h coordinate of the rectangle bounding the slot
|
||||
* @return true if the item panel slot within the specified rectangle should not be rendered.
|
||||
*/
|
||||
public boolean hideItemPanelSlot(GuiContainer gui, int x, int y, int w, int h);
|
||||
default boolean hideItemPanelSlot(GuiContainer gui, int x, int y, int w, int h) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import codechicken.nei.bookmark.BookmarkItem;
|
||||
import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType;
|
||||
import codechicken.nei.bookmark.BookmarkPayload;
|
||||
import codechicken.nei.bookmark.BookmarksGridSlot;
|
||||
import codechicken.nei.bookmark.tree.GuiCraftingTree;
|
||||
import codechicken.nei.recipe.AutoCraftingManager;
|
||||
import codechicken.nei.recipe.GuiCraftingRecipe;
|
||||
import codechicken.nei.recipe.GuiFavoriteButton;
|
||||
@@ -101,6 +102,12 @@ public abstract class ShortcutInputHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ItemPanels.bookmarkPanel.getGrid().isCraftingMode(groupId)
|
||||
&& KeyManager.isKeyDown("bookmark.open_crafting_tree")) {
|
||||
GuiCraftingTree.openCraftingTreeGui(ItemPanels.bookmarkPanel.getGrid(), groupId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NEIClientConfig.autocraftingEnabled() && KeyManager.isKeyDown("gui.craft_items")
|
||||
&& NEIClientUtils.shiftKey()
|
||||
&& ItemPanels.bookmarkPanel.getGrid().isCraftingMode(groupId)) {
|
||||
@@ -477,6 +484,7 @@ public abstract class ShortcutInputHandler {
|
||||
final Map<String, String> hotkeys = new HashMap<>();
|
||||
|
||||
if (groupId != -1) {
|
||||
final boolean isCraftingMode = ItemPanels.bookmarkPanel.getGrid().isCraftingMode(groupId);
|
||||
|
||||
hotkeys.put(
|
||||
KeyManager.getKeyName("bookmark.remove_recipe", NEIClientUtils.SHIFT_HASH),
|
||||
@@ -486,6 +494,12 @@ public abstract class ShortcutInputHandler {
|
||||
KeyManager.getKeyName("bookmark.chat_link", NEIClientUtils.CTRL_HASH),
|
||||
NEIClientUtils.translate("bookmark.group.chat_link"));
|
||||
|
||||
if (isCraftingMode) {
|
||||
hotkeys.put(
|
||||
KeyManager.getKeyName("bookmark.open_crafting_tree"),
|
||||
NEIClientUtils.translate("bookmark.group.open_crafting_tree"));
|
||||
}
|
||||
|
||||
if (BookmarkContainerInfo.getBookmarkContainerHandler(gui) != null) {
|
||||
hotkeys.put(
|
||||
KeyManager.getKeyName("bookmark.pull_items"),
|
||||
@@ -495,7 +509,7 @@ public abstract class ShortcutInputHandler {
|
||||
NEIClientUtils.translate("bookmark.group.pull_items_shift"));
|
||||
}
|
||||
|
||||
if (NEIClientConfig.autocraftingEnabled() && ItemPanels.bookmarkPanel.getGrid().isCraftingMode(groupId)) {
|
||||
if (NEIClientConfig.autocraftingEnabled() && isCraftingMode) {
|
||||
hotkeys.put(
|
||||
NEIClientConfig
|
||||
.getKeyName("gui.craft_items", NEIClientUtils.SHIFT_HASH + NEIClientUtils.CTRL_HASH),
|
||||
|
||||
@@ -179,11 +179,15 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
|
||||
}
|
||||
|
||||
public int addGroup(BookmarkGroup group) {
|
||||
int groupId = this.bookmarkItems.stream().mapToInt(item -> item.groupId).max().orElse(DEFAULT_GROUP_ID) + 1;
|
||||
final int groupId = getNextGroupId();
|
||||
this.groups.put(groupId, group);
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public int getNextGroupId() {
|
||||
return this.bookmarkItems.stream().mapToInt(item -> item.groupId).max().orElse(DEFAULT_GROUP_ID) + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumPages() {
|
||||
|
||||
@@ -950,7 +954,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
|
||||
}
|
||||
|
||||
public void removeRecipe(int itemIndex, boolean removeFullRecipe) {
|
||||
final BookmarkItem item = getCalculatedItem(itemIndex);
|
||||
final BookmarkItem item = getBookmarkItem(itemIndex);
|
||||
|
||||
if (item == null) {
|
||||
return;
|
||||
@@ -1214,28 +1218,10 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
|
||||
if (group.collapsed) {
|
||||
shiftGroupAmount(targetItem.groupId, shift);
|
||||
} else if (targetItem.recipeId != null && targetItem.type != BookmarkItemType.ITEM) {
|
||||
final RecipeId recipeId = this.gridGenerator.itemToRecipe
|
||||
.getOrDefault(targetItemIndex, targetItem.recipeId);
|
||||
final boolean recipeInMiddle = group.crafting != null && group.crafting.recipeInMiddle.contains(recipeId);
|
||||
long multiplier = Integer.MAX_VALUE;
|
||||
|
||||
for (BookmarkItem item : this.bookmarkItems) {
|
||||
if (item.equalsRecipe(recipeId, targetItem.groupId) && !item.emptyFactor()) {
|
||||
multiplier = Math.min(
|
||||
shiftMultiplier(
|
||||
Math.max(recipeInMiddle ? 1 : 0, item.multiplier),
|
||||
shift,
|
||||
recipeInMiddle ? 1 : 0),
|
||||
multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
for (BookmarkItem item : this.bookmarkItems) {
|
||||
if (item.equalsRecipe(recipeId, targetItem.groupId) && !item.emptyFactor()) {
|
||||
item.multiplier = multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
shiftRecipeAmount(
|
||||
targetItem.groupId,
|
||||
this.gridGenerator.itemToRecipe.getOrDefault(targetItemIndex, targetItem.recipeId),
|
||||
shift);
|
||||
} else {
|
||||
|
||||
for (BookmarkItem item : this.bookmarkItems) {
|
||||
@@ -1245,6 +1231,32 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
|
||||
}
|
||||
}
|
||||
|
||||
this.onItemsChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void shiftRecipeAmount(int targetGroupId, RecipeId recipeId, long shift) {
|
||||
final BookmarkGroup group = getGroup(targetGroupId);
|
||||
final boolean recipeInMiddle = group != null && group.crafting != null
|
||||
&& group.crafting.recipeInMiddle.contains(recipeId);
|
||||
long multiplier = Integer.MAX_VALUE;
|
||||
|
||||
for (BookmarkItem item : this.bookmarkItems) {
|
||||
if (item.equalsRecipe(recipeId, targetGroupId) && !item.emptyFactor()) {
|
||||
multiplier = Math.min(
|
||||
shiftMultiplier(
|
||||
Math.max(recipeInMiddle ? 1 : 0, item.multiplier),
|
||||
shift,
|
||||
recipeInMiddle ? 1 : 0),
|
||||
multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
for (BookmarkItem item : this.bookmarkItems) {
|
||||
if (item.equalsRecipe(recipeId, targetGroupId) && !item.emptyFactor()) {
|
||||
item.multiplier = multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
this.onItemsChanged();
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
class Camera {
|
||||
|
||||
private static final float ZOOM_MIN = 0.1f;
|
||||
private static final float ZOOM_MAX = 8.0f;
|
||||
private static final double ZOOM_STEP_BASE = 1.4;
|
||||
private static final float SCROLL_STEP_DIVISOR = 5f;
|
||||
private static final int CULL_MARGIN = 32;
|
||||
private static final int CULL_ROW_MARGIN = 16;
|
||||
|
||||
private static final float INITIAL_SCROLL = -8f;
|
||||
|
||||
public int widgetX;
|
||||
public int widgetY;
|
||||
public int widgetW;
|
||||
public int widgetH;
|
||||
|
||||
public float scrollX = INITIAL_SCROLL;
|
||||
public float scrollY = INITIAL_SCROLL;
|
||||
public float zoomLevel = 1.0f;
|
||||
|
||||
private float lastDragX;
|
||||
private float lastDragY;
|
||||
private boolean dragging;
|
||||
|
||||
public void setViewport(int x, int y, int w, int h) {
|
||||
this.widgetX = x;
|
||||
this.widgetY = y;
|
||||
this.widgetW = w;
|
||||
this.widgetH = h;
|
||||
}
|
||||
|
||||
public boolean isInWidget(int x, int y) {
|
||||
return x >= this.widgetX && y >= this.widgetY
|
||||
&& x < this.widgetX + this.widgetW
|
||||
&& y < this.widgetY + this.widgetH;
|
||||
}
|
||||
|
||||
public void startDrag(int mouseX, int mouseY) {
|
||||
this.dragging = true;
|
||||
this.lastDragX = mouseX;
|
||||
this.lastDragY = mouseY;
|
||||
}
|
||||
|
||||
public void drag(int mouseX, int mouseY) {
|
||||
if (!this.dragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scrollX -= (mouseX - this.lastDragX) / this.zoomLevel;
|
||||
this.scrollY -= (mouseY - this.lastDragY) / this.zoomLevel;
|
||||
this.lastDragX = mouseX;
|
||||
this.lastDragY = mouseY;
|
||||
}
|
||||
|
||||
public void endDrag() {
|
||||
this.dragging = false;
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return this.dragging;
|
||||
}
|
||||
|
||||
public void scroll(int mouseX, int mouseY, int direction, boolean shiftKey, boolean ctrlKey) {
|
||||
|
||||
if (shiftKey) {
|
||||
this.scrollX -= direction * this.widgetW / (SCROLL_STEP_DIVISOR * this.zoomLevel);
|
||||
} else if (ctrlKey) {
|
||||
final int localX = mouseX - this.widgetX;
|
||||
final int localY = mouseY - this.widgetY;
|
||||
final float oldZoom = this.zoomLevel;
|
||||
|
||||
this.zoomLevel = clamp((float) (this.zoomLevel * Math.pow(ZOOM_STEP_BASE, direction)), ZOOM_MIN, ZOOM_MAX);
|
||||
this.scrollX += localX / oldZoom - localX / this.zoomLevel;
|
||||
this.scrollY += localY / oldZoom - localY / this.zoomLevel;
|
||||
} else {
|
||||
this.scrollY -= direction * this.widgetH / (SCROLL_STEP_DIVISOR * this.zoomLevel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void centerOn(float worldX, float worldY) {
|
||||
this.scrollX = worldX - (this.widgetW / this.zoomLevel) / 2f;
|
||||
this.scrollY = worldY - (this.widgetH / this.zoomLevel) / 2f;
|
||||
}
|
||||
|
||||
public void clampToBounds(int treeWidth, int treeHeight) {
|
||||
this.scrollX = clamp(this.scrollX, -this.widgetW / this.zoomLevel + 4, treeWidth - 4);
|
||||
this.scrollY = clamp(this.scrollY, -this.widgetH / this.zoomLevel + 4, treeHeight - 4);
|
||||
}
|
||||
|
||||
public int visibleRowMin() {
|
||||
final float zScrollY = this.scrollY * this.zoomLevel;
|
||||
return (int) ((zScrollY - CULL_MARGIN) / this.zoomLevel) - CULL_ROW_MARGIN;
|
||||
}
|
||||
|
||||
public int visibleRowMax() {
|
||||
final float zScrollY = this.scrollY * this.zoomLevel;
|
||||
return (int) ((zScrollY + CULL_MARGIN) / this.zoomLevel) + (int) (this.widgetH / this.zoomLevel)
|
||||
+ CULL_ROW_MARGIN;
|
||||
}
|
||||
|
||||
public Point2D.Double toWorldPoint(Point2D mouse) {
|
||||
final float zScrollX = this.scrollX * this.zoomLevel;
|
||||
final float zScrollY = this.scrollY * this.zoomLevel;
|
||||
|
||||
return new Point2D.Double(
|
||||
(mouse.getX() - this.widgetX + zScrollX) / this.zoomLevel,
|
||||
(mouse.getY() - this.widgetY + zScrollY) / this.zoomLevel);
|
||||
}
|
||||
|
||||
static float clamp(float value, float min, float max) {
|
||||
return value < min ? min : Math.min(value, max);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,719 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Point;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import net.minecraft.client.renderer.RenderHelper;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
import codechicken.nei.NEIClientUtils.Alignment;
|
||||
import codechicken.nei.PositionedStack;
|
||||
import codechicken.nei.bookmark.BookmarkItem;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||
import codechicken.nei.scroll.GuiHelper;
|
||||
import codechicken.nei.util.ReadableNumberConverter;
|
||||
|
||||
class CraftingTreeCanvas {
|
||||
|
||||
private static final DrawableResource SCROLLBAR_H = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
0,
|
||||
88,
|
||||
2,
|
||||
24).build();
|
||||
|
||||
private static final DrawableResource SCROLLBAR_W = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
0,
|
||||
84,
|
||||
24,
|
||||
2).build();
|
||||
|
||||
private static final DrawableResource ICON_COLLAPSED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
4,
|
||||
88,
|
||||
5,
|
||||
5).build();
|
||||
|
||||
private static final DrawableResource ICON_COLLAPSED_HOVER = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
11,
|
||||
88,
|
||||
5,
|
||||
5).build();
|
||||
|
||||
private static final DrawableResource ICON_EXPANDED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
4,
|
||||
95,
|
||||
5,
|
||||
5).build();
|
||||
|
||||
private static final DrawableResource ICON_EXPANDED_HOVER = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
11,
|
||||
95,
|
||||
5,
|
||||
5).build();
|
||||
|
||||
private static final DrawableResource ICON_REMOVE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
85,
|
||||
48,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource ICON_CRAFTING = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
67,
|
||||
48,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource SLOT_DEFAULT = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
68,
|
||||
2,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_MISSING = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
92,
|
||||
2,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_CRAFTING = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
116,
|
||||
2,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_DISABLED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
140,
|
||||
2,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_HIGHLIGHTED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
164,
|
||||
2,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final Color LINE_COLOR = new Color(0xFF888888, true);
|
||||
private static final int HOVER_COLOR = 0x66888888;
|
||||
private static final float BADGE_SCALE = 0.6f;
|
||||
|
||||
public static final int SLOT_SIZE = 18;
|
||||
public static final int X_SPACING = 24;
|
||||
public static final int Y_SPACING = 14;
|
||||
public static final int ROOT_SPACING = 36;
|
||||
private static final float FIT_MARGIN = 8f;
|
||||
|
||||
public final CraftingTreeGraph graph = new CraftingTreeGraph();
|
||||
|
||||
public final Map<ItemTreeSlot, Point> pointByNode = new HashMap<>();
|
||||
public final TreeMap<Integer, List<ItemTreeSlot>> rows = new TreeMap<>();
|
||||
public final Set<RecipeId> collapsedRecipes = Collections.newSetFromMap(new HashMap<>());
|
||||
public int treeWidth = 0;
|
||||
public int treeHeight = 0;
|
||||
|
||||
private final Camera camera = new Camera();
|
||||
|
||||
public void setViewport(int x, int y, int w, int h) {
|
||||
this.camera.setViewport(x, y, w, h);
|
||||
}
|
||||
|
||||
public boolean isInWidget(int x, int y) {
|
||||
return this.camera.isInWidget(x, y);
|
||||
}
|
||||
|
||||
public void fitToView() {
|
||||
final float firstResultCenterX = this.graph.roots.isEmpty() ? this.treeWidth / 2f
|
||||
: this.pointByNode.get(this.graph.roots.get(0)).x + SLOT_SIZE / 2f;
|
||||
|
||||
this.camera.zoomLevel = 1.0f;
|
||||
this.camera.scrollX = this.treeWidth <= (this.camera.widgetW - FIT_MARGIN)
|
||||
? (this.treeWidth - this.camera.widgetW) / 2f
|
||||
: firstResultCenterX - this.camera.widgetW / 2f;
|
||||
this.camera.scrollY = this.treeHeight <= (this.camera.widgetH - FIT_MARGIN)
|
||||
? (this.treeHeight - this.camera.widgetH) / 2f
|
||||
: -FIT_MARGIN;
|
||||
}
|
||||
|
||||
public void startDrag(int mouseX, int mouseY) {
|
||||
this.camera.startDrag(mouseX, mouseY);
|
||||
}
|
||||
|
||||
public void drag(int mouseX, int mouseY) {
|
||||
this.camera.drag(mouseX, mouseY);
|
||||
}
|
||||
|
||||
public void endDrag() {
|
||||
this.camera.endDrag();
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return this.camera.isDragging();
|
||||
}
|
||||
|
||||
public void scroll(int mouseX, int mouseY, int direction, boolean shiftKey, boolean ctrlKey) {
|
||||
this.camera.scroll(mouseX, mouseY, direction, shiftKey, ctrlKey);
|
||||
}
|
||||
|
||||
public void scrollTo(ItemTreeSlot node) {
|
||||
if (node == null || !this.pointByNode.containsKey(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Point point = this.pointByNode.get(node);
|
||||
final float nodeCenterX = point.x + SLOT_SIZE / 2f;
|
||||
final float nodeCenterY = point.y + SLOT_SIZE / 2f;
|
||||
|
||||
this.camera.centerOn(nodeCenterX, nodeCenterY);
|
||||
}
|
||||
|
||||
public ItemTreeSlot getNodeMouseOver(Point2D mouse) {
|
||||
return findVisibleNode(mouse, (node, local) -> {
|
||||
final Point point = this.pointByNode.get(node);
|
||||
|
||||
return local.x >= point.x && local.x < point.x + SLOT_SIZE
|
||||
&& local.y >= point.y
|
||||
&& local.y < point.y + SLOT_SIZE;
|
||||
});
|
||||
}
|
||||
|
||||
public ItemTreeSlot getHandlerBadgeMouseOver(Point2D mouse) {
|
||||
return findVisibleNode(mouse, (node, local) -> {
|
||||
if (node.prefItem == null || !this.graph.childrenOf.containsKey(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Point point = this.pointByNode.get(node);
|
||||
final float[] badge = badgeRect(point.x, point.y);
|
||||
|
||||
return local.x >= badge[0] && local.x < badge[0] + badge[2]
|
||||
&& local.y >= badge[1]
|
||||
&& local.y < badge[1] + badge[3];
|
||||
});
|
||||
}
|
||||
|
||||
public ItemTreeSlot getCollapseArrowMouseOver(Point2D mouse) {
|
||||
return findVisibleNode(mouse, (node, local) -> {
|
||||
if (!this.graph.childrenOf.containsKey(node) || node.prefItem == null
|
||||
|| this.collapsedRecipes.contains(node.prefItem.recipeId) && !this.graph.roots.contains(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Point point = this.pointByNode.get(node);
|
||||
final float iconX = point.x + SLOT_SIZE / 2 - ICON_COLLAPSED.width - 1;
|
||||
final float iconY = point.y + SLOT_SIZE + 1;
|
||||
final float iconW = ICON_COLLAPSED.width;
|
||||
final float iconH = ICON_COLLAPSED.height;
|
||||
|
||||
return local.x >= iconX && local.x < iconX + iconW && local.y >= iconY && local.y < iconY + iconH;
|
||||
});
|
||||
}
|
||||
|
||||
private ItemTreeSlot findVisibleNode(Point2D mouse, BiPredicate<ItemTreeSlot, Point2D.Double> hitTest) {
|
||||
final Point2D.Double local = this.camera.toWorldPoint(mouse);
|
||||
|
||||
for (List<ItemTreeSlot> row : this.rows.subMap(camera.visibleRowMin(), camera.visibleRowMax()).values()) {
|
||||
for (ItemTreeSlot node : row) {
|
||||
if (hitTest.test(node, local)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void draw(Point2D mouse, Set<BookmarkItem> collapsedItems, SearchResult searchResult,
|
||||
boolean useInventorySnapshot, ItemTreeSlot hoveredNode, ItemTreeSlot deleteHoverNode) {
|
||||
this.camera.clampToBounds(this.treeWidth, this.treeHeight);
|
||||
|
||||
final float zScrollX = this.camera.scrollX * this.camera.zoomLevel;
|
||||
final float zScrollY = this.camera.scrollY * this.camera.zoomLevel;
|
||||
final int cropXMin = (int) ((zScrollX - 32) / this.camera.zoomLevel);
|
||||
final int cropXMax = (int) ((zScrollX + 32) / this.camera.zoomLevel)
|
||||
+ (int) (this.camera.widgetW / this.camera.zoomLevel);
|
||||
final ItemTreeSlot collapseHoverNode = this.camera.isDragging() ? null : getCollapseArrowMouseOver(mouse);
|
||||
|
||||
GuiHelper.useScissor(this.camera.widgetX, this.camera.widgetY, this.camera.widgetW, this.camera.widgetH, () -> {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
RenderHelper.enableGUIStandardItemLighting();
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
GL11.glTranslatef(this.camera.widgetX - zScrollX, this.camera.widgetY - zScrollY, 0.0f);
|
||||
GL11.glScalef(this.camera.zoomLevel, this.camera.zoomLevel, 1.0f);
|
||||
|
||||
for (List<ItemTreeSlot> row : this.rows.subMap(this.camera.visibleRowMin(), this.camera.visibleRowMax())
|
||||
.values()) {
|
||||
for (ItemTreeSlot node : row) {
|
||||
final Point point = this.pointByNode.get(node);
|
||||
|
||||
drawParentLine(node);
|
||||
|
||||
if (point.x >= cropXMin && point.x <= cropXMax) {
|
||||
drawSlot(node, useInventorySnapshot, searchResult, collapsedItems, node == hoveredNode);
|
||||
drawCollapseIcon(node, collapsedItems, node == collapseHoverNode);
|
||||
|
||||
if (node == hoveredNode) {
|
||||
NEIClientUtils.gl2DRenderContext(
|
||||
() -> {
|
||||
GuiDraw.drawRect(
|
||||
point.x + 1,
|
||||
point.y + 1,
|
||||
SLOT_SIZE - 2,
|
||||
SLOT_SIZE - 2,
|
||||
HOVER_COLOR);
|
||||
});
|
||||
}
|
||||
|
||||
if (node.prefItem != null && this.graph.childrenOf.containsKey(node)) {
|
||||
drawSlotBadge(node, point.x, point.y);
|
||||
|
||||
if (node == deleteHoverNode) {
|
||||
drawDeleteButton(point);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glPopAttrib();
|
||||
GL11.glPopMatrix();
|
||||
});
|
||||
|
||||
drawScrollbars();
|
||||
}
|
||||
|
||||
public void drawFull(Set<BookmarkItem> collapsedItems, SearchResult searchResult, boolean useInventorySnapshot) {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
RenderHelper.enableGUIStandardItemLighting();
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
|
||||
for (List<ItemTreeSlot> row : this.rows.values()) {
|
||||
for (ItemTreeSlot node : row) {
|
||||
final Point point = this.pointByNode.get(node);
|
||||
|
||||
drawParentLine(node);
|
||||
drawSlot(node, useInventorySnapshot, searchResult, collapsedItems, false);
|
||||
|
||||
if (node.prefItem != null && this.graph.childrenOf.containsKey(node)) {
|
||||
drawSlotBadge(node, point.x, point.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glPopAttrib();
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
private void drawSlot(ItemTreeSlot node, boolean useInventorySnapshot, SearchResult searchResult,
|
||||
Set<BookmarkItem> collapsedItems, boolean hovered) {
|
||||
final Point point = this.pointByNode.get(node);
|
||||
|
||||
if (searchResult != null) {
|
||||
|
||||
if (node == searchResult.highlightNode) {
|
||||
SLOT_HIGHLIGHTED.draw(point.x, point.y);
|
||||
} else if (searchResult.searchMatches.contains(node)) {
|
||||
SLOT_DEFAULT.draw(point.x, point.y);
|
||||
} else {
|
||||
SLOT_DISABLED.draw(point.x, point.y);
|
||||
}
|
||||
|
||||
} else if (useInventorySnapshot) {
|
||||
|
||||
if (node.leftAmount == 0) {
|
||||
|
||||
if (this.graph.parentOf.get(node) != null && this.graph.parentOf.get(node).leftAmount == 0
|
||||
|| node.requestedAmount == 0) {
|
||||
SLOT_DISABLED.draw(point.x, point.y);
|
||||
} else {
|
||||
SLOT_DEFAULT.draw(point.x, point.y);
|
||||
}
|
||||
|
||||
} else if (!this.graph.childrenOf.containsKey(node)) {
|
||||
SLOT_MISSING.draw(point.x, point.y);
|
||||
} else if (collapsedItems.contains(node.ingrItem)) {
|
||||
|
||||
if (this.graph.subtreeMissingCache.contains(node)) {
|
||||
SLOT_MISSING.draw(point.x, point.y);
|
||||
} else {
|
||||
SLOT_CRAFTING.draw(point.x, point.y);
|
||||
}
|
||||
|
||||
} else if (!this.graph.childrenNeedCraftingCache.contains(node)) {
|
||||
SLOT_CRAFTING.draw(point.x, point.y);
|
||||
} else {
|
||||
SLOT_DEFAULT.draw(point.x, point.y);
|
||||
}
|
||||
|
||||
} else {
|
||||
SLOT_DEFAULT.draw(point.x, point.y);
|
||||
}
|
||||
|
||||
GuiContainerManager
|
||||
.drawItem(point.x + (SLOT_SIZE - 16) / 2, point.y + (SLOT_SIZE - 16) / 2, node.emptyStack, true);
|
||||
|
||||
if (node.requestedAmount > 0 && node.leftAmount == 0) {
|
||||
drawStackSize(node, node.ingrItem.getStackSize(node.requestedAmount), point.x, point.y, true);
|
||||
} else if (node.requestedAmount > 0 && node.leftAmount > 0) {
|
||||
final long amount = hovered && node.prefItem != null
|
||||
&& this.graph.roots.contains(node)
|
||||
&& this.collapsedRecipes.contains(node.prefItem.recipeId)
|
||||
&& NEIClientUtils.controlKey() ? node.currentRequestedAmount : node.requestedAmount;
|
||||
|
||||
drawStackSize(node, node.ingrItem.getStackSize(amount), point.x, point.y, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void drawStackSize(ItemTreeSlot node, long stackSize, int x, int y, boolean resolved) {
|
||||
String amountString = stackSize < 10_000 ? String.valueOf(stackSize)
|
||||
: ReadableNumberConverter.INSTANCE.toWideReadableForm(stackSize);
|
||||
|
||||
if (node.isFluidDisplay) {
|
||||
amountString += "L";
|
||||
}
|
||||
|
||||
final boolean hasOutputChance = node.prefItem != null && node.prefItem.chance != PositionedStack.CHANCE_FULL;
|
||||
int color = 0xFFFFFF;
|
||||
|
||||
if (node.ingrItem.chance != PositionedStack.CHANCE_FULL || hasOutputChance || node.isContainerItem) {
|
||||
amountString = "~" + amountString;
|
||||
color = 0xFFAA00;
|
||||
}
|
||||
|
||||
if (resolved) {
|
||||
color = 0x55FF55;
|
||||
}
|
||||
|
||||
NEIClientUtils.drawNEIOverlayText(
|
||||
amountString,
|
||||
new Rectangle4i(
|
||||
x + (SLOT_SIZE - 16) / 2,
|
||||
y + (SLOT_SIZE - 16) / 2,
|
||||
SLOT_SIZE - (SLOT_SIZE - 16),
|
||||
SLOT_SIZE - (SLOT_SIZE - 16)),
|
||||
1,
|
||||
color,
|
||||
true,
|
||||
node.isFluidDisplay ? Alignment.BottomLeft : Alignment.BottomRight);
|
||||
}
|
||||
|
||||
protected void drawSlotBadge(ItemTreeSlot node, int x, int y) {
|
||||
final float[] badge = badgeRect(x, y);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glTranslatef(badge[0], badge[1], 4.0f);
|
||||
GL11.glScalef(BADGE_SCALE, BADGE_SCALE, 1.0f);
|
||||
|
||||
if (node.info.hasImageOrItem()) {
|
||||
final DrawableResource image = node.info.getImage();
|
||||
|
||||
if (image != null) {
|
||||
image.draw(0, 0);
|
||||
} else {
|
||||
GuiContainerManager.drawItem(0, 0, node.info.getItemStack(), true);
|
||||
}
|
||||
|
||||
} else {
|
||||
ICON_CRAFTING.draw(0, 0);
|
||||
}
|
||||
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
private void drawCollapseIcon(ItemTreeSlot node, Set<BookmarkItem> collapsedItems, boolean hover) {
|
||||
|
||||
if (!this.graph.childrenOf.containsKey(node) || node.prefItem == null
|
||||
|| this.collapsedRecipes.contains(node.prefItem.recipeId) && !this.graph.roots.contains(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DrawableResource icon;
|
||||
|
||||
if (collapsedItems.contains(node.ingrItem)) {
|
||||
icon = hover ? ICON_COLLAPSED_HOVER : ICON_COLLAPSED;
|
||||
} else {
|
||||
icon = hover ? ICON_EXPANDED_HOVER : ICON_EXPANDED;
|
||||
}
|
||||
|
||||
final Point point = this.pointByNode.get(node);
|
||||
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
icon.draw(point.x + SLOT_SIZE / 2 - icon.width - 1, point.y + SLOT_SIZE + 1);
|
||||
}
|
||||
|
||||
private void drawDeleteButton(Point point) {
|
||||
final float[] badge = badgeRect(point.x, point.y);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glTranslatef(badge[0], badge[1], 350.0f);
|
||||
GL11.glScalef(BADGE_SCALE, BADGE_SCALE, 1.0f);
|
||||
ICON_REMOVE.draw(0, 0);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
private static float[] badgeRect(float x, float y) {
|
||||
final float w = ICON_REMOVE.width * BADGE_SCALE;
|
||||
final float h = ICON_REMOVE.height * BADGE_SCALE;
|
||||
|
||||
return new float[] { x - w / 2 + 1, y - h / 2 + 1, w, h };
|
||||
}
|
||||
|
||||
private void drawParentLine(ItemTreeSlot node) {
|
||||
|
||||
if (!this.graph.parentOf.containsKey(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Point point = this.pointByNode.get(node);
|
||||
final float x0 = point.x + SLOT_SIZE / 2f;
|
||||
final float y0 = point.y - 1;
|
||||
|
||||
final ItemTreeSlot parentNode = this.graph.parentOf.get(node);
|
||||
final Point parentPoint = this.pointByNode.get(parentNode);
|
||||
final float x1 = parentPoint.x + SLOT_SIZE / 2f;
|
||||
final float y1 = parentPoint.y + SLOT_SIZE;
|
||||
|
||||
if (x0 != x1) {
|
||||
final float midY = (y0 + y1) / 2f;
|
||||
|
||||
NEIClientUtils.drawRect(x0, Math.min(y0, midY), 1, Math.abs(midY - y0) + 1, LINE_COLOR);
|
||||
NEIClientUtils.drawRect(Math.min(x0, x1), midY, Math.abs(x1 - x0) + 1, 1, LINE_COLOR);
|
||||
NEIClientUtils.drawRect(x1, Math.min(midY, y1), 1, Math.abs(y1 - midY) + 1, LINE_COLOR);
|
||||
} else {
|
||||
NEIClientUtils.drawRect(x0, Math.min(y0, y1), 1, Math.abs(y1 - y0) + 1, LINE_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawScrollbars() {
|
||||
if (this.treeWidth <= 0 || this.treeHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final float scrollXPct = Camera.clamp(this.camera.scrollX / this.treeWidth, 0.0f, 1.0f);
|
||||
final float scrollYPct = Camera.clamp(this.camera.scrollY / this.treeHeight, 0.0f, 1.0f);
|
||||
|
||||
GL11.glTranslatef(0, 0, 500.0f);
|
||||
|
||||
SCROLLBAR_H.draw(
|
||||
this.camera.widgetX + this.camera.widgetW - 3,
|
||||
(int) (this.camera.widgetY + scrollYPct * (this.camera.widgetH - SCROLLBAR_H.height)));
|
||||
|
||||
SCROLLBAR_W.draw(
|
||||
(int) (this.camera.widgetX + scrollXPct * (this.camera.widgetW - SCROLLBAR_W.width)),
|
||||
this.camera.widgetY + this.camera.widgetH - 3);
|
||||
|
||||
GL11.glTranslatef(0, 0, -500.0f);
|
||||
}
|
||||
|
||||
public void recalculateCoordinates(Set<RecipeId> collapsedRecipes, Set<BookmarkItem> collapsedItems,
|
||||
ItemTreeSlot anchor) {
|
||||
final Map<ItemTreeSlot, Integer> offsetFromParent = new HashMap<>();
|
||||
final Point anchorOldPoint = anchor != null ? resolveAnchorPoint(anchor) : null;
|
||||
|
||||
this.collapsedRecipes.clear();
|
||||
this.pointByNode.clear();
|
||||
this.rows.clear();
|
||||
this.treeWidth = 0;
|
||||
this.treeHeight = 0;
|
||||
|
||||
this.collapsedRecipes.addAll(collapsedRecipes);
|
||||
|
||||
for (ItemTreeSlot node : this.graph.items) {
|
||||
this.pointByNode.put(node, new Point(0, 0));
|
||||
}
|
||||
|
||||
final List<int[]> contour = new ArrayList<>();
|
||||
|
||||
for (ItemTreeSlot root : this.graph.roots) {
|
||||
final List<int[]> layout = computeLayout(root, collapsedItems, offsetFromParent);
|
||||
final int shift = contour.isEmpty() ? 0 : requiredRootShift(contour, layout);
|
||||
|
||||
mergeContour(contour, layout, shift);
|
||||
assignPositions(root, shift, 0, collapsedItems, offsetFromParent);
|
||||
}
|
||||
|
||||
int minX = 0;
|
||||
|
||||
for (Point point : this.pointByNode.values()) {
|
||||
minX = Math.min(minX, point.x);
|
||||
}
|
||||
|
||||
if (minX < 0) {
|
||||
for (Point point : this.pointByNode.values()) {
|
||||
point.x -= minX;
|
||||
}
|
||||
}
|
||||
|
||||
for (List<ItemTreeSlot> row : this.rows.values()) {
|
||||
for (ItemTreeSlot node : row) {
|
||||
this.treeWidth = Math.max(this.treeWidth, this.pointByNode.get(node).x + CraftingTreeCanvas.SLOT_SIZE);
|
||||
this.treeHeight = Math
|
||||
.max(this.treeHeight, this.pointByNode.get(node).y + CraftingTreeCanvas.SLOT_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (anchorOldPoint != null) {
|
||||
final Point anchorNewPoint = resolveAnchorPoint(anchor);
|
||||
|
||||
if (anchorNewPoint != null) {
|
||||
this.camera.scrollX += anchorNewPoint.x - anchorOldPoint.x;
|
||||
this.camera.scrollY += anchorNewPoint.y - anchorOldPoint.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Point resolveAnchorPoint(ItemTreeSlot anchor) {
|
||||
Point result = this.pointByNode.get(anchor);
|
||||
|
||||
if (result == null) {
|
||||
for (Map.Entry<ItemTreeSlot, Point> entry : this.pointByNode.entrySet()) {
|
||||
if (entry.getKey().ingrItem.equals(anchor.ingrItem)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<int[]> computeLayout(ItemTreeSlot node, Set<BookmarkItem> collapsedItems,
|
||||
Map<ItemTreeSlot, Integer> offsetFromParent) {
|
||||
List<ItemTreeSlot> children = Collections.emptyList();
|
||||
|
||||
if (!collapsedItems.contains(node.ingrItem)) {
|
||||
children = this.graph.childrenOf.getOrDefault(node, Collections.emptyList());
|
||||
}
|
||||
|
||||
if (children.isEmpty()) {
|
||||
final List<int[]> rows = new ArrayList<>();
|
||||
rows.add(new int[] { 0, 0 });
|
||||
return rows;
|
||||
}
|
||||
|
||||
final int[] offsets = new int[children.size()];
|
||||
final List<int[]> combined = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
final List<int[]> childRows = computeLayout(children.get(i), collapsedItems, offsetFromParent);
|
||||
final int shift = i == 0 ? 0 : requiredShift(combined, childRows, X_SPACING);
|
||||
|
||||
offsets[i] = shift;
|
||||
mergeContour(combined, childRows, shift);
|
||||
}
|
||||
|
||||
final int selfX = (offsets[0] + offsets[children.size() - 1]) / 2;
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
offsetFromParent.put(children.get(i), offsets[i] - selfX);
|
||||
}
|
||||
|
||||
for (int[] row : combined) {
|
||||
row[0] -= selfX;
|
||||
row[1] -= selfX;
|
||||
}
|
||||
|
||||
final List<int[]> rows = new ArrayList<>(combined.size() + 1);
|
||||
rows.add(new int[] { 0, 0 });
|
||||
rows.addAll(combined);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static int requiredShift(List<int[]> existing, List<int[]> incoming, int spacing) {
|
||||
int shift = 0;
|
||||
final int rows = Math.min(existing.size(), incoming.size());
|
||||
|
||||
for (int r = 0; r < rows; r++) {
|
||||
shift = Math.max(shift, existing.get(r)[1] + spacing - incoming.get(r)[0]);
|
||||
}
|
||||
|
||||
return shift;
|
||||
}
|
||||
|
||||
private static int requiredRootShift(List<int[]> existing, List<int[]> incoming) {
|
||||
int existingRight = Integer.MIN_VALUE;
|
||||
|
||||
for (int[] row : existing) {
|
||||
existingRight = Math.max(existingRight, row[1]);
|
||||
}
|
||||
|
||||
int incomingLeft = Integer.MAX_VALUE;
|
||||
|
||||
for (int[] row : incoming) {
|
||||
incomingLeft = Math.min(incomingLeft, row[0]);
|
||||
}
|
||||
|
||||
return Math.max(0, existingRight + ROOT_SPACING - incomingLeft);
|
||||
}
|
||||
|
||||
private static void mergeContour(List<int[]> target, List<int[]> source, int shift) {
|
||||
for (int r = 0; r < source.size(); r++) {
|
||||
final int left = source.get(r)[0] + shift;
|
||||
final int right = source.get(r)[1] + shift;
|
||||
|
||||
if (r < target.size()) {
|
||||
target.get(r)[0] = Math.min(target.get(r)[0], left);
|
||||
target.get(r)[1] = Math.max(target.get(r)[1], right);
|
||||
} else {
|
||||
target.add(new int[] { left, right });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assignPositions(ItemTreeSlot node, int x, int y, Set<BookmarkItem> collapsedItems,
|
||||
Map<ItemTreeSlot, Integer> offsetFromParent) {
|
||||
this.rows.computeIfAbsent(y, ignored -> new ArrayList<>()).add(node);
|
||||
this.pointByNode.put(node, new Point(x, y));
|
||||
|
||||
final int childY = y + CraftingTreeCanvas.SLOT_SIZE + Y_SPACING;
|
||||
|
||||
if (!collapsedItems.contains(node.ingrItem)) {
|
||||
for (ItemTreeSlot child : this.graph.childrenOf.getOrDefault(node, Collections.emptyList())) {
|
||||
assignPositions(child, x + offsetFromParent.get(child), childY, collapsedItems, offsetFromParent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||
|
||||
public class CraftingTreeGraph {
|
||||
|
||||
public final List<ItemTreeSlot> roots = new ArrayList<>();
|
||||
public final List<ItemTreeSlot> items = new ArrayList<>();
|
||||
public final Map<ItemTreeSlot, ItemTreeSlot> parentOf = new HashMap<>();
|
||||
public final Map<ItemTreeSlot, List<ItemTreeSlot>> childrenOf = new HashMap<>();
|
||||
|
||||
public final Set<ItemTreeSlot> subtreeMissingCache = new HashSet<>();
|
||||
public final Set<ItemTreeSlot> childrenNeedCraftingCache = new HashSet<>();
|
||||
|
||||
public void rebuild(CraftingTreeMath math) {
|
||||
this.subtreeMissingCache.clear();
|
||||
this.childrenNeedCraftingCache.clear();
|
||||
|
||||
math.refresh();
|
||||
|
||||
this.roots.clear();
|
||||
this.roots.addAll(math.rootSlots);
|
||||
this.items.clear();
|
||||
this.items.addAll(math.items);
|
||||
this.parentOf.clear();
|
||||
this.parentOf.putAll(math.parentOf);
|
||||
this.childrenOf.clear();
|
||||
this.childrenOf.putAll(math.childrenOf);
|
||||
|
||||
for (ItemTreeSlot root : this.roots) {
|
||||
computeSubtreeMissing(root);
|
||||
}
|
||||
|
||||
for (Map.Entry<ItemTreeSlot, List<ItemTreeSlot>> entry : this.childrenOf.entrySet()) {
|
||||
if (entry.getValue().stream().anyMatch(child -> child.leftAmount != 0)) {
|
||||
this.childrenNeedCraftingCache.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<RecipeId> collectUnusedRecipes(ItemTreeSlot node) {
|
||||
final Map<RecipeId, Set<RecipeId>> children = new HashMap<>();
|
||||
final Map<RecipeId, Set<RecipeId>> consumers = new HashMap<>();
|
||||
int rootCount = 0;
|
||||
|
||||
for (ItemTreeSlot item : this.items) {
|
||||
if (item.prefItem != null) {
|
||||
children.computeIfAbsent(item.ingrItem.recipeId, k -> new HashSet<>()).add(item.prefItem.recipeId);
|
||||
consumers.computeIfAbsent(item.prefItem.recipeId, k -> new HashSet<>()).add(item.ingrItem.recipeId);
|
||||
|
||||
if (item.ingrItem.recipeId.equals(node.prefItem.recipeId)) {
|
||||
rootCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Set<RecipeId> unusedRecipes = new HashSet<>();
|
||||
final RecipeId rootRecipeId = node.prefItem.recipeId;
|
||||
|
||||
if (rootCount <= 1) {
|
||||
unusedRecipes.add(rootRecipeId);
|
||||
collectSubtree(rootRecipeId, children, consumers, unusedRecipes);
|
||||
}
|
||||
|
||||
return unusedRecipes;
|
||||
}
|
||||
|
||||
private void collectSubtree(RecipeId recipeId, Map<RecipeId, Set<RecipeId>> children,
|
||||
Map<RecipeId, Set<RecipeId>> consumers, Set<RecipeId> unusedRecipes) {
|
||||
for (RecipeId child : children.getOrDefault(recipeId, Collections.emptySet())) {
|
||||
if (consumers.get(child).size() == 1 && unusedRecipes.add(child)) {
|
||||
collectSubtree(child, children, consumers, unusedRecipes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean computeSubtreeMissing(ItemTreeSlot node) {
|
||||
boolean hasMissing = !this.childrenOf.containsKey(node);
|
||||
|
||||
for (ItemTreeSlot child : this.childrenOf.getOrDefault(node, Collections.emptyList())) {
|
||||
hasMissing |= child.leftAmount != 0 && computeSubtreeMissing(child);
|
||||
}
|
||||
|
||||
if (hasMissing) {
|
||||
this.subtreeMissingCache.add(node);
|
||||
}
|
||||
|
||||
return hasMissing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import codechicken.nei.bookmark.BookmarkItem;
|
||||
import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType;
|
||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||
import codechicken.nei.recipe.chain.RecipeChainMath;
|
||||
|
||||
public class CraftingTreeMath extends RecipeChainMath {
|
||||
|
||||
public final List<ItemTreeSlot> rootSlots = new ArrayList<>();
|
||||
public final List<ItemTreeSlot> items = new ArrayList<>();
|
||||
public final Map<ItemTreeSlot, ItemTreeSlot> parentOf = new HashMap<>();
|
||||
public final Map<ItemTreeSlot, List<ItemTreeSlot>> childrenOf = new HashMap<>();
|
||||
|
||||
private final Deque<ItemTreeSlot> parentStack = new ArrayDeque<>();
|
||||
private ItemTreeSlot lastItem = null;
|
||||
|
||||
public CraftingTreeMath(List<BookmarkItem> chainItems, Set<RecipeId> collapsedRecipes) {
|
||||
super(chainItems, collapsedRecipes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeChainMath refresh() {
|
||||
this.rootSlots.clear();
|
||||
this.items.clear();
|
||||
this.parentOf.clear();
|
||||
this.childrenOf.clear();
|
||||
this.parentStack.clear();
|
||||
this.lastItem = null;
|
||||
|
||||
return super.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void calculationStepTrigger(BookmarkItem ingrItem, BookmarkItem prefItem, long requestedAmount,
|
||||
long containerAmount, long inventoryAmount, long ingrAmount, long produced) {
|
||||
|
||||
if (ingrItem.type == BookmarkItemType.RESULT) {
|
||||
final ItemTreeSlot slot = new ItemTreeSlot(
|
||||
ingrItem,
|
||||
Math.max(0, this.requiredAmount.get(prefItem) - requestedAmount),
|
||||
requestedAmount,
|
||||
this.requiredAmount.get(prefItem),
|
||||
0,
|
||||
prefItem);
|
||||
|
||||
this.rootSlots.add(slot);
|
||||
this.items.add(slot);
|
||||
|
||||
this.lastItem = slot;
|
||||
} else {
|
||||
|
||||
if (prefItem != null && hasContainerItem(prefItem.itemStack)) {
|
||||
|
||||
if (requestedAmount > 1) {
|
||||
final long requestedSteps = getToolsContainerItems(
|
||||
prefItem.itemStack.copy(),
|
||||
requestedAmount).leftSteps;
|
||||
requestedAmount = (long) Math
|
||||
.ceil((double) requestedAmount / Math.max(1, requestedAmount - requestedSteps));
|
||||
}
|
||||
|
||||
if (containerAmount > 1) {
|
||||
final long containerSteps = getToolsContainerItems(
|
||||
prefItem.itemStack.copy(),
|
||||
containerAmount).leftSteps;
|
||||
containerAmount = (long) Math
|
||||
.ceil((double) containerAmount / Math.max(1, containerAmount - containerSteps));
|
||||
}
|
||||
}
|
||||
|
||||
final ItemTreeSlot slot = new ItemTreeSlot(
|
||||
ingrItem,
|
||||
0,
|
||||
requestedAmount,
|
||||
produced,
|
||||
containerAmount,
|
||||
prefItem);
|
||||
|
||||
this.items.add(slot);
|
||||
this.parentOf.put(slot, this.parentStack.peek());
|
||||
this.childrenOf.computeIfAbsent(this.parentStack.peek(), k -> new ArrayList<>()).add(slot);
|
||||
|
||||
this.lastItem = slot;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareIngredients(RecipeId recipeId, long multiplier, List<RecipeId> visited) {
|
||||
this.parentStack.push(this.lastItem);
|
||||
super.prepareIngredients(recipeId, multiplier, visited);
|
||||
this.lastItem = this.parentStack.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void calculateSuitableRecipe(BookmarkItem ingrItem, long ingrMultiplier, List<RecipeId> visited) {
|
||||
final BookmarkItem prefItem = this.preferredItems.get(ingrItem);
|
||||
super.calculateSuitableRecipe(ingrItem, ingrMultiplier, visited);
|
||||
|
||||
if (prefItem != null && !isForeignBoxBoundary(ingrItem)
|
||||
&& !visited.contains(prefItem.recipeId)
|
||||
&& !this.childrenOf.containsKey(this.lastItem)) {
|
||||
visited.add(prefItem.recipeId);
|
||||
prepareIngredients(prefItem.recipeId, visited);
|
||||
visited.remove(prefItem.recipeId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void prepareIngredients(RecipeId recipeId, List<RecipeId> visited) {
|
||||
this.parentStack.push(this.lastItem);
|
||||
|
||||
for (BookmarkItem item : this.ingredientsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
if (!item.emptyFactor()) {
|
||||
calculateSuitableRecipe(item, visited);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastItem = this.parentStack.pop();
|
||||
}
|
||||
|
||||
protected void calculateSuitableRecipe(BookmarkItem ingrItem, List<RecipeId> visited) {
|
||||
final BookmarkItem prefItem = this.preferredItems.get(ingrItem);
|
||||
final ItemTreeSlot slot = new ItemTreeSlot(ingrItem, 0, 0, 0, 0, prefItem);
|
||||
|
||||
this.items.add(slot);
|
||||
this.parentOf.put(slot, this.parentStack.peek());
|
||||
this.childrenOf.computeIfAbsent(this.parentStack.peek(), k -> new ArrayList<>()).add(slot);
|
||||
|
||||
this.lastItem = slot;
|
||||
|
||||
if (prefItem != null && !isForeignBoxBoundary(ingrItem) && !visited.contains(prefItem.recipeId)) {
|
||||
visited.add(prefItem.recipeId);
|
||||
prepareIngredients(prefItem.recipeId, visited);
|
||||
visited.remove(prefItem.recipeId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.bookmark.BookmarkItem;
|
||||
|
||||
class CraftingTreeState {
|
||||
|
||||
enum CollapseMode {
|
||||
EXPANDED,
|
||||
SMART,
|
||||
ALL
|
||||
}
|
||||
|
||||
enum StatsSection {
|
||||
INGREDIENTS_NEEDED,
|
||||
INGREDIENTS_AVAILABLE,
|
||||
CRAFTING_NEEDED,
|
||||
CRAFTING_AVAILABLE,
|
||||
RESULTS,
|
||||
REMAINDERS,
|
||||
HANDLERS
|
||||
}
|
||||
|
||||
private static final String KEY_STATS_VISIBLE = "craftingTree.statsVisible";
|
||||
private static final String KEY_STATS_SECTIONS = "craftingTree.statsSections";
|
||||
private static final String KEY_USE_INVENTORY_SNAPSHOT = "craftingTree.useInventorySnapshot";
|
||||
private static final String KEY_COLLAPSE_MODE = "craftingTree.collapseMode";
|
||||
|
||||
public boolean statsPanelVisible = true;
|
||||
public final Set<StatsSection> visibleStatsSections = EnumSet.allOf(StatsSection.class);
|
||||
public boolean useInventorySnapshot = false;
|
||||
public CollapseMode collapseMode = CollapseMode.EXPANDED;
|
||||
|
||||
public final Set<BookmarkItem> collapsedItems = new HashSet<>();
|
||||
public ItemTreeSlot replaceRecipeNode;
|
||||
|
||||
public SearchResult searchItemResult;
|
||||
public SearchResult searchStatsResult;
|
||||
|
||||
public void load() {
|
||||
this.statsPanelVisible = !NEIClientConfig.world.nbt.hasKey(KEY_STATS_VISIBLE)
|
||||
|| NEIClientConfig.world.nbt.getBoolean(KEY_STATS_VISIBLE);
|
||||
|
||||
final int savedSectionsMask = NEIClientConfig.world.nbt.hasKey(KEY_STATS_SECTIONS)
|
||||
? NEIClientConfig.world.nbt.getInteger(KEY_STATS_SECTIONS)
|
||||
: -1;
|
||||
|
||||
this.visibleStatsSections.clear();
|
||||
|
||||
for (StatsSection section : StatsSection.values()) {
|
||||
if ((savedSectionsMask & (1 << section.ordinal())) != 0) {
|
||||
this.visibleStatsSections.add(section);
|
||||
}
|
||||
}
|
||||
|
||||
this.useInventorySnapshot = NEIClientConfig.world.nbt.getBoolean(KEY_USE_INVENTORY_SNAPSHOT);
|
||||
|
||||
final CollapseMode[] collapseModes = CollapseMode.values();
|
||||
final int savedCollapseMode = NEIClientConfig.world.nbt.getInteger(KEY_COLLAPSE_MODE);
|
||||
this.collapseMode = savedCollapseMode >= 0 && savedCollapseMode < collapseModes.length
|
||||
? collapseModes[savedCollapseMode]
|
||||
: CollapseMode.EXPANDED;
|
||||
this.replaceRecipeNode = null;
|
||||
}
|
||||
|
||||
public void toggleStatsPanelVisible() {
|
||||
this.statsPanelVisible = !this.statsPanelVisible;
|
||||
NEIClientConfig.world.nbt.setBoolean(KEY_STATS_VISIBLE, this.statsPanelVisible);
|
||||
}
|
||||
|
||||
public void toggleStatsSection(StatsSection section) {
|
||||
|
||||
if (!this.visibleStatsSections.remove(section)) {
|
||||
this.visibleStatsSections.add(section);
|
||||
}
|
||||
|
||||
int mask = 0;
|
||||
|
||||
for (StatsSection visibleSection : this.visibleStatsSections) {
|
||||
mask |= 1 << visibleSection.ordinal();
|
||||
}
|
||||
|
||||
NEIClientConfig.world.nbt.setInteger(KEY_STATS_SECTIONS, mask);
|
||||
}
|
||||
|
||||
public void toggleUseInventorySnapshot() {
|
||||
this.useInventorySnapshot = !this.useInventorySnapshot;
|
||||
NEIClientConfig.world.nbt.setBoolean(KEY_USE_INVENTORY_SNAPSHOT, this.useInventorySnapshot);
|
||||
}
|
||||
|
||||
public void cycleCollapseMode() {
|
||||
final CollapseMode[] collapseModes = CollapseMode.values();
|
||||
this.collapseMode = collapseModes[(this.collapseMode.ordinal() + 1) % collapseModes.length];
|
||||
NEIClientConfig.world.nbt.setInteger(KEY_COLLAPSE_MODE, this.collapseMode.ordinal());
|
||||
}
|
||||
|
||||
public SearchResult activeSearchResult() {
|
||||
return this.searchItemResult != null ? this.searchItemResult : this.searchStatsResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import codechicken.nei.GuiNEIButton;
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
|
||||
class CraftingTreeToolbar {
|
||||
|
||||
private static class GuiStatsButton extends GuiNEIButton {
|
||||
|
||||
private final Supplier<DrawableResource> icon;
|
||||
private final BooleanSupplier isEnabled;
|
||||
private final Supplier<String> tooltip;
|
||||
|
||||
public GuiStatsButton(int buttonId, Supplier<DrawableResource> icon, Supplier<String> tooltip,
|
||||
BooleanSupplier isEnabled) {
|
||||
super(buttonId, 0, 0, 16, 16, "");
|
||||
this.tooltip = tooltip;
|
||||
this.icon = icon;
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public String getTooltip() {
|
||||
return this.tooltip.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHoverState(boolean mouseOver) {
|
||||
return this.isEnabled.getAsBoolean() ? 0 : super.getHoverState(mouseOver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawContent(Minecraft minecraft, int y, int x, boolean mouseOver) {
|
||||
final DrawableResource icon = this.icon.get();
|
||||
final int iconX = this.xPosition + (this.width - icon.width / 2) / 2;
|
||||
final int iconY = this.yPosition + (this.height - icon.height / 2) / 2;
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glTranslated(iconX, iconY, 0);
|
||||
GL11.glScalef(0.5f, 0.5f, 1);
|
||||
|
||||
icon.bindTexture();
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
icon.draw(0, 0);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final DrawableResource STATS_TEXTURE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
2,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource COMPACT_STATS_TEXTURE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
20,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource FIT_TEXTURE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
38,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource INV_TEXTURE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
56,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource LINK_TEXTURE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
74,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource ICON_COLLAPSED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
92,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource ICON_INDETERMINATE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
110,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource ICON_EXPANDED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
128,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource ICON_SCREENSHOT = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
146,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource ICON_SETTINGS = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
164,
|
||||
66,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final int BUTTON_SPACING = 20;
|
||||
|
||||
public static final int INV_BUTTON_ID = 0;
|
||||
public static final int FIT_BUTTON_ID = 1;
|
||||
public static final int COLLAPSE_ALL_BUTTON_ID = 2;
|
||||
public static final int TOGGLE_STATS_BUTTON_ID = 3;
|
||||
public static final int STATS_SECTIONS_BUTTON_ID = 4;
|
||||
public static final int LINK_BUTTON_ID = 5;
|
||||
public static final int SCREENSHOT_BUTTON_ID = 6;
|
||||
|
||||
private final CraftingTreeState uiState;
|
||||
private final GuiStatsButton[] buttons;
|
||||
|
||||
public CraftingTreeToolbar(CraftingTreeState uiState) {
|
||||
this.uiState = uiState;
|
||||
|
||||
this.buttons = new GuiStatsButton[] {
|
||||
new GuiStatsButton(
|
||||
INV_BUTTON_ID,
|
||||
() -> INV_TEXTURE,
|
||||
() -> NEIClientUtils.translate(
|
||||
this.uiState.useInventorySnapshot ? "bookmark.tree.toolbar.inventory_on"
|
||||
: "bookmark.tree.toolbar.inventory_off"),
|
||||
() -> this.uiState.useInventorySnapshot),
|
||||
|
||||
new GuiStatsButton(
|
||||
FIT_BUTTON_ID,
|
||||
() -> FIT_TEXTURE,
|
||||
() -> NEIClientUtils.translate("bookmark.tree.toolbar.fit_to_view"),
|
||||
() -> false),
|
||||
|
||||
new GuiStatsButton(COLLAPSE_ALL_BUTTON_ID, () -> switch (this.uiState.collapseMode) {
|
||||
case EXPANDED -> ICON_EXPANDED;
|
||||
case SMART -> ICON_INDETERMINATE;
|
||||
case ALL -> ICON_COLLAPSED;
|
||||
}, () -> NEIClientUtils.translate(switch (this.uiState.collapseMode) {
|
||||
case EXPANDED -> "bookmark.tree.toolbar.collapse_resolved";
|
||||
case SMART -> "bookmark.tree.toolbar.collapse_all";
|
||||
case ALL -> "bookmark.tree.toolbar.expand_all";
|
||||
}), () -> this.uiState.collapseMode != CraftingTreeState.CollapseMode.EXPANDED),
|
||||
|
||||
new GuiStatsButton(
|
||||
TOGGLE_STATS_BUTTON_ID,
|
||||
() -> this.uiState.visibleStatsSections.size() == CraftingTreeState.StatsSection.values().length
|
||||
? STATS_TEXTURE
|
||||
: COMPACT_STATS_TEXTURE,
|
||||
() -> NEIClientUtils.translate(
|
||||
this.uiState.statsPanelVisible ? "bookmark.tree.toolbar.stats_on"
|
||||
: "bookmark.tree.toolbar.stats_off"),
|
||||
() -> this.uiState.statsPanelVisible),
|
||||
|
||||
new GuiStatsButton(
|
||||
STATS_SECTIONS_BUTTON_ID,
|
||||
() -> ICON_SETTINGS,
|
||||
() -> NEIClientUtils.translate("bookmark.tree.toolbar.stats_sections"),
|
||||
() -> false),
|
||||
new GuiStatsButton(
|
||||
LINK_BUTTON_ID,
|
||||
() -> LINK_TEXTURE,
|
||||
() -> NEIClientUtils.translate("bookmark.tree.toolbar.link"),
|
||||
() -> false),
|
||||
new GuiStatsButton(
|
||||
SCREENSHOT_BUTTON_ID,
|
||||
() -> ICON_SCREENSHOT,
|
||||
() -> NEIClientUtils.translate("bookmark.tree.toolbar.screenshot"),
|
||||
() -> false)
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public void layout(int x, int y) {
|
||||
for (int i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].xPosition = x + BUTTON_SPACING * i;
|
||||
this.buttons[i].yPosition = y;
|
||||
}
|
||||
}
|
||||
|
||||
public void addTo(List<GuiButton> buttonList) {
|
||||
buttonList.addAll(Arrays.asList(this.buttons));
|
||||
}
|
||||
|
||||
public GuiButton getButton(int buttonId) {
|
||||
|
||||
for (int i = 0; i < this.buttons.length; i++) {
|
||||
if (this.buttons[i].id == buttonId) {
|
||||
return this.buttons[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getTooltip(Minecraft mc, int mousex, int mousey) {
|
||||
for (GuiStatsButton button : this.buttons) {
|
||||
if (button.mousePressed(mc, mousex, mousey)) {
|
||||
return button.getTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,90 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.bookmark.BookmarkItem;
|
||||
import codechicken.nei.recipe.GuiRecipeTab;
|
||||
import codechicken.nei.recipe.HandlerInfo;
|
||||
import codechicken.nei.recipe.StackInfo;
|
||||
|
||||
class ItemTreeSlot {
|
||||
|
||||
public final BookmarkItem ingrItem;
|
||||
public final BookmarkItem prefItem;
|
||||
|
||||
public final long ingeritRequestedAmount;
|
||||
public final long currentRequestedAmount;
|
||||
public final long requestedAmount;
|
||||
|
||||
public final long containerAmount;
|
||||
public final long producedAmount;
|
||||
public final long leftAmount;
|
||||
|
||||
public final ItemStack emptyStack;
|
||||
public final ItemStack itemStack;
|
||||
public final boolean isFluidDisplay;
|
||||
public final boolean isContainerItem;
|
||||
|
||||
public final HandlerInfo info;
|
||||
public final long multiplier;
|
||||
|
||||
public ItemTreeSlot(BookmarkItem ingrItem, long ingeritRequestedAmount, long currentRequestedAmount,
|
||||
long producedAmount, long containerAmount, BookmarkItem prefItem) {
|
||||
|
||||
this.ingrItem = ingrItem;
|
||||
this.ingeritRequestedAmount = ingeritRequestedAmount;
|
||||
this.currentRequestedAmount = currentRequestedAmount;
|
||||
this.requestedAmount = ingeritRequestedAmount + currentRequestedAmount;
|
||||
|
||||
this.containerAmount = containerAmount;
|
||||
this.producedAmount = producedAmount;
|
||||
this.leftAmount = producedAmount + containerAmount;
|
||||
this.prefItem = prefItem;
|
||||
|
||||
if (prefItem != null) {
|
||||
this.info = handlerInfo(prefItem.recipeId.getHandlerName());
|
||||
this.multiplier = prefItem.getMultiplierFromAmount(this.requestedAmount);
|
||||
|
||||
this.itemStack = prefItem.getItemStack(this.requestedAmount);
|
||||
this.emptyStack = prefItem.getItemStack(0);
|
||||
} else {
|
||||
this.info = null;
|
||||
this.multiplier = ingrItem.getMultiplierFromAmount(this.requestedAmount);
|
||||
this.itemStack = ingrItem.getItemStack(this.requestedAmount);
|
||||
this.emptyStack = ingrItem.getItemStack(0);
|
||||
}
|
||||
|
||||
this.isFluidDisplay = StackInfo.itemStackToNBT(this.emptyStack).hasKey("gtFluidName");
|
||||
this.isContainerItem = isContainerItem(this.emptyStack);
|
||||
}
|
||||
|
||||
private static HandlerInfo handlerInfo(String handlerName) {
|
||||
final HandlerInfo info = GuiRecipeTab.getHandlerInfo(handlerName, null);
|
||||
return info != null ? info : GuiRecipeTab.DEFAULT_HANDLER_INFO;
|
||||
}
|
||||
|
||||
private static boolean isContainerItem(ItemStack stack) {
|
||||
boolean isContainerItem = false;
|
||||
|
||||
if (stack.getItem().hasContainerItem(stack)) {
|
||||
final boolean isPausedItemDamageSound = StackInfo.isPausedItemDamageSound();
|
||||
|
||||
try {
|
||||
|
||||
StackInfo.pauseItemDamageSound(true);
|
||||
|
||||
final ItemStack containerItem = stack.getItem().getContainerItem(stack);
|
||||
|
||||
if (containerItem != null) {
|
||||
isContainerItem = stack.getItem() == containerItem.getItem();
|
||||
}
|
||||
|
||||
} finally {
|
||||
StackInfo.pauseItemDamageSound(isPausedItemDamageSound);
|
||||
}
|
||||
}
|
||||
|
||||
return isContainerItem;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
|
||||
class SearchResult {
|
||||
|
||||
public final Object searchGUID;
|
||||
public ItemTreeSlot highlightNode;
|
||||
public final Set<ItemTreeSlot> searchMatches = new LinkedHashSet<>();
|
||||
|
||||
public SearchResult(Object searchGUID) {
|
||||
this.searchGUID = searchGUID;
|
||||
}
|
||||
|
||||
public boolean matchesGUID(Object guid) {
|
||||
if (guid instanceof ItemStack stack && this.searchGUID instanceof ItemStack searchStack) {
|
||||
return NEIClientUtils.areStacksSameTypeCraftingWithNBT(stack, searchStack);
|
||||
}
|
||||
|
||||
return this.searchGUID.equals(guid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,744 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.client.renderer.RenderHelper;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
import codechicken.nei.ItemSorter;
|
||||
import codechicken.nei.ItemStackAmount;
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
import codechicken.nei.NEIClientUtils.Alignment;
|
||||
import codechicken.nei.Widget;
|
||||
import codechicken.nei.bookmark.BookmarkItem;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.recipe.GuiRecipeTab;
|
||||
import codechicken.nei.recipe.HandlerInfo;
|
||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||
import codechicken.nei.recipe.StackInfo;
|
||||
import codechicken.nei.recipe.chain.RecipeChainMath;
|
||||
import codechicken.nei.scroll.ScrollBar;
|
||||
import codechicken.nei.scroll.ScrollContainer;
|
||||
import codechicken.nei.util.ReadableNumberConverter;
|
||||
|
||||
class StatsPanel extends ScrollContainer {
|
||||
|
||||
public static class HandlerStats {
|
||||
|
||||
public long iterations;
|
||||
public long recipeCount;
|
||||
}
|
||||
|
||||
private static class SectionLabel extends Widget {
|
||||
|
||||
public final String text;
|
||||
|
||||
public SectionLabel(String text) {
|
||||
this.text = text;
|
||||
this.w = GuiDraw.fontRenderer.getStringWidth(text);
|
||||
this.h = GuiDraw.fontRenderer.FONT_HEIGHT + 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(int mx, int my) {
|
||||
NEIClientUtils.gl2DRenderContext(
|
||||
() -> GuiDraw.fontRenderer.drawStringWithShadow(this.text, this.x, this.y, 0xFFFFFFFF));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemStatsSlot extends Widget {
|
||||
|
||||
public final ItemStack stack;
|
||||
public final long stackSize;
|
||||
public final RecipeId recipeId;
|
||||
public final long multiplier;
|
||||
public final HandlerInfo info;
|
||||
public final DrawableResource icon;
|
||||
private final boolean isFluidDisplay;
|
||||
public boolean highlight = false;
|
||||
|
||||
public ItemStatsSlot(ItemStack stack, long stackSize, DrawableResource icon, RecipeId recipeId,
|
||||
long multiplier) {
|
||||
this.stack = StackInfo.withAmount(stack, 0);
|
||||
this.stackSize = stackSize;
|
||||
this.icon = icon;
|
||||
this.recipeId = recipeId;
|
||||
this.multiplier = multiplier;
|
||||
this.isFluidDisplay = StackInfo.itemStackToNBT(stack).hasKey("gtFluidName");
|
||||
this.info = this.recipeId != null ? handlerInfo(this.recipeId.getHandlerName()) : null;
|
||||
this.w = SLOT_SIZE;
|
||||
this.h = SLOT_SIZE;
|
||||
}
|
||||
|
||||
public ItemStatsSlot(ItemStack stack, long stackSize, DrawableResource icon) {
|
||||
this(stack, stackSize, icon, null, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(int mx, int my) {
|
||||
|
||||
if (this.highlight) {
|
||||
SLOT_HIGHLIGHTED.draw(this.x, this.y);
|
||||
} else {
|
||||
this.icon.draw(this.x, this.y);
|
||||
}
|
||||
|
||||
GuiContainerManager
|
||||
.drawItem(this.x + (SLOT_SIZE - 16) / 2, this.y + (SLOT_SIZE - 16) / 2, this.stack, true, "");
|
||||
|
||||
drawStackSize();
|
||||
|
||||
if (mx >= this.x && mx < this.x + SLOT_SIZE && my >= this.y && my < this.y + SLOT_SIZE) {
|
||||
NEIClientUtils.gl2DRenderContext(
|
||||
() -> GuiDraw.drawRect(this.x + 1, this.y + 1, SLOT_SIZE - 2, SLOT_SIZE - 2, HOVER_COLOR));
|
||||
}
|
||||
|
||||
if (this.recipeId != null) {
|
||||
drawHandlerBadge();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawHandlerBadge() {
|
||||
final float scale = 0.6f;
|
||||
final float badgeX = x - 6 * scale;
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glTranslatef(badgeX, y - 6 * scale, 3.0f);
|
||||
GL11.glScalef(scale, scale, 1.0f);
|
||||
|
||||
if (this.info.hasImageOrItem()) {
|
||||
final DrawableResource image = this.info.getImage();
|
||||
|
||||
if (image != null) {
|
||||
image.draw(0, 0);
|
||||
} else {
|
||||
GuiContainerManager.drawItem(0, 0, this.info.getItemStack(), true);
|
||||
}
|
||||
|
||||
} else {
|
||||
ICON_CRAFTING.draw(0, 0);
|
||||
}
|
||||
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
private void drawStackSize() {
|
||||
String amountString = stackSize < 10_000 ? String.valueOf(stackSize)
|
||||
: ReadableNumberConverter.INSTANCE.toWideReadableForm(stackSize);
|
||||
|
||||
if (this.isFluidDisplay) {
|
||||
amountString += "L";
|
||||
}
|
||||
|
||||
NEIClientUtils.drawNEIOverlayText(
|
||||
amountString,
|
||||
new Rectangle4i(
|
||||
x + (SLOT_SIZE - 16) / 2,
|
||||
y + (SLOT_SIZE - 16) / 2,
|
||||
SLOT_SIZE - (SLOT_SIZE - 16),
|
||||
SLOT_SIZE - (SLOT_SIZE - 16)),
|
||||
1,
|
||||
0xFFFFFF,
|
||||
true,
|
||||
this.isFluidDisplay ? Alignment.BottomLeft : Alignment.BottomRight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class HandlerStatsSlot extends Widget {
|
||||
|
||||
public final String handlerName;
|
||||
public final HandlerInfo info;
|
||||
public final HandlerStats stats;
|
||||
public boolean highlight = false;
|
||||
|
||||
public HandlerStatsSlot(String handlerName, HandlerStats stats) {
|
||||
this.handlerName = handlerName;
|
||||
this.info = handlerInfo(handlerName);
|
||||
this.stats = stats;
|
||||
this.w = SLOT_SIZE;
|
||||
this.h = SLOT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(int mx, int my) {
|
||||
|
||||
if (this.highlight) {
|
||||
SLOT_HIGHLIGHTED.draw(this.x, this.y);
|
||||
} else {
|
||||
SLOT_DEFAULT.draw(this.x, this.y);
|
||||
}
|
||||
|
||||
if (this.info.hasImageOrItem()) {
|
||||
final DrawableResource image = this.info.getImage();
|
||||
|
||||
if (image != null) {
|
||||
image.draw(this.x + (SLOT_SIZE - 16) / 2, this.y + (SLOT_SIZE - 16) / 2);
|
||||
} else {
|
||||
GuiContainerManager.drawItem(
|
||||
this.x + (SLOT_SIZE - 16) / 2,
|
||||
this.y + (SLOT_SIZE - 16) / 2,
|
||||
this.info.getItemStack(),
|
||||
true);
|
||||
}
|
||||
} else {
|
||||
ICON_CRAFTING.draw(this.x + (SLOT_SIZE - 16) / 2, this.y + (SLOT_SIZE - 16) / 2);
|
||||
}
|
||||
|
||||
NEIClientUtils.drawNEIOverlayText(
|
||||
"x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(this.stats.iterations),
|
||||
new Rectangle4i(this.x + 1, this.y + 1, SLOT_SIZE - 2, SLOT_SIZE - 2),
|
||||
1,
|
||||
0xFFFFFF,
|
||||
true,
|
||||
Alignment.TopLeft);
|
||||
|
||||
NEIClientUtils.drawNEIOverlayText(
|
||||
ReadableNumberConverter.INSTANCE.toWideReadableForm(this.stats.recipeCount),
|
||||
new Rectangle4i(this.x + 1, this.y + 1, SLOT_SIZE - 2, SLOT_SIZE - 2),
|
||||
1,
|
||||
0xFFFFFF,
|
||||
true,
|
||||
Alignment.BottomRight);
|
||||
|
||||
if (mx >= this.x && mx < this.x + SLOT_SIZE && my >= this.y && my < this.y + SLOT_SIZE) {
|
||||
NEIClientUtils.gl2DRenderContext(
|
||||
() -> GuiDraw.drawRect(this.x + 1, this.y + 1, SLOT_SIZE - 2, SLOT_SIZE - 2, HOVER_COLOR));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final int SLOT_SIZE = 18;
|
||||
|
||||
private static final int SLOT_INLINE_GAP = 2;
|
||||
private static final int SLOT_BLOCK_GAP = 2;
|
||||
private static final int COLUMN_GAP = 10;
|
||||
|
||||
private static final int HOVER_COLOR = 0x66999999;
|
||||
|
||||
private static final DrawableResource BG_TEXTURE = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
26,
|
||||
84,
|
||||
46,
|
||||
16).build();
|
||||
|
||||
private static final DrawableResource SLOT_DEFAULT = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
68,
|
||||
26,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_MISSING = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
92,
|
||||
26,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_CRAFTING = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
116,
|
||||
26,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_DISABLED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
140,
|
||||
26,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource SLOT_HIGHLIGHTED = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
164,
|
||||
26,
|
||||
18,
|
||||
18).build();
|
||||
|
||||
private static final DrawableResource ICON_CRAFTING = new DrawableBuilder(
|
||||
"nei:textures/gui/craftingtree.png",
|
||||
67,
|
||||
48,
|
||||
16,
|
||||
16).build();
|
||||
|
||||
private static final int Z_OFFSET = 100;
|
||||
|
||||
public StatsPanel() {
|
||||
final ScrollBar scrollbar = new ScrollBar().setTrackWidth(4)
|
||||
.setThumbTexture(new DrawableBuilder("nei:textures/gui/craftingtree.png", 26, 100, 24, 4).build(), 0, 0)
|
||||
.setTrackTexture(new DrawableBuilder("nei:textures/gui/craftingtree.png", 26, 104, 24, 4).build(), 0, 0)
|
||||
.setTrackPadding(1, -5, 1, 0).setOverflowType(ScrollBar.OverflowType.AUTO);
|
||||
|
||||
setHorizontalScroll(scrollbar);
|
||||
setPaddingBlock(4, 4);
|
||||
setPaddingInline(1, 1);
|
||||
}
|
||||
|
||||
public void rebuild(RecipeChainMath math, CraftingTreeGraph graph, boolean useInventorySnapshot,
|
||||
Set<CraftingTreeState.StatsSection> sections, int widgetX, int widgetY, int widgetW, int widgetH) {
|
||||
|
||||
clear();
|
||||
|
||||
final List<String> labels = new ArrayList<>();
|
||||
final List<List<Widget>> sectionGroups = new ArrayList<>();
|
||||
final List<Widget> inputsAndAvailable = new ArrayList<>();
|
||||
|
||||
if (sections.contains(CraftingTreeState.StatsSection.INGREDIENTS_NEEDED)) {
|
||||
inputsAndAvailable.addAll(collectInputs(math, useInventorySnapshot));
|
||||
}
|
||||
|
||||
if (sections.contains(CraftingTreeState.StatsSection.INGREDIENTS_AVAILABLE)) {
|
||||
inputsAndAvailable.addAll(collectAvailable(math));
|
||||
}
|
||||
|
||||
if (!inputsAndAvailable.isEmpty()) {
|
||||
labels.add(NEIClientUtils.translate("bookmark.tree.stats.ingredients"));
|
||||
sectionGroups.add(inputsAndAvailable);
|
||||
}
|
||||
|
||||
final boolean showCraftingNeeded = sections.contains(CraftingTreeState.StatsSection.CRAFTING_NEEDED);
|
||||
final boolean showCraftingAvailable = sections.contains(CraftingTreeState.StatsSection.CRAFTING_AVAILABLE);
|
||||
|
||||
if (showCraftingNeeded || showCraftingAvailable) {
|
||||
final List<Widget> craftingNeededSlot = new ArrayList<>();
|
||||
final List<Widget> craftingAvailableSlot = new ArrayList<>();
|
||||
final Map<BookmarkItem, Boolean> recipeMissing = new LinkedHashMap<>();
|
||||
final Map<BookmarkItem, Integer> recipeLevel = new HashMap<>();
|
||||
|
||||
for (Map.Entry<ItemTreeSlot, List<ItemTreeSlot>> entry : graph.childrenOf.entrySet()) {
|
||||
final ItemTreeSlot node = entry.getKey();
|
||||
final boolean needsCrafting = graph.childrenNeedCraftingCache.contains(node);
|
||||
|
||||
recipeMissing.merge(node.prefItem, needsCrafting, (a, b) -> a || b);
|
||||
recipeLevel.merge(node.prefItem, treeLevel(node, graph.parentOf), Math::max);
|
||||
}
|
||||
|
||||
final List<Map.Entry<BookmarkItem, Boolean>> sortedRecipeMissing = new ArrayList<>(
|
||||
recipeMissing.entrySet());
|
||||
|
||||
sortedRecipeMissing.sort(
|
||||
Comparator.<Map.Entry<BookmarkItem, Boolean>>comparingInt(entry -> recipeLevel.get(entry.getKey()))
|
||||
.thenComparing(entry -> entry.getKey().itemStack, ItemSorter.instance));
|
||||
|
||||
for (Map.Entry<BookmarkItem, Boolean> entry : sortedRecipeMissing) {
|
||||
final BookmarkItem resultItem = entry.getKey();
|
||||
final long amount = resultItem.getAmount();
|
||||
|
||||
if (amount > 0) {
|
||||
final boolean missingIngredients = entry.getValue();
|
||||
|
||||
if (missingIngredients ? !showCraftingNeeded : !showCraftingAvailable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<Widget> slot = missingIngredients ? craftingNeededSlot : craftingAvailableSlot;
|
||||
|
||||
slot.add(
|
||||
new ItemStatsSlot(
|
||||
resultItem.itemStack,
|
||||
resultItem.getStackSize(amount),
|
||||
missingIngredients ? SLOT_DEFAULT : SLOT_CRAFTING,
|
||||
resultItem.recipeId,
|
||||
resultItem.getMultiplier()));
|
||||
}
|
||||
}
|
||||
|
||||
final List<Widget> craftingSlots = new ArrayList<>(craftingNeededSlot);
|
||||
craftingSlots.addAll(craftingAvailableSlot);
|
||||
|
||||
if (!craftingSlots.isEmpty()) {
|
||||
labels.add(NEIClientUtils.translate("bookmark.tree.stats.crafting"));
|
||||
sectionGroups.add(craftingSlots);
|
||||
}
|
||||
}
|
||||
|
||||
final List<Widget> outputsAndRemainders = new ArrayList<>();
|
||||
|
||||
if (sections.contains(CraftingTreeState.StatsSection.RESULTS)) {
|
||||
outputsAndRemainders.addAll(collectOutputs(math));
|
||||
}
|
||||
|
||||
if (sections.contains(CraftingTreeState.StatsSection.REMAINDERS)) {
|
||||
outputsAndRemainders.addAll(collectRemainder(math));
|
||||
}
|
||||
|
||||
if (!outputsAndRemainders.isEmpty()) {
|
||||
labels.add(NEIClientUtils.translate("bookmark.tree.stats.results"));
|
||||
sectionGroups.add(outputsAndRemainders);
|
||||
}
|
||||
|
||||
if (sections.contains(CraftingTreeState.StatsSection.HANDLERS)) {
|
||||
final List<Widget> handlers = collectHandlers(math);
|
||||
|
||||
if (!handlers.isEmpty()) {
|
||||
labels.add(NEIClientUtils.translate("bookmark.tree.stats.handlers"));
|
||||
sectionGroups.add(handlers);
|
||||
}
|
||||
}
|
||||
|
||||
layoutSections(labels, sectionGroups, widgetX, widgetY, widgetW, widgetH);
|
||||
}
|
||||
|
||||
private void layoutSections(List<String> labels, List<List<Widget>> sections, int widgetX, int widgetY, int widgetW,
|
||||
int widgetH) {
|
||||
final int maxRowWidth = Math.max(SLOT_SIZE, widgetW);
|
||||
final int sectionCount = sections.size();
|
||||
final int[] sizes = new int[sectionCount];
|
||||
final int[] labelWidths = new int[sectionCount];
|
||||
final int[] columns = new int[sectionCount];
|
||||
final int maxRows = widgetH / ((GuiDraw.fontRenderer.FONT_HEIGHT + 4) + SLOT_SIZE * 2 + SLOT_BLOCK_GAP) > 3 ? 2
|
||||
: 1;
|
||||
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
sizes[i] = sections.get(i).size();
|
||||
labelWidths[i] = GuiDraw.fontRenderer.getStringWidth(labels.get(i) + ":");
|
||||
columns[i] = sizes[i] == 0 ? 0 : (sizes[i] + maxRows - 1) / maxRows;
|
||||
}
|
||||
|
||||
int compactWidth = COLUMN_GAP * Math.max(0, sectionCount - 1);
|
||||
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
compactWidth += sectionWidth(columns[i], labelWidths[i]);
|
||||
}
|
||||
|
||||
if (compactWidth <= maxRowWidth) {
|
||||
int budget = (maxRowWidth - compactWidth) / (SLOT_SIZE + SLOT_INLINE_GAP);
|
||||
|
||||
while (budget > 0) {
|
||||
boolean grew = false;
|
||||
|
||||
for (int i = 0; i < sectionCount && budget > 0; i++) {
|
||||
if (columns[i] < sizes[i]) {
|
||||
columns[i]++;
|
||||
--budget;
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!grew) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<Widget> widgets = new ArrayList<>();
|
||||
int cursorX = 0;
|
||||
int maxHeight = 0;
|
||||
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
final int height = placeSection(widgets, labels.get(i), sections.get(i), columns[i], cursorX, 0);
|
||||
maxHeight = Math.max(maxHeight, height);
|
||||
cursorX += sectionWidth(columns[i], labelWidths[i]) + COLUMN_GAP;
|
||||
}
|
||||
|
||||
final int contentW = Math.max(0, cursorX - COLUMN_GAP);
|
||||
final int contentH = maxHeight;
|
||||
|
||||
final int maxVisibleH = maxRows * SLOT_SIZE + Math.max(0, maxRows - 1) * SLOT_BLOCK_GAP
|
||||
+ GuiDraw.fontRenderer.FONT_HEIGHT
|
||||
+ 4;
|
||||
|
||||
final int visibleHeight = Math.min(contentH, maxVisibleH);
|
||||
|
||||
this.w = widgetW;
|
||||
this.x = widgetX;
|
||||
this.h = visibleHeight + this.paddingBlockStart + this.paddingBlockEnd;
|
||||
this.y = widgetY + widgetH - this.h;
|
||||
|
||||
final int xShift = contentW < this.w ? (this.w - contentW) / 2 : 0;
|
||||
|
||||
for (Widget widget : widgets) {
|
||||
widget.x += xShift;
|
||||
}
|
||||
|
||||
setWidgets(widgets);
|
||||
update();
|
||||
}
|
||||
|
||||
private static int sectionWidth(int columns, int labelWidth) {
|
||||
return Math.max(columns * SLOT_SIZE + Math.max(0, columns - 1) * SLOT_INLINE_GAP, labelWidth);
|
||||
}
|
||||
|
||||
private static int placeSection(List<Widget> widgets, String label, List<Widget> group, int columns, int x, int y) {
|
||||
final SectionLabel sectionLabel = new SectionLabel(label + ":");
|
||||
sectionLabel.x = x;
|
||||
sectionLabel.y = y;
|
||||
widgets.add(sectionLabel);
|
||||
|
||||
final int gridY = y + GuiDraw.fontRenderer.FONT_HEIGHT + 4;
|
||||
final int rowWidth = columns * SLOT_SIZE + Math.max(0, columns - 1) * SLOT_INLINE_GAP;
|
||||
int cursorX = 0;
|
||||
int row = 0;
|
||||
boolean any = false;
|
||||
|
||||
for (Widget entry : group) {
|
||||
|
||||
if (cursorX > 0) {
|
||||
if (cursorX + SLOT_INLINE_GAP + SLOT_SIZE > rowWidth) {
|
||||
cursorX = 0;
|
||||
row++;
|
||||
} else {
|
||||
cursorX += SLOT_INLINE_GAP;
|
||||
}
|
||||
} else if (cursorX + SLOT_SIZE > rowWidth) {
|
||||
cursorX = 0;
|
||||
row++;
|
||||
}
|
||||
|
||||
entry.x = x + cursorX;
|
||||
entry.y = gridY + row * (SLOT_SIZE + SLOT_BLOCK_GAP);
|
||||
widgets.add(entry);
|
||||
|
||||
cursorX += SLOT_SIZE;
|
||||
any = true;
|
||||
}
|
||||
|
||||
final int rows = any ? row + 1 : 0;
|
||||
return (gridY - y) + rows * SLOT_SIZE + Math.max(0, rows - 1) * SLOT_BLOCK_GAP;
|
||||
}
|
||||
|
||||
public ItemStack getStackMouseOver(int mouseX, int mouseY) {
|
||||
final Widget widget = getWidgetUnderMouse(mouseX, mouseY);
|
||||
return widget instanceof ItemStatsSlot slot ? slot.stack : null;
|
||||
}
|
||||
|
||||
public void setHighlight(SearchResult searchResult) {
|
||||
for (Widget widget : getWidgets()) {
|
||||
if (widget instanceof HandlerStatsSlot slot) {
|
||||
slot.highlight = searchResult != null && searchResult.matchesGUID(slot.handlerName);
|
||||
} else if (widget instanceof ItemStatsSlot slot) {
|
||||
slot.highlight = searchResult != null && searchResult.matchesGUID(slot.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object getWidgetGUIDUnderMouse(int mouseX, int mouseY) {
|
||||
final Widget widget = getWidgetUnderMouse(mouseX, mouseY);
|
||||
|
||||
if (widget instanceof HandlerStatsSlot slot) {
|
||||
return slot.handlerName;
|
||||
} else if (widget instanceof ItemStatsSlot slot) {
|
||||
return slot.stack;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getHandlerMouseOver(int mouseX, int mouseY) {
|
||||
final Widget widget = getWidgetUnderMouse(mouseX, mouseY);
|
||||
|
||||
if (widget instanceof HandlerStatsSlot slot) {
|
||||
return slot.handlerName;
|
||||
} else if (widget instanceof ItemStatsSlot slot && slot.recipeId != null) {
|
||||
return slot.recipeId.getHandlerName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public RecipeId getRecipeIdMouseOver(int mouseX, int mouseY) {
|
||||
final Widget widget = getWidgetUnderMouse(mouseX, mouseY);
|
||||
return widget instanceof ItemStatsSlot slot ? slot.recipeId : null;
|
||||
}
|
||||
|
||||
public boolean isDraggingScrollbar() {
|
||||
return canScrollHorizontal() && getHorizontalScroll().isMouseDragged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMouseWheel(int direction, int mouseX, int mouseY) {
|
||||
|
||||
if (canScrollHorizontal() && boundsOutside().contains(mouseX, mouseY)) {
|
||||
getHorizontalScroll().setScrollOffset(this, getHorizontalScrollOffset() - direction * MOUSE_SCROLL_SPEED);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onMouseWheel(direction, mouseX, mouseY);
|
||||
}
|
||||
|
||||
public void draw(int mouseX, int mouseY) {
|
||||
GL11.glTranslatef(0, 0, Z_OFFSET);
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
|
||||
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
RenderHelper.enableGUIStandardItemLighting();
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
|
||||
BG_TEXTURE.draw(this.x, this.y, this.w, this.h, 0, 0, 2, 0);
|
||||
|
||||
super.draw(mouseX, mouseY);
|
||||
|
||||
GL11.glPopAttrib();
|
||||
GL11.glTranslatef(0, 0, -Z_OFFSET);
|
||||
}
|
||||
|
||||
public void drawFull() {
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
|
||||
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
RenderHelper.enableGUIStandardItemLighting();
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
|
||||
drawContent(-1, -1);
|
||||
GL11.glPopAttrib();
|
||||
}
|
||||
|
||||
private List<Widget> collectAvailable(RecipeChainMath math) {
|
||||
final ItemStackAmount items = new ItemStackAmount();
|
||||
final List<Widget> widgets = new ArrayList<>();
|
||||
|
||||
for (BookmarkItem item : math.initialItems) {
|
||||
final long amount = math.requiredAmount.getOrDefault(item, 0L);
|
||||
|
||||
if (amount > 0) {
|
||||
items.add(item.itemStack, item.getStackSize(amount));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (Map.Entry<NBTTagCompound, Long> entry : items.entrySet()) {
|
||||
if (entry.getValue() > 0) {
|
||||
widgets.add(
|
||||
new ItemStatsSlot(StackInfo.loadFromNBT(entry.getKey(), 0), entry.getValue(), SLOT_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
private List<Widget> collectInputs(RecipeChainMath math, boolean useInventorySnapshot) {
|
||||
final ItemStackAmount items = new ItemStackAmount();
|
||||
final List<Widget> widgets = new ArrayList<>();
|
||||
|
||||
for (BookmarkItem item : math.recipeIngredients) {
|
||||
final long amount = math.requiredAmount.containsKey(math.preferredItems.get(item)) ? 0
|
||||
: math.requiredAmount.getOrDefault(item, item.getAmount());
|
||||
|
||||
if (amount > 0) {
|
||||
items.add(item.itemStack, item.getStackSize(amount));
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<NBTTagCompound, Long> entry : items.entrySet()) {
|
||||
if (entry.getValue() > 0) {
|
||||
widgets.add(
|
||||
new ItemStatsSlot(
|
||||
StackInfo.loadFromNBT(entry.getKey(), 0),
|
||||
entry.getValue(),
|
||||
useInventorySnapshot ? SLOT_MISSING : SLOT_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
private List<Widget> collectOutputs(RecipeChainMath math) {
|
||||
final ItemStackAmount items = new ItemStackAmount();
|
||||
final List<Widget> widgets = new ArrayList<>();
|
||||
|
||||
for (BookmarkItem item : math.recipeResults) {
|
||||
final long amount = item.getAmount() - math.requiredAmount.getOrDefault(item, 0L);
|
||||
|
||||
if (amount > 0 && math.outputRecipes.containsKey(item.recipeId)) {
|
||||
items.add(item.itemStack, item.getStackSize(amount));
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<NBTTagCompound, Long> entry : items.entrySet()) {
|
||||
if (entry.getValue() > 0) {
|
||||
widgets.add(
|
||||
new ItemStatsSlot(StackInfo.loadFromNBT(entry.getKey(), 0), entry.getValue(), SLOT_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
private List<Widget> collectRemainder(RecipeChainMath math) {
|
||||
final ItemStackAmount items = new ItemStackAmount();
|
||||
final List<Widget> widgets = new ArrayList<>();
|
||||
|
||||
for (BookmarkItem item : math.recipeResults) {
|
||||
final long amount = item.getAmount() - math.requiredAmount.getOrDefault(item, 0L);
|
||||
|
||||
if (amount > 0 && !math.outputRecipes.containsKey(item.recipeId)) {
|
||||
items.add(item.itemStack, item.getStackSize(amount));
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemStack stack : math.containerItemsInventory) {
|
||||
if (stack != null) {
|
||||
items.add(stack);
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemStack stack : math.containerItemsCrafting) {
|
||||
if (stack != null) {
|
||||
items.add(stack);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<NBTTagCompound, Long> entry : items.entrySet()) {
|
||||
if (entry.getValue() > 0) {
|
||||
widgets.add(
|
||||
new ItemStatsSlot(StackInfo.loadFromNBT(entry.getKey(), 0), entry.getValue(), SLOT_DISABLED));
|
||||
}
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
private List<Widget> collectHandlers(RecipeChainMath math) {
|
||||
final Map<String, HandlerStats> handlerStats = new HashMap<>();
|
||||
final List<Widget> widgets = new ArrayList<>();
|
||||
|
||||
for (BookmarkItem item : math.recipeResults) {
|
||||
final HandlerStats stats = handlerStats
|
||||
.computeIfAbsent(item.recipeId.getHandlerName(), k -> new HandlerStats());
|
||||
stats.iterations += item.getMultiplier();
|
||||
stats.recipeCount++;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, HandlerStats> entry : handlerStats.entrySet()) {
|
||||
widgets.add(new HandlerStatsSlot(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}
|
||||
|
||||
private static int treeLevel(ItemTreeSlot node, Map<ItemTreeSlot, ItemTreeSlot> parentOf) {
|
||||
int level = 0;
|
||||
|
||||
for (ItemTreeSlot current = parentOf.get(node); current != null; current = parentOf.get(current)) {
|
||||
level++;
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
private static HandlerInfo handlerInfo(String handlerName) {
|
||||
final HandlerInfo info = GuiRecipeTab.getHandlerInfo(handlerName, null);
|
||||
return info != null ? info : GuiRecipeTab.DEFAULT_HANDLER_INFO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import static codechicken.lib.gui.GuiDraw.drawStringC;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import codechicken.lib.gui.GuiDraw;
|
||||
import codechicken.nei.LayoutManager;
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
import codechicken.nei.bookmark.tree.CraftingTreeState.StatsSection;
|
||||
|
||||
class StatsSectionsMenu {
|
||||
|
||||
private static final StatsSection[] SECTIONS = StatsSection.values();
|
||||
|
||||
private static final int ROW_HEIGHT = 16;
|
||||
private static final int LABEL_PADDING = 10;
|
||||
private static final int MIN_WIDTH = 70;
|
||||
private static final int TOP_PADDING = 4;
|
||||
private static final int Z_OFFSET = 500;
|
||||
|
||||
private static final int COLOR_HOVER = 0xFFFFFFA0;
|
||||
private static final int COLOR_ENABLED = 0xFFE0E0E0;
|
||||
private static final int COLOR_DISABLED = 0xFFA0A0A0;
|
||||
|
||||
public boolean visible = false;
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
private int w;
|
||||
private int h;
|
||||
|
||||
public void open(int anchorX, int anchorY) {
|
||||
this.visible = true;
|
||||
|
||||
int maxLabelWidth = 0;
|
||||
|
||||
for (StatsSection section : SECTIONS) {
|
||||
maxLabelWidth = Math.max(maxLabelWidth, GuiDraw.fontRenderer.getStringWidth(label(section)));
|
||||
}
|
||||
|
||||
this.w = Math.max(MIN_WIDTH, maxLabelWidth + LABEL_PADDING * 2);
|
||||
this.h = TOP_PADDING + SECTIONS.length * ROW_HEIGHT;
|
||||
this.x = anchorX;
|
||||
this.y = anchorY;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
public boolean isInWidget(int mouseX, int mouseY) {
|
||||
return this.visible && mouseX >= this.x
|
||||
&& mouseX < this.x + this.w
|
||||
&& mouseY >= this.y
|
||||
&& mouseY < this.y + this.h;
|
||||
}
|
||||
|
||||
public boolean handleClick(int mouseX, int mouseY, CraftingTreeState uiState) {
|
||||
|
||||
if (!isInWidget(mouseX, mouseY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int relativeY = mouseY - this.y - TOP_PADDING;
|
||||
|
||||
if (relativeY >= 0) {
|
||||
final int index = relativeY / ROW_HEIGHT;
|
||||
|
||||
if (index < SECTIONS.length) {
|
||||
uiState.toggleStatsSection(SECTIONS[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void draw(int mouseX, int mouseY, CraftingTreeState uiState) {
|
||||
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
GL11.glTranslatef(0, 0, Z_OFFSET);
|
||||
|
||||
for (int i = 0; i < SECTIONS.length; i++) {
|
||||
final StatsSection section = SECTIONS[i];
|
||||
final int rowY = this.y + TOP_PADDING + i * ROW_HEIGHT;
|
||||
final boolean hover = mouseX >= this.x && mouseX < this.x + this.w
|
||||
&& mouseY >= rowY
|
||||
&& mouseY < rowY + ROW_HEIGHT;
|
||||
final boolean checked = uiState.visibleStatsSections.contains(section);
|
||||
final int color = hover ? COLOR_HOVER : checked ? COLOR_ENABLED : COLOR_DISABLED;
|
||||
|
||||
if (checked) {
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
LayoutManager.drawButtonBackground(this.x, rowY, this.w, ROW_HEIGHT, false, 1);
|
||||
} else {
|
||||
GL11.glColor4f(0.65F, 0.65F, 0.65F, 1.0F);
|
||||
LayoutManager.drawButtonBackground(this.x, rowY, this.w, ROW_HEIGHT, false, 0);
|
||||
}
|
||||
|
||||
drawStringC(label(section), this.x, rowY, this.w, ROW_HEIGHT, color);
|
||||
}
|
||||
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
GL11.glTranslatef(0, 0, -Z_OFFSET);
|
||||
}
|
||||
|
||||
private static String label(StatsSection section) {
|
||||
return NEIClientUtils.translate(switch (section) {
|
||||
case INGREDIENTS_NEEDED -> "bookmark.tree.stats.ingredients_needed";
|
||||
case INGREDIENTS_AVAILABLE -> "bookmark.tree.stats.ingredients_available";
|
||||
case CRAFTING_NEEDED -> "bookmark.tree.stats.crafting_needed";
|
||||
case CRAFTING_AVAILABLE -> "bookmark.tree.stats.crafting_available";
|
||||
case RESULTS -> "bookmark.tree.stats.results";
|
||||
case REMAINDERS -> "bookmark.tree.stats.remainders";
|
||||
case HANDLERS -> "bookmark.tree.stats.handlers";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package codechicken.nei.bookmark.tree;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.recipe.GuiRecipeButton;
|
||||
import codechicken.nei.recipe.Recipe;
|
||||
import codechicken.nei.recipe.RecipeHandlerRef;
|
||||
|
||||
public class TreeOverlayButton extends GuiRecipeButton {
|
||||
|
||||
protected static final DrawableResource ICON_FILL = new DrawableBuilder(
|
||||
"nei:textures/nei_sprites.png",
|
||||
28,
|
||||
76,
|
||||
9,
|
||||
10).build();
|
||||
protected static final int BUTTON_ID_SHIFT = 4;
|
||||
|
||||
public final GuiCraftingTree treeGui;
|
||||
|
||||
public TreeOverlayButton(GuiCraftingTree treeGui, RecipeHandlerRef handlerRef, int x, int y) {
|
||||
super(handlerRef, x, y, handlerRef.recipeIndex + BUTTON_ID_SHIFT, "+");
|
||||
this.treeGui = treeGui;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> handleTooltip(List<String> currenttip) {
|
||||
currenttip.add(NEIClientUtils.translate("bookmark.tree.overlay"));
|
||||
return currenttip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> handleHotkeys(int mousex, int mousey, Map<String, String> hotkeys) {
|
||||
return hotkeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(int mouseX, int mouseY) {
|
||||
if (this.treeGui != null) {
|
||||
this.treeGui.addRecipe(Recipe.of(this.handlerRef));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawContent(Minecraft minecraft, int y, int x, boolean mouseOver) {
|
||||
final int iconX = this.xPosition + (this.width - ICON_FILL.width - 1) / 2;
|
||||
final int iconY = this.yPosition + (this.height - ICON_FILL.height) / 2;
|
||||
|
||||
GL11.glColor4f(1, 1, 1, this.enabled ? 1 : 0.5f);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
ICON_FILL.draw(iconX, iconY);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawItemOverlay() {}
|
||||
|
||||
public Recipe getRecipe() {
|
||||
return Recipe.of(this.handlerRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lastKeyTyped(char keyChar, int keyID) {}
|
||||
|
||||
}
|
||||
@@ -40,6 +40,10 @@ public class DrawableResource extends Image {
|
||||
return height + paddingTop + paddingBottom;
|
||||
}
|
||||
|
||||
public void bindTexture() {
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
|
||||
}
|
||||
|
||||
public void draw(int xOffset, int yOffset) {
|
||||
draw(xOffset, yOffset, 0, 0, 0, 0);
|
||||
}
|
||||
@@ -50,7 +54,7 @@ public class DrawableResource extends Image {
|
||||
final int height = this.height - maskBottom - maskTop;
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
|
||||
bindTexture();
|
||||
drawModalRectWithCustomSizedTexture(xOffset + maskLeft, yOffset + maskTop, maskLeft, maskTop, width, height);
|
||||
if (!is2DTexture) GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
@@ -72,7 +76,7 @@ public class DrawableResource extends Image {
|
||||
final int remainderHeight = tileHeight - (tileHeightCount * middleHeight);
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
|
||||
bindTexture();
|
||||
|
||||
// middle parts
|
||||
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
|
||||
|
||||
@@ -620,7 +620,7 @@ public class GuiContainerManager {
|
||||
|
||||
if (!secondDisplayName.isEmpty()) {
|
||||
tooltip.add(1, EnumChatFormatting.DARK_GRAY + secondDisplayName + GuiDraw.TOOLTIP_LINESPACE);
|
||||
} else {
|
||||
} else if (!tooltip.get(0).startsWith(GuiDraw.TOOLTIP_HANDLER)) {
|
||||
tooltip.set(0, tooltip.get(0) + GuiDraw.TOOLTIP_LINESPACE);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import codechicken.nei.KeyManager;
|
||||
import codechicken.nei.LayoutManager;
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.NEIClientUtils;
|
||||
import codechicken.nei.bookmark.tree.GuiCraftingTree;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||
@@ -127,6 +128,12 @@ public class GuiFavoriteButton extends GuiRecipeButton {
|
||||
saveRecipeInBookmark();
|
||||
}
|
||||
|
||||
if (KeyManager.isKeyDown("bookmark.open_crafting_tree") && NEIClientUtils.shiftKey()
|
||||
&& saveRecipeInBookmark()) {
|
||||
final int groupId = ItemPanels.bookmarkPanel.getGrid().getNextGroupId() - 1;
|
||||
GuiCraftingTree.openCraftingTreeGui(ItemPanels.bookmarkPanel.getGrid(), groupId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,6 +142,10 @@ public class GuiFavoriteButton extends GuiRecipeButton {
|
||||
KeyManager.getKeyName("bookmark.add", NEIClientUtils.SHIFT_HASH),
|
||||
translate("recipe.favorite.bookmark_recipe"));
|
||||
|
||||
hotkeys.put(
|
||||
KeyManager.getKeyName("bookmark.open_crafting_tree", NEIClientUtils.SHIFT_HASH),
|
||||
NEIClientUtils.translate("bookmark.group.open_crafting_tree"));
|
||||
|
||||
if (this.recipe.getResults().size() > 1) {
|
||||
hotkeys.put(
|
||||
NEIMouseUtils.getKeyName(NEIMouseUtils.MOUSE_BTN_NONE + NEIMouseUtils.MOUSE_SCROLL),
|
||||
@@ -189,8 +200,8 @@ public class GuiFavoriteButton extends GuiRecipeButton {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void saveRecipeInBookmark() {
|
||||
ItemPanels.bookmarkPanel.addGroup(getRecipesTree(getRecipe()), BookmarkViewMode.TODO_LIST, true);
|
||||
public boolean saveRecipeInBookmark() {
|
||||
return ItemPanels.bookmarkPanel.addGroup(getRecipesTree(getRecipe()), BookmarkViewMode.TODO_LIST, true);
|
||||
}
|
||||
|
||||
private List<Recipe> getRecipesTree(Recipe mainRecipe) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import codechicken.nei.drawable.DrawableResource;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||
import codechicken.nei.scroll.GuiHelper;
|
||||
import codechicken.nei.util.ReadableNumberConverter;
|
||||
|
||||
public class RecipeTooltipLineHandler implements ITooltipLineHandler {
|
||||
|
||||
@@ -32,9 +33,15 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
|
||||
protected NEIRecipeWidget widget = null;
|
||||
protected RecipeId recipeId = null;
|
||||
protected boolean createdGui = false;
|
||||
protected long multiplier = 0;
|
||||
|
||||
public RecipeTooltipLineHandler(RecipeId recipeId, long multiplier) {
|
||||
this.recipeId = recipeId;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
public RecipeTooltipLineHandler(RecipeId recipeId) {
|
||||
this.recipeId = recipeId;
|
||||
this(recipeId, 0);
|
||||
}
|
||||
|
||||
public ItemStack getItemStack() {
|
||||
@@ -45,6 +52,10 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
|
||||
return this.recipeId;
|
||||
}
|
||||
|
||||
public long getMultiplier() {
|
||||
return this.multiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getSize() {
|
||||
|
||||
@@ -104,6 +115,14 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
|
||||
GuiDraw.drawRect(BG_PADDING, BG_PADDING, size.width - BG_PADDING * 2, 12, 0x30000000);
|
||||
GuiDraw.drawStringC(this.recipeName, this.widget.w / 2, BG_PADDING + 2, 0xffffff);
|
||||
|
||||
if (this.multiplier > 0) {
|
||||
GuiDraw.drawString(
|
||||
"x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(this.multiplier),
|
||||
BG_PADDING + 2,
|
||||
BG_PADDING + 2,
|
||||
0xffffff);
|
||||
}
|
||||
|
||||
GuiHelper.useScissor(
|
||||
this.widget.x,
|
||||
this.widget.y,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package codechicken.nei.recipe.chain;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -25,7 +26,7 @@ import codechicken.nei.recipe.StackInfo;
|
||||
|
||||
public class RecipeChainMath {
|
||||
|
||||
private static class ContainerItemResult {
|
||||
protected static class ContainerItemResult {
|
||||
|
||||
public ItemStack stack;
|
||||
public ItemStack containerItem;
|
||||
@@ -48,13 +49,19 @@ public class RecipeChainMath {
|
||||
public final List<BookmarkItem> recipeIngredients = new ArrayList<>();
|
||||
public final List<BookmarkItem> recipeResults = new ArrayList<>();
|
||||
|
||||
protected final Map<RecipeId, List<BookmarkItem>> ingredientsByRecipe = new HashMap<>();
|
||||
protected final Map<RecipeId, List<BookmarkItem>> resultsByRecipe = new HashMap<>();
|
||||
|
||||
public final Map<BookmarkItem, BookmarkItem> preferredItems = new HashMap<>();
|
||||
|
||||
public final Map<BookmarkItem, Long> requiredAmount = new HashMap<>();
|
||||
public final List<ItemStack> containerItems = new ArrayList<>();
|
||||
|
||||
public final List<ItemStack> containerItemsCrafting = new ArrayList<>();
|
||||
public final List<ItemStack> containerItemsInventory = new ArrayList<>();
|
||||
|
||||
private final List<ItemStack> containerItemsBlacklist = new ArrayList<>();
|
||||
|
||||
private RecipeChainMath(List<BookmarkItem> recipeItems, Set<RecipeId> collapsedRecipes) {
|
||||
protected RecipeChainMath(List<BookmarkItem> recipeItems, Set<RecipeId> collapsedRecipes) {
|
||||
final Map<RecipeId, Long> multipliers = new HashMap<>();
|
||||
|
||||
for (BookmarkItem item : recipeItems) {
|
||||
@@ -70,6 +77,8 @@ public class RecipeChainMath {
|
||||
}
|
||||
}
|
||||
|
||||
rebuildRecipeIndex();
|
||||
|
||||
for (Map.Entry<RecipeId, Long> entry : multipliers.entrySet()) {
|
||||
if (entry.getValue() > 1 || collapsedRecipes.contains(entry.getKey())) {
|
||||
collectPreferredItems(entry.getKey(), this.preferredItems, new HashSet<>());
|
||||
@@ -125,14 +134,26 @@ public class RecipeChainMath {
|
||||
|
||||
}
|
||||
|
||||
private void rebuildRecipeIndex() {
|
||||
this.ingredientsByRecipe.clear();
|
||||
this.resultsByRecipe.clear();
|
||||
|
||||
for (BookmarkItem item : this.recipeIngredients) {
|
||||
this.ingredientsByRecipe.computeIfAbsent(item.recipeId, k -> new ArrayList<>()).add(item);
|
||||
}
|
||||
|
||||
for (BookmarkItem item : this.recipeResults) {
|
||||
this.resultsByRecipe.computeIfAbsent(item.recipeId, k -> new ArrayList<>()).add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectPreferredItems(RecipeId recipeId, Map<BookmarkItem, BookmarkItem> preferredItems,
|
||||
Set<RecipeId> visited) {
|
||||
|
||||
visited.add(recipeId);
|
||||
|
||||
for (BookmarkItem ingrItem : this.recipeIngredients) {
|
||||
if (!ingrItem.emptyFactor() && recipeId.equals(ingrItem.recipeId)
|
||||
&& !preferredItems.containsKey(ingrItem)) {
|
||||
for (BookmarkItem ingrItem : this.ingredientsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
if (!ingrItem.emptyFactor() && !preferredItems.containsKey(ingrItem)) {
|
||||
BookmarkItem activeItem = null;
|
||||
BookmarkItem prefItem = null;
|
||||
|
||||
@@ -178,8 +199,8 @@ public class RecipeChainMath {
|
||||
private void removeLoop(RecipeId recipeId, Map<BookmarkItem, BookmarkItem> preferredItems, Set<RecipeId> visited) {
|
||||
visited.add(recipeId);
|
||||
|
||||
for (BookmarkItem ingrItem : this.recipeIngredients) {
|
||||
if (!ingrItem.emptyFactor() && recipeId.equals(ingrItem.recipeId) && preferredItems.containsKey(ingrItem)) {
|
||||
for (BookmarkItem ingrItem : this.ingredientsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
if (!ingrItem.emptyFactor() && preferredItems.containsKey(ingrItem)) {
|
||||
BookmarkItem prefItem = preferredItems.get(ingrItem);
|
||||
|
||||
if (visited.contains(prefItem.recipeId)) {
|
||||
@@ -194,6 +215,87 @@ public class RecipeChainMath {
|
||||
visited.remove(recipeId);
|
||||
}
|
||||
|
||||
private List<RecipeId> getOutputRecipeProcessingOrder() {
|
||||
|
||||
if (this.outputRecipes.size() <= 1) {
|
||||
return new ArrayList<>(this.outputRecipes.keySet());
|
||||
}
|
||||
|
||||
final Map<RecipeId, Set<RecipeId>> dependents = new HashMap<>();
|
||||
final Map<RecipeId, Integer> inDegree = new HashMap<>();
|
||||
|
||||
for (RecipeId recipeId : this.outputRecipes.keySet()) {
|
||||
inDegree.put(recipeId, 0);
|
||||
}
|
||||
|
||||
for (RecipeId recipeId : this.outputRecipes.keySet()) {
|
||||
final Set<RecipeId> dependencies = new HashSet<>();
|
||||
collectBoxDependencies(recipeId, recipeId, dependencies, new HashSet<>());
|
||||
dependents.put(recipeId, dependencies);
|
||||
|
||||
for (RecipeId dependency : dependencies) {
|
||||
inDegree.merge(dependency, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
|
||||
final List<RecipeId> order = new ArrayList<>();
|
||||
final ArrayDeque<RecipeId> queue = new ArrayDeque<>();
|
||||
|
||||
for (RecipeId recipeId : this.outputRecipes.keySet()) {
|
||||
if (inDegree.get(recipeId) == 0) {
|
||||
queue.add(recipeId);
|
||||
}
|
||||
}
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
final RecipeId recipeId = queue.poll();
|
||||
order.add(recipeId);
|
||||
|
||||
for (RecipeId dependency : dependents.getOrDefault(recipeId, Collections.emptySet())) {
|
||||
if (inDegree.merge(dependency, -1, Integer::sum) == 0) {
|
||||
queue.add(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (order.size() != this.outputRecipes.size()) {
|
||||
for (RecipeId recipeId : this.outputRecipes.keySet()) {
|
||||
if (!order.contains(recipeId)) {
|
||||
order.add(recipeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private void collectBoxDependencies(RecipeId boxId, RecipeId currentRecipeId, Set<RecipeId> dependencies,
|
||||
Set<RecipeId> visited) {
|
||||
|
||||
if (!visited.add(currentRecipeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (BookmarkItem ingrItem : this.ingredientsByRecipe.getOrDefault(currentRecipeId, Collections.emptyList())) {
|
||||
if (ingrItem.emptyFactor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BookmarkItem prefItem = this.preferredItems.get(ingrItem);
|
||||
if (prefItem == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.outputRecipes.containsKey(prefItem.recipeId) && !prefItem.recipeId.equals(boxId)) {
|
||||
dependencies.add(prefItem.recipeId);
|
||||
} else {
|
||||
collectBoxDependencies(boxId, prefItem.recipeId, dependencies, visited);
|
||||
}
|
||||
}
|
||||
|
||||
visited.remove(currentRecipeId);
|
||||
}
|
||||
|
||||
public RecipeId createMasterRoot() {
|
||||
final List<BookmarkItem> rootIngredients = new ArrayList<>();
|
||||
|
||||
@@ -216,6 +318,7 @@ public class RecipeChainMath {
|
||||
BookmarkItem.builder(-1, ROOT_ITEM).factor(1).recipeId(ROOT_RECIPE_ID).type(BookmarkItemType.RESULT)
|
||||
.build());
|
||||
this.recipeIngredients.addAll(rootIngredients);
|
||||
rebuildRecipeIndex();
|
||||
|
||||
return ROOT_RECIPE_ID;
|
||||
}
|
||||
@@ -276,6 +379,12 @@ public class RecipeChainMath {
|
||||
return missedItems;
|
||||
}
|
||||
|
||||
protected boolean isForeignBoxBoundary(BookmarkItem ingrItem) {
|
||||
final BookmarkItem prefItem = this.preferredItems.get(ingrItem);
|
||||
return prefItem != null && ingrItem.type != BookmarkItemType.RESULT
|
||||
&& this.outputRecipes.containsKey(prefItem.recipeId);
|
||||
}
|
||||
|
||||
private void resetCalculation() {
|
||||
|
||||
for (BookmarkItem item : this.recipeIngredients) {
|
||||
@@ -288,7 +397,8 @@ public class RecipeChainMath {
|
||||
|
||||
this.preferredItems.clear();
|
||||
this.requiredAmount.clear();
|
||||
this.containerItems.clear();
|
||||
this.containerItemsCrafting.clear();
|
||||
this.containerItemsInventory.clear();
|
||||
this.containerItemsBlacklist.clear();
|
||||
|
||||
for (RecipeId recipeId : this.outputRecipes.keySet()) {
|
||||
@@ -313,9 +423,13 @@ public class RecipeChainMath {
|
||||
}
|
||||
}
|
||||
|
||||
for (BookmarkItem prefItem : this.recipeResults) {
|
||||
if (!prefItem.emptyFactor() && this.outputRecipes.containsKey(prefItem.recipeId)) {
|
||||
final long prefMultiplier = this.outputRecipes.get(prefItem.recipeId);
|
||||
for (RecipeId recipeId : getOutputRecipeProcessingOrder()) {
|
||||
final long prefMultiplier = this.outputRecipes.get(recipeId);
|
||||
|
||||
for (BookmarkItem prefItem : this.resultsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
if (prefItem.emptyFactor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prefItem.itemStack.getItem().hasContainerItem(prefItem.itemStack)) {
|
||||
this.containerItemsBlacklist.add(prefItem.itemStack);
|
||||
@@ -341,20 +455,40 @@ public class RecipeChainMath {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void calculateSuitableRecipe(BookmarkItem ingrItem, long ingrMultiplier, List<RecipeId> visited) {
|
||||
protected void calculateSuitableRecipe(BookmarkItem ingrItem, long ingrMultiplier, List<RecipeId> visited) {
|
||||
final BookmarkItem prefItem = this.preferredItems.get(ingrItem);
|
||||
long ingrAmount = ingrItem
|
||||
.getAmount(ingrMultiplier, prefItem != null ? prefItem.itemStack : ingrItem.itemStack);
|
||||
final long requested = ingrAmount;
|
||||
long containerAmount = 0;
|
||||
long inventoryAmount = 0;
|
||||
|
||||
// calculate existing containers
|
||||
if (ingrAmount > 0) {
|
||||
for (ItemStack stack : ingrItem.permutations.values()) {
|
||||
if (hasContainerItem(stack)) {
|
||||
final long stackSize = ingrItem.getStackSize(ingrAmount);
|
||||
final long shiftSize = shiftContainerItems(stack, stackSize);
|
||||
if (stackSize != shiftSize && (ingrAmount = shiftSize * ingrItem.fluidCellAmount) == 0) {
|
||||
break;
|
||||
long stackSize = ingrItem.getStackSize(ingrAmount);
|
||||
long shiftSize = shiftContainerItems(stack, stackSize, this.containerItemsCrafting);
|
||||
|
||||
if (stackSize != shiftSize) {
|
||||
final long newIngrAmount = shiftSize * ingrItem.fluidCellAmount;
|
||||
containerAmount += ingrAmount - newIngrAmount;
|
||||
if ((ingrAmount = newIngrAmount) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stackSize = shiftSize;
|
||||
shiftSize = shiftContainerItems(stack, stackSize, this.containerItemsInventory);
|
||||
|
||||
if (stackSize != shiftSize) {
|
||||
final long newIngrAmount = shiftSize * ingrItem.fluidCellAmount;
|
||||
inventoryAmount += ingrAmount - newIngrAmount;
|
||||
if ((ingrAmount = newIngrAmount) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,20 +496,36 @@ public class RecipeChainMath {
|
||||
// calculate existing initial items
|
||||
if (ingrAmount > 0) {
|
||||
for (BookmarkItem item : this.initialItems) {
|
||||
if (item.containsItems(ingrItem)
|
||||
&& (ingrAmount = addRequiredAmount(item, ingrAmount, item.getAmount())) == 0) {
|
||||
break;
|
||||
if (item.containsItems(ingrItem)) {
|
||||
final long newIngrAmount = addRequiredAmount(
|
||||
item,
|
||||
ingrAmount,
|
||||
item.getAmount(),
|
||||
this.containerItemsInventory);
|
||||
inventoryAmount += ingrAmount - newIngrAmount;
|
||||
if ((ingrAmount = newIngrAmount) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shift amount
|
||||
if (prefItem == null) {
|
||||
addRequiredAmount(ingrItem, ingrAmount, Long.MAX_VALUE);
|
||||
} else if (visited.contains(prefItem.recipeId)) {
|
||||
addRequiredAmount(prefItem, ingrAmount, Long.MAX_VALUE);
|
||||
} else {
|
||||
addRequiredAmount(prefItem, ingrAmount, Long.MAX_VALUE);
|
||||
final BookmarkItem producer = prefItem != null ? prefItem : ingrItem;
|
||||
final long beforeShift = this.requiredAmount.getOrDefault(producer, 0L);
|
||||
|
||||
addRequiredAmount(producer, ingrAmount, Long.MAX_VALUE, this.containerItemsCrafting);
|
||||
|
||||
calculationStepTrigger(
|
||||
ingrItem,
|
||||
prefItem,
|
||||
requested,
|
||||
containerAmount,
|
||||
inventoryAmount,
|
||||
ingrAmount,
|
||||
this.requiredAmount.getOrDefault(producer, 0L) - beforeShift);
|
||||
|
||||
if (prefItem != null && !isForeignBoxBoundary(ingrItem) && !visited.contains(prefItem.recipeId)) {
|
||||
final long multiplier = Math
|
||||
.max(0, prefItem.getMultiplierFromAmount(this.requiredAmount.get(prefItem)) - prefItem.multiplier);
|
||||
|
||||
@@ -389,17 +539,21 @@ public class RecipeChainMath {
|
||||
|
||||
}
|
||||
|
||||
private void prepareIngredients(RecipeId recipeId, long multiplier, List<RecipeId> visited) {
|
||||
protected void calculationStepTrigger(BookmarkItem ingrItem, BookmarkItem prefItem, long requested,
|
||||
long containerAmount, long inventoryAmount, long ingrAmount, long produced) {}
|
||||
|
||||
for (BookmarkItem item : this.recipeIngredients) {
|
||||
if (!item.emptyFactor() && recipeId.equals(item.recipeId)) {
|
||||
protected void prepareIngredients(RecipeId recipeId, long multiplier, List<RecipeId> visited) {
|
||||
|
||||
for (BookmarkItem item : this.ingredientsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
if (!item.emptyFactor()) {
|
||||
calculateSuitableRecipe(item, multiplier, visited);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private long addRequiredAmount(BookmarkItem prefItem, long ingrAmount, long maxAmount) {
|
||||
protected long addRequiredAmount(BookmarkItem prefItem, long ingrAmount, long maxAmount,
|
||||
List<ItemStack> containerItems) {
|
||||
long shiftAmount = this.requiredAmount.getOrDefault(prefItem, 0L);
|
||||
|
||||
if (hasContainerItem(prefItem.itemStack)) {
|
||||
@@ -410,7 +564,7 @@ public class RecipeChainMath {
|
||||
itemStack = itemStack.copy();
|
||||
itemStack.stackSize = 1;
|
||||
|
||||
ingrAmount = shiftContainerItems(itemStack, prefItem.getStackSize(ingrAmount))
|
||||
ingrAmount = shiftContainerItems(itemStack, prefItem.getStackSize(ingrAmount), containerItems)
|
||||
* prefItem.fluidCellAmount;
|
||||
|
||||
if (ingrAmount > 0) {
|
||||
@@ -419,7 +573,8 @@ public class RecipeChainMath {
|
||||
|
||||
if (result.stack == null) {
|
||||
final long stepsPerReplacement = steps - result.leftSteps;
|
||||
multiplier = Math.min(steps / stepsPerReplacement, maxAmount - shiftAmount);
|
||||
multiplier = Math
|
||||
.min(steps / stepsPerReplacement, (maxAmount - shiftAmount) / prefItem.fluidCellAmount);
|
||||
steps -= multiplier * stepsPerReplacement;
|
||||
} else {
|
||||
steps = result.leftSteps;
|
||||
@@ -431,16 +586,16 @@ public class RecipeChainMath {
|
||||
while (stackSize > Integer.MAX_VALUE) {
|
||||
final ItemStack copy = result.containerItem.copy();
|
||||
copy.stackSize = Integer.MAX_VALUE;
|
||||
this.containerItems.add(copy);
|
||||
containerItems.add(copy);
|
||||
stackSize -= copy.stackSize;
|
||||
}
|
||||
|
||||
result.containerItem.stackSize = (int) stackSize;
|
||||
this.containerItems.add(result.containerItem);
|
||||
containerItems.add(result.containerItem);
|
||||
}
|
||||
|
||||
if (result.stack != null) {
|
||||
this.containerItems.add(result.stack);
|
||||
containerItems.add(result.stack);
|
||||
}
|
||||
|
||||
ingrAmount = steps * prefItem.fluidCellAmount;
|
||||
@@ -461,11 +616,11 @@ public class RecipeChainMath {
|
||||
return ingrAmount;
|
||||
}
|
||||
|
||||
private long shiftContainerItems(ItemStack aStack, long steps) {
|
||||
final int initialSize = this.containerItems.size();
|
||||
private long shiftContainerItems(ItemStack aStack, long steps, List<ItemStack> containerItems) {
|
||||
final int initialSize = containerItems.size();
|
||||
|
||||
for (int i = 0; i < initialSize && steps > 0; i++) {
|
||||
ItemStack bStack = this.containerItems.get(i);
|
||||
ItemStack bStack = containerItems.get(i);
|
||||
|
||||
if (bStack != null && NEIClientUtils.areStacksSameTypeCraftingWithNBT(aStack, bStack)) {
|
||||
final ContainerItemResult result = getToolsContainerItems(bStack, steps);
|
||||
@@ -474,20 +629,20 @@ public class RecipeChainMath {
|
||||
steps = result.leftSteps;
|
||||
|
||||
if (result.containerItem != null) {
|
||||
this.containerItems.add(result.containerItem);
|
||||
containerItems.add(result.containerItem);
|
||||
}
|
||||
|
||||
this.containerItems.set(i, bStack);
|
||||
containerItems.set(i, bStack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.containerItems.removeIf(stack -> stack == null);
|
||||
containerItems.removeIf(stack -> stack == null);
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
private ContainerItemResult getToolsContainerItems(ItemStack aStack, long steps) {
|
||||
protected ContainerItemResult getToolsContainerItems(ItemStack aStack, long steps) {
|
||||
final NBTTagCompound tagCompound = aStack.getTagCompound();
|
||||
|
||||
if (tagCompound != null && tagCompound.hasKey("GT.ToolStats")) {
|
||||
@@ -541,7 +696,7 @@ public class RecipeChainMath {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean hasContainerItem(ItemStack aStack) {
|
||||
protected boolean hasContainerItem(ItemStack aStack) {
|
||||
|
||||
if (aStack.getItem().hasContainerItem(aStack)) {
|
||||
|
||||
@@ -558,16 +713,12 @@ public class RecipeChainMath {
|
||||
}
|
||||
|
||||
private void addShift(RecipeId recipeId, long shift) {
|
||||
for (BookmarkItem item : this.recipeIngredients) {
|
||||
if (recipeId.equals(item.recipeId)) {
|
||||
item.multiplier += shift;
|
||||
}
|
||||
for (BookmarkItem item : this.ingredientsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
item.multiplier += shift;
|
||||
}
|
||||
|
||||
for (BookmarkItem item : this.recipeResults) {
|
||||
if (recipeId.equals(item.recipeId)) {
|
||||
item.multiplier += shift;
|
||||
}
|
||||
for (BookmarkItem item : this.resultsByRecipe.getOrDefault(recipeId, Collections.emptyList())) {
|
||||
item.multiplier += shift;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,12 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
|
||||
}
|
||||
|
||||
if (this.lastShiftKey) {
|
||||
for (ItemStack stack : math.containerItems) {
|
||||
for (ItemStack stack : math.containerItemsInventory) {
|
||||
if (stack != null) {
|
||||
remainder.add(stack.copy());
|
||||
}
|
||||
}
|
||||
for (ItemStack stack : math.containerItemsCrafting) {
|
||||
if (stack != null) {
|
||||
remainder.add(stack.copy());
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ nei.bookmark.group.sorting=Move Position
|
||||
nei.bookmark.group.remove_recipe=Remove Group
|
||||
nei.bookmark.group.pull_items=Pull all Items
|
||||
nei.bookmark.group.pull_items_shift=Pull missing Items
|
||||
nei.bookmark.group.open_crafting_tree=Open Crafting Tree
|
||||
nei.bookmark.group.craft_missing=Craft Missing Items
|
||||
nei.bookmark.group.craft_all=Craft Items
|
||||
nei.bookmark.group.chat_link=Send Bookmark Group to Chat
|
||||
@@ -52,6 +53,45 @@ nei.bookmark.crafting_chain.output=Results
|
||||
nei.bookmark.crafting_chain.remainder=Remainders
|
||||
nei.bookmark.crafting_chain.needed=Required Crafts
|
||||
nei.bookmark.crafting_chain.available=Available Items
|
||||
nei.bookmark.tree.title=Crafting Tree
|
||||
nei.bookmark.tree.empty=No crafting chain to display
|
||||
nei.bookmark.tree.overlay=Use for recipe tree
|
||||
nei.bookmark.tree.item.state.resolved=In inventory: %s
|
||||
nei.bookmark.tree.item.state.partial=Still needed: %s
|
||||
nei.bookmark.tree.item.state.craftable=Can be crafted
|
||||
nei.bookmark.tree.item.state.surplus=Extra produced: %s
|
||||
nei.bookmark.tree.item.toggle_recipe=Collapse/Expand Recipe
|
||||
|
||||
nei.bookmark.tree.stats.handlers=Handlers
|
||||
nei.bookmark.tree.stats.ingredients=Ingredients
|
||||
nei.bookmark.tree.stats.ingredients_needed=Ingredients Needed
|
||||
nei.bookmark.tree.stats.ingredients_available=Ingredients Available
|
||||
nei.bookmark.tree.stats.results=Results
|
||||
nei.bookmark.tree.stats.remainders=Remainders
|
||||
nei.bookmark.tree.stats.crafting=Crafting
|
||||
nei.bookmark.tree.stats.crafting_needed=Crafting Needed
|
||||
nei.bookmark.tree.stats.crafting_available=Crafting Available
|
||||
nei.bookmark.tree.stats.handler_iterations=Iterations: %s
|
||||
nei.bookmark.tree.stats.handler_recipes=Unique Recipes: %s
|
||||
|
||||
nei.bookmark.tree.stats.highlight_handler=Highlight Recipe Handler
|
||||
nei.bookmark.tree.stats.next_highlight=Go to Next Highlighted Handler
|
||||
nei.bookmark.tree.stats.prev_highlight=Go to Previous Highlighted Handler
|
||||
nei.bookmark.tree.stats.clear_highlight=Clear Handler Highlight
|
||||
|
||||
nei.bookmark.tree.toolbar.inventory_off=Recompute tree using player inventory (snapshot)
|
||||
nei.bookmark.tree.toolbar.inventory_on=Using inventory snapshot - click to clear
|
||||
nei.bookmark.tree.toolbar.fit_to_view=Fit tree to view
|
||||
nei.bookmark.tree.toolbar.stats_off=Show stats panel
|
||||
nei.bookmark.tree.toolbar.stats_on=Hide stats panel
|
||||
nei.bookmark.tree.toolbar.stats_sections=Choose visible stats sections
|
||||
nei.bookmark.tree.toolbar.link=Send Bookmark Group to Chat
|
||||
nei.bookmark.tree.toolbar.collapse_resolved=Collapse items with nothing left to craft
|
||||
nei.bookmark.tree.toolbar.collapse_all=Collapse all nested items
|
||||
nei.bookmark.tree.toolbar.expand_all=Expand all nested items
|
||||
nei.bookmark.tree.toolbar.screenshot=Save tree as image
|
||||
nei.bookmark.tree.screenshot.fbo_unsupported=Could not save crafting tree screenshot: framebuffers are not supported
|
||||
|
||||
nei.inventory.search.placeholder=Search...
|
||||
nei.itempanel.quantity.default=One stack
|
||||
nei.itempanel.loading=Loading Item List...
|
||||
@@ -207,6 +247,7 @@ nei.options.keys.gui.search=Focus Search Bar
|
||||
nei.options.keys.bookmark.add=Add Bookmark
|
||||
nei.options.keys.bookmark.remove_recipe=Remove Group from Bookmarks (Shift +)
|
||||
nei.options.keys.bookmark.pull_items=Pull all Bookmarked Items
|
||||
nei.options.keys.bookmark.open_crafting_tree=Open Crafting Tree Window
|
||||
nei.options.keys.bookmark.hide=Toggle Bookmark Panel Visibility
|
||||
nei.options.keys.bookmark.chat_link=Send link to chat (Ctrl +)
|
||||
nei.options.keys.gui.overlay=Fill/Overlay Crafting Grid
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Reference in new issue
Block a user