GuiItemList.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. package me.shedaniel.gui;
  2. import com.google.common.collect.ImmutableList;
  3. import com.mojang.blaze3d.platform.GlStateManager;
  4. import me.shedaniel.ClientListener;
  5. import me.shedaniel.Core;
  6. import me.shedaniel.config.REIItemListOrdering;
  7. import me.shedaniel.gui.widget.Button;
  8. import me.shedaniel.gui.widget.Control;
  9. import me.shedaniel.gui.widget.REISlot;
  10. import me.shedaniel.gui.widget.TextBox;
  11. import me.shedaniel.impl.REIRecipeManager;
  12. import me.shedaniel.listenerdefinitions.IMixinContainerGui;
  13. import net.fabricmc.fabric.client.itemgroup.FabricCreativeGuiComponents;
  14. import net.minecraft.client.MinecraftClient;
  15. import net.minecraft.client.gui.ContainerGui;
  16. import net.minecraft.client.network.ClientPlayerEntity;
  17. import net.minecraft.client.render.GuiLighting;
  18. import net.minecraft.client.resource.language.I18n;
  19. import net.minecraft.client.util.Window;
  20. import net.minecraft.item.ItemGroup;
  21. import net.minecraft.item.ItemStack;
  22. import net.minecraft.item.Items;
  23. import net.minecraft.util.DefaultedList;
  24. import net.minecraft.util.Identifier;
  25. import net.minecraft.util.math.MathHelper;
  26. import net.minecraft.util.registry.Registry;
  27. import java.awt.*;
  28. import java.util.*;
  29. import java.util.List;
  30. import java.util.stream.Collectors;
  31. import java.util.stream.Stream;
  32. public class GuiItemList extends Drawable {
  33. public final int FOOTERSIZE;
  34. private ContainerGui overlayedGui;
  35. private static int page = 0;
  36. private ArrayList<REISlot> displaySlots;
  37. protected ArrayList<Control> controls;
  38. private boolean needsResize = false;
  39. Button buttonLeft, buttonRight, buttonCheating, buttonConfig;
  40. private TextBox searchBox;
  41. private ArrayList<ItemStack> view;
  42. private Control lastHovered;
  43. protected boolean visible = true;
  44. private int oldGuiLeft = 0;
  45. private boolean cheatMode = false;
  46. private List<ItemStack> lastPlayerItems = new ArrayList<>();
  47. public GuiItemList(ContainerGui overlayedGui) {
  48. super(calculateRect(overlayedGui));
  49. FOOTERSIZE = Core.runtimeConfig.centreSearchBox ? 18 : 44;
  50. displaySlots = new ArrayList<>();
  51. controls = new ArrayList<>();
  52. this.overlayedGui = overlayedGui;
  53. view = new ArrayList<>();
  54. resize();
  55. }
  56. public boolean canCheat() {
  57. ClientPlayerEntity player = MinecraftClient.getInstance().player;
  58. if (cheatMode) {
  59. if (!player.allowsPermissionLevel(1)) {
  60. cheatClicked(0);
  61. return false;
  62. }
  63. return true;
  64. }
  65. return false;
  66. }
  67. private static Rectangle calculateRect(ContainerGui overlayedGui) {
  68. Window res = REIRenderHelper.getResolution();
  69. int startX = (((IMixinContainerGui) overlayedGui).getGuiLeft() + ((IMixinContainerGui) overlayedGui).getXSize()) + 10;
  70. int width = res.getScaledWidth() - startX;
  71. return new Rectangle(startX, 0, width, res.getScaledHeight());
  72. }
  73. protected void resize() {
  74. Window res = REIRenderHelper.getResolution();
  75. if (overlayedGui != MinecraftClient.getInstance().currentGui) {
  76. if (MinecraftClient.getInstance().currentGui instanceof ContainerGui) {
  77. overlayedGui = (ContainerGui) MinecraftClient.getInstance().currentGui;
  78. } else {
  79. needsResize = true;
  80. return;
  81. }
  82. }
  83. oldGuiLeft = ((IMixinContainerGui) overlayedGui).getGuiLeft();
  84. rect = calculateRect(overlayedGui);
  85. page = 0;
  86. buttonLeft = new Button(rect.x, rect.y + 3, 16, 20, "<");
  87. buttonLeft.onClick = this::btnLeftClicked;
  88. buttonRight = new Button(rect.x + rect.width - 18, rect.y + 3, 16, 20, ">");
  89. buttonRight.onClick = this::btnRightClicked;
  90. controls.clear();
  91. controls.add(buttonLeft);
  92. controls.add(buttonRight);
  93. String savedText = "";
  94. if (searchBox != null) {
  95. savedText = searchBox.getText();
  96. }
  97. searchBox = new TextBox(getSearchBoxArea());
  98. searchBox.setText(savedText);
  99. if (Core.config.enableCraftableOnlyButton) {
  100. Button buttonCraftableOnly = new Button(getCraftableToggleArea(), "");
  101. buttonCraftableOnly.setOnClick(i -> {
  102. Core.runtimeConfig.craftableOnly = !Core.runtimeConfig.craftableOnly;
  103. REIRenderHelper.updateSearch();
  104. return true;
  105. });
  106. controls.add(buttonCraftableOnly);
  107. }
  108. controls.add(searchBox);
  109. buttonCheating = new Button(5, 5, 45, 20, getCheatModeText());
  110. buttonCheating.onClick = this::cheatClicked;
  111. buttonConfig = new Button(5, 28, 45, 20, I18n.translate("text.rei.config"));
  112. buttonConfig.onClick = i -> {
  113. MinecraftClient.getInstance().openGui(null);
  114. MinecraftClient.getInstance().openGui(new ConfigGui(overlayedGui));
  115. return true;
  116. };
  117. controls.add(buttonConfig);
  118. controls.add(buttonCheating);
  119. calculateSlots();
  120. updateView();
  121. fillSlots();
  122. controls.addAll(displaySlots);
  123. }
  124. private Rectangle getSearchBoxArea() {
  125. int widthOffset = Core.config.enableCraftableOnlyButton ? -24 : 0;
  126. int ch = ((IMixinContainerGui) overlayedGui).getContainerHeight(), cw = ((IMixinContainerGui) overlayedGui).getContainerWidth();
  127. if (Core.runtimeConfig.centreSearchBox) {
  128. if (ch + 4 + 18 > MinecraftClient.getInstance().window.getScaledHeight()) //Will be out of bounds
  129. return new Rectangle(overlayedGui.width / 2 - cw / 2, rect.height + 100, cw + widthOffset, 18);
  130. return new Rectangle(overlayedGui.width / 2 - cw / 2, rect.height - 31, cw + widthOffset, 18);
  131. }
  132. return new Rectangle(rect.x, rect.height - 31, rect.width - 4 + widthOffset, 18);
  133. }
  134. private Rectangle getCraftableToggleArea() {
  135. Rectangle searchBoxArea = getSearchBoxArea();
  136. searchBoxArea.setLocation(searchBoxArea.x + searchBoxArea.width + 4, searchBoxArea.y - 1);
  137. searchBoxArea.setSize(20, 20);
  138. return searchBoxArea;
  139. }
  140. private void fillSlots() {
  141. page = MathHelper.clamp(page, 0, MathHelper.ceil(view.size() / displaySlots.size()));
  142. int firstSlot = page * displaySlots.size();
  143. for(int i = 0; i < displaySlots.size(); i++) {
  144. if (firstSlot + i < view.size() && firstSlot + i >= 0) {
  145. displaySlots.get(i).setStack(view.get(firstSlot + i));
  146. } else {
  147. displaySlots.get(i).setStack(ItemStack.EMPTY);
  148. }
  149. }
  150. }
  151. private void calculateSlots() {
  152. int x = rect.x, y = rect.y + 20;
  153. Window res = REIRenderHelper.getResolution();
  154. displaySlots.clear();
  155. int xOffset = 0, yOffset = 0, row = 0, perRow = 0, currentX = 0, currentY = 0;
  156. while (true) {
  157. xOffset += 18;
  158. if (row == 0)
  159. perRow++;
  160. if (x + xOffset + 22 > res.getScaledWidth()) {
  161. xOffset = 0;
  162. yOffset += 18;
  163. row++;
  164. }
  165. if (y + yOffset + 9 + FOOTERSIZE > rect.height) {
  166. xOffset = 0;
  167. yOffset = 0;
  168. break;
  169. }
  170. }
  171. x += (rect.width - perRow * 18) / 2;
  172. y += (rect.height - FOOTERSIZE - 2 - row * 18) / 2;
  173. while (true) {
  174. REISlot slot = new REISlot(x + xOffset, y + yOffset);
  175. slot.setCheatable(true);
  176. if (REIRecipeManager.instance().canAddSlot(MinecraftClient.getInstance().currentGui.getClass(), slot.rect))
  177. displaySlots.add(slot);
  178. xOffset += 18;
  179. currentX++;
  180. if (currentX >= perRow) {
  181. xOffset = 0;
  182. yOffset += 18;
  183. currentX = 0;
  184. currentY++;
  185. }
  186. if (currentY >= row)
  187. break;
  188. }
  189. }
  190. @Override
  191. public void draw() {
  192. if (!visible)
  193. return;
  194. if (MinecraftClient.getInstance().currentGui instanceof ContainerGui)
  195. overlayedGui = (ContainerGui) MinecraftClient.getInstance().currentGui;
  196. if (needsResize == true || oldGuiLeft != ((IMixinContainerGui) overlayedGui).getGuiLeft())
  197. resize();
  198. else if (Core.runtimeConfig.craftableOnly && (!hasSameListContent(new LinkedList<>(lastPlayerItems), getInventoryItemsTypes()) || (getInventoryItemsTypes().size() != lastPlayerItems.size()))) {
  199. this.lastPlayerItems = new LinkedList<>(getInventoryItemsTypes());
  200. updateView();
  201. }
  202. GlStateManager.pushMatrix();
  203. updateButtons();
  204. controls.forEach(Control::draw);
  205. GuiLighting.disable();
  206. String header = String.format("%s/%s", page + 1, MathHelper.ceil(view.size() / displaySlots.size()) + 1);
  207. MinecraftClient.getInstance().fontRenderer.drawWithShadow(header, rect.x + (rect.width / 2) - (MinecraftClient.getInstance().fontRenderer.getStringWidth(header) / 2), rect.y + 10, -1);
  208. GlStateManager.popMatrix();
  209. }
  210. private boolean hasSameListContent(List<ItemStack> list1, List<ItemStack> list2) {
  211. Collections.sort(list1, (itemStack, t1) -> {
  212. return itemStack.getDisplayName().getFormattedText().compareToIgnoreCase(t1.getDisplayName().getFormattedText());
  213. });
  214. Collections.sort(list2, (itemStack, t1) -> {
  215. return itemStack.getDisplayName().getFormattedText().compareToIgnoreCase(t1.getDisplayName().getFormattedText());
  216. });
  217. String lastString = String.join("", list1.stream().map(itemStack -> {
  218. return itemStack.getDisplayName().getFormattedText();
  219. }).collect(Collectors.toList())), currentString = String.join("", list2.stream().map(itemStack -> {
  220. return itemStack.getDisplayName().getFormattedText();
  221. }).collect(Collectors.toList()));
  222. return lastString.equals(currentString);
  223. }
  224. private void updateButtons() {
  225. buttonLeft.setEnabled(MathHelper.ceil(view.size() / displaySlots.size()) > 1);
  226. buttonRight.setEnabled(MathHelper.ceil(view.size() / displaySlots.size()) > 1);
  227. }
  228. public boolean btnRightClicked(int button) {
  229. if (button == 0) {
  230. page++;
  231. if (page > MathHelper.ceil(view.size() / displaySlots.size()))
  232. page = 0;
  233. fillSlots();
  234. return true;
  235. }
  236. return false;
  237. }
  238. public boolean btnLeftClicked(int button) {
  239. if (button == 0) {
  240. page--;
  241. if (page < 0)
  242. page = MathHelper.ceil(view.size() / displaySlots.size());
  243. fillSlots();
  244. return true;
  245. }
  246. return false;
  247. }
  248. public boolean cheatClicked(int button) {
  249. if (button == 0) {
  250. cheatMode = !cheatMode;
  251. buttonCheating.setString(getCheatModeText());
  252. return true;
  253. }
  254. return false;
  255. }
  256. private String getCheatModeText() {
  257. return I18n.translate(String.format("%s%s", "text.rei.", cheatMode ? "cheat" : "nocheat"));
  258. }
  259. protected void updateView() {
  260. String searchText = searchBox.getText();
  261. view.clear();
  262. List<ItemStack> stacks = new ArrayList<>();
  263. if (ClientListener.stackList == null && !Registry.ITEM.isEmpty())
  264. Core.getListeners(ClientListener.class).forEach(ClientListener::onDoneLoading);
  265. if (ClientListener.stackList != null) {
  266. List<ItemStack> stackList = new LinkedList<>(ClientListener.stackList);
  267. List<ItemGroup> itemGroups = new LinkedList<>(Arrays.asList(ItemGroup.GROUPS));
  268. FabricCreativeGuiComponents.COMMON_GROUPS.forEach(itemGroups::add);
  269. itemGroups.add(null);
  270. if (Core.config.itemListOrdering != REIItemListOrdering.REGISTRY)
  271. Collections.sort(stackList, (itemStack, t1) -> {
  272. if (Core.config.itemListOrdering.equals(REIItemListOrdering.NAME))
  273. return itemStack.getDisplayName().getFormattedText().compareToIgnoreCase(t1.getDisplayName().getFormattedText());
  274. if (Core.config.itemListOrdering.equals(REIItemListOrdering.ITEM_GROUPS))
  275. return itemGroups.indexOf(itemStack.getItem().getItemGroup()) - itemGroups.indexOf(t1.getItem().getItemGroup());
  276. return 0;
  277. });
  278. if (!Core.config.isAscending)
  279. Collections.reverse(stackList);
  280. Arrays.stream(searchText.split("\\|")).forEachOrdered(s -> {
  281. List<SearchArgument> arguments = new ArrayList<>();
  282. while (s.startsWith(" ")) s = s.substring(1);
  283. while (s.endsWith(" ")) s = s.substring(0, s.length());
  284. if (s.startsWith("@-") || s.startsWith("-@"))
  285. arguments.add(new SearchArgument(SearchArgument.ArgumentType.MOD, s.substring(2), false));
  286. else if (s.startsWith("@"))
  287. arguments.add(new SearchArgument(SearchArgument.ArgumentType.MOD, s.substring(1), true));
  288. else if (s.startsWith("#-") || s.startsWith("-#"))
  289. arguments.add(new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, s.substring(2), false));
  290. else if (s.startsWith("#"))
  291. arguments.add(new SearchArgument(SearchArgument.ArgumentType.TOOLTIP, s.substring(1), true));
  292. else if (s.startsWith("-"))
  293. arguments.add(new SearchArgument(SearchArgument.ArgumentType.TEXT, s.substring(1), false));
  294. else
  295. arguments.add(new SearchArgument(SearchArgument.ArgumentType.TEXT, s, true));
  296. stackList.stream().filter(itemStack -> filterItem(itemStack, arguments)).forEachOrdered(stacks::add);
  297. });
  298. }
  299. List<ItemStack> workingItems = ClientListener.stackList == null || (Core.runtimeConfig.craftableOnly && lastPlayerItems.size() > 0) ? new ArrayList<>() : ClientListener.stackList;
  300. if (Core.runtimeConfig.craftableOnly) {
  301. REIRecipeManager.instance().findCraftableByItems(lastPlayerItems).forEach(workingItems::add);
  302. workingItems.addAll(lastPlayerItems);
  303. }
  304. final List<ItemStack> finalWorkingItems = workingItems;
  305. view.addAll(stacks.stream().filter(itemStack -> {
  306. if (!Core.runtimeConfig.craftableOnly)
  307. return true;
  308. for(ItemStack workingItem : finalWorkingItems)
  309. if (itemStack.isEqualIgnoreTags(workingItem))
  310. return true;
  311. return false;
  312. }).distinct().collect(Collectors.toList()));
  313. page = 0;
  314. fillSlots();
  315. }
  316. private List<ItemStack> getInventoryItemsTypes() {
  317. List<DefaultedList<ItemStack>> field_7543 = ImmutableList.of(MinecraftClient.getInstance().player.inventory.main, MinecraftClient.getInstance().player.inventory.armor
  318. , MinecraftClient.getInstance().player.inventory.offHand);
  319. List<ItemStack> inventoryStacks = new ArrayList<>();
  320. field_7543.forEach(itemStacks -> itemStacks.forEach(itemStack -> {
  321. if (!itemStack.getItem().equals(Items.AIR))
  322. inventoryStacks.add(itemStack);
  323. }));
  324. return inventoryStacks;
  325. }
  326. private boolean filterItem(ItemStack itemStack, List<SearchArgument> arguments) {
  327. String mod = getMod(itemStack);
  328. List<String> toolTipsList = REIRenderHelper.getOverlayedGui().getStackTooltip(itemStack);
  329. String toolTipsMixed = toolTipsList.stream().skip(1).collect(Collectors.joining()).toLowerCase();
  330. String allMixed = Stream.of(itemStack.getDisplayName().getString(), toolTipsMixed).collect(Collectors.joining()).toLowerCase();
  331. for(SearchArgument searchArgument : arguments.stream().filter(searchArgument -> !searchArgument.isInclude()).collect(Collectors.toList())) {
  332. if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.MOD))
  333. if (mod.toLowerCase().contains(searchArgument.getText().toLowerCase()))
  334. return false;
  335. if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TOOLTIP))
  336. if (toolTipsMixed.contains(searchArgument.getText().toLowerCase()))
  337. return false;
  338. if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TEXT))
  339. if (allMixed.contains(searchArgument.getText().toLowerCase()))
  340. return false;
  341. }
  342. for(SearchArgument searchArgument : arguments.stream().filter(SearchArgument::isInclude).collect(Collectors.toList())) {
  343. if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.MOD))
  344. if (!mod.toLowerCase().contains(searchArgument.getText().toLowerCase()))
  345. return false;
  346. if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TOOLTIP))
  347. if (!toolTipsMixed.contains(searchArgument.getText().toLowerCase()))
  348. return false;
  349. if (searchArgument.getArgumentType().equals(SearchArgument.ArgumentType.TEXT))
  350. if (!allMixed.contains(searchArgument.getText().toLowerCase()))
  351. return false;
  352. }
  353. return true;
  354. }
  355. public void tick() {
  356. controls.forEach(f -> f.tick());
  357. }
  358. public void setLastHovered(Control ctrl) {
  359. lastHovered = ctrl;
  360. }
  361. public Control getLastHovered() {
  362. return lastHovered;
  363. }
  364. private String getMod(ItemStack stack) {
  365. if (stack != null && !stack.isEmpty()) {
  366. Identifier location = Registry.ITEM.getId(stack.getItem());
  367. return location.getNamespace();
  368. }
  369. return "";
  370. }
  371. }