Compare commits

..
82 Commits
Author SHA1 Message Date
Ruling-0 da55c3b12c Merge remote-tracking branch 'origin/master' into mu 2026-10-24 09:01:08 -05:00
slprime ebe83f970f Fix group drag between bookmark namespaces (#1090) 2026-10-24 01:09:00 +03:00
slprime ad9beb7f90 Fill crafting grid with one recipe when Ctrl is held (#1089) 2026-10-23 23:44:16 +03:00
slprime 68bdb51c6b Update tree camera and scrollbar dragging every frame (#1087) 2026-10-22 23:58:41 +03:00
slprime e4e1964dec Fix NPE in BookmarksGridSlot when dragging a group across namespaces (#1086) 2026-10-22 16:22:23 +03:00
slprime 560618024f Autocrafting Optimization (#1084) 2026-10-21 01:24:40 +03:00
slprime e2a2d2a9df Batch 9-slice quads in DrawableResource (#1082) 2026-10-19 15:47:52 +03:00
slprime 7163630cb1 Move 9-slice setup into DrawableBuilder (#1081) 2026-10-16 23:18:28 +03:00
RessedandMartin Robertz ab168ad6ba Make StrictIdentifierFilter (<modid:name:meta>) resolve item on first match (#1080)
Co-authored-by: Martin Robertz <[email protected]>
2026-10-16 20:42:59 +03:00
Martin Robertz e13e3483f0 update+deps+bs 2026-10-16 10:20:51 +02:00
slprime de26547b64 Treat item aliases as translation keys (#1079) 2026-10-15 20:19:58 +03:00
slprime c49f8808fd Fix NPE in item panel getMask (#1078) 2026-10-15 18:59:55 +03:00
slprime 1c6cc8b693 Unify hovered recipe lookup into HoveredRecipe (#1076) 2026-10-12 22:03:07 +03:00
Ruling-0 9f74331116 Merge remote-tracking branch 'origin/master' into mu 2026-10-12 11:27:47 -05:00
slprime 43ae1b613a Add recipe origins to handler debug widget (#1072) 2026-10-12 13:43:22 +03:00
Ruling-0 cdac4cc4a8 Bump MaterialLib to 0.24.0-mu 2026-10-11 18:25:32 -05:00
Wilhelm Schuster 402c4325a6 Update java version in GitHub issue template (#1074) 2026-10-11 23:33:54 +02:00
slprime 550e85c456 Fix -@mod and !@mod search syntax in Extended mode (#1071) 2026-10-10 14:58:17 +03:00
slprime 1673079cbb ItemZoom: Add side priority option (#1070) 2026-10-10 14:36:28 +03:00
Ruling-0 9958dd45ff Bump MaterialLib to 0.21.0-mu 2026-10-09 21:16:39 -05:00
Ruling-0 a25879aaad Bump MaterialLib to 0.20.0-mu 2026-10-09 19:03:07 -05:00
Ruling-0 d8c5870a91 Merge remote-tracking branch 'origin/master' into mu 2026-10-09 18:56:18 -05:00
slprime e6174c446a Add options for vertical line count in chain tooltips (#1068) 2026-10-09 21:14:11 +03:00
slprime 869b4571b0 Add recipe cycle freeze option (#1067) 2026-10-09 19:47:38 +03:00
Ruling-0 96260fe68b Bump MaterialLib to 0.19.0-mu 2026-10-06 15:02:21 -05:00
slprime 35f5400ecb Fix Crafting Tree NPE (#1064) 2026-10-06 19:57:10 +03:00
micvog 1e2801c891 Fix crash when returning to the crafting tree after its bookmark group was removed (#1063) 2026-10-06 19:46:37 +03:00
Ruling-0 f44536d2e8 Merge remote-tracking branch 'origin/master' into mu 2026-10-05 13:22:17 -05:00
Ressedandslprime 1b3b495fec Fix crash while dragging bookmark group to another grid (#1062)
Co-authored-by: slprime <[email protected]>
2026-10-05 20:07:52 +03:00
slprime 2844ea35bc Fix Old Handlers Hack (#1061) 2026-10-04 22:07:41 +03:00
Ruling-0 fa8342cfed Bump MaterialLib to 0.18.0-mu 2026-10-04 12:35:02 -05:00
Ruling-0 82ddc4da41 Bump MaterialLib to 0.17.0-mu 2026-10-04 12:16:02 -05:00
Ruling-0 3d1d2f79e5 Bump MaterialLib to 0.16.0-mu 2026-10-04 02:48:31 -05:00
slprime c020ecbf1f Add Custom Renderer API for Fluids (#1059) 2026-10-02 01:22:58 +03:00
slprime a105a60a1a Fix Subset Width Calculation (#1058) 2026-10-01 23:16:35 +03:00
Ruling-0 867651ecab Bump MaterialLib to 0.15.0-mu 2026-09-30 13:01:52 -05:00
Ruling-0 0674aae0ec Merge remote-tracking branch 'origin/master' into mu 2026-09-30 11:33:15 -05:00
Ruling-0 5277fd36f5 Bump MaterialLib to 0.14.0-mu 2026-09-30 11:03:30 -05:00
Ruling-0 b6b31eca33 Bump MaterialLib to 0.13.0-mu 2026-09-29 16:57:41 -05:00
slprime 3a4a6734dc Fix NPE on GT fluid display without NBT (#1057) 2026-09-29 22:30:43 +03:00
slprime 11f9194de9 Fix EnderIo Tank (#1056) 2026-09-29 20:00:29 +03:00
Ruling-0 0dbeae9f68 Bump MaterialLib to 0.12.0-mu 2026-09-29 07:22:41 -05:00
Ruling-0 f0547b5ca6 Merge remote-tracking branch 'origin/master' into mu 2026-09-28 07:38:24 -05:00
slprimeandgithub-actions[bot] a685efb3b7 Recipe Group Optimization (#1054)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-28 00:19:29 +03:00
danyadevandMartin Robertz 9f3697bb67 fix cycling for fluid permutations (#1048)
Co-authored-by: Martin Robertz <[email protected]>
2026-09-27 18:21:56 +03:00
slprime 18abb880f8 Add widget size to recipe event (#1053) 2026-09-27 14:13:27 +03:00
slprime 5868187410 Save Tree State (#1052) 2026-09-27 11:00:27 +03:00
slprime f84c4ef2bd Rename PositionStack.Fluid to Tank (#1051) 2026-09-27 08:09:52 +03:00
slprime a54c414595 Add Recipe Groups to GuiRecipe (#1049) 2026-09-27 07:39:27 +03:00
slprime 3c472a02cb Add Flowinf Texture to PositionStack (#1047) 2026-09-26 01:06:38 +03:00
slprime 382278bebb Fix Keybinding Category Names (#1046) 2026-09-25 23:36:57 +03:00
slprime 55c9811a2b Fix search autofocus losing the first typed character (#1045) 2026-09-25 22:17:38 +03:00
slprime 0ec87a8b9e Refactoring Chain Tooltip (#1044) 2026-09-25 22:08:15 +03:00
slprime 939845a9ed Add List View to Recipe (#1041) 2026-09-23 00:14:39 +03:00
Ruling-0 e7a252a0de Bump MaterialLib to 0.11.0-mu 2026-09-22 09:54:05 -05:00
Ruling-0 442118d2e4 Merge remote-tracking branch 'origin/master' into mu 2026-09-20 11:21:25 -05:00
Martin Robertz c9db437dd1 update+gradle+bs+deps+gitignore
fix curse auto uploader
2026-09-18 00:21:05 +02:00
Ruling-0 972d0e4f3c Merge remote-tracking branch 'origin/master' into mu 2026-09-17 16:25:45 -05:00
Jakub b1f20104a6 Publishing (#1039) 2026-09-17 14:09:06 +02:00
AlexandAlex Vermette ab8d5f275f fix(NEIController): Skip MMB on slot click to avoid unecessary conflict with itemstack using it (#1038)
Co-authored-by: Alex Vermette <[email protected]>
2026-09-17 09:16:46 +03:00
Ruling-0 bffef55cb8 Merge remote-tracking branch 'origin/master' into mu 2026-09-15 16:10:19 -05:00
slprime 9e87f2b158 Item Craftables Panel Optimization (#1036) 2026-09-14 18:36:58 +03:00
Dream Master ab512d3fd8 update deps 2026-09-14 10:28:02 +02:00
slprime 4a9f17590b Fix Slot Highlighting (#1035) 2026-09-14 08:44:35 +03:00
slprime a845e6c3e5 Registry NEI Fluids in Server (#1033) 2026-09-12 23:41:17 +03:00
Chitak 985 4f3a0826fe ItemFluidDisplay/getSubItems skips the last fluid (#1032) 2026-09-12 22:43:05 +03:00
slprime 6cd7697ca0 Repair Handler Optimization (#1031) 2026-09-12 20:02:52 +03:00
slprime 38dc14eca1 Fix concurrent modification crash in API.hideItem (#1030) 2026-09-11 08:30:49 +03:00
slprime 1fc824410b Change Fluid Amount Limit (#1029) 2026-09-11 08:10:28 +03:00
slprime af6dba1269 Correctly filtering neifluids (#1027) 2026-09-10 22:52:37 +03:00
Ruling-0 e1d19e0bfc Merge remote-tracking branch 'origin/master' into mu 2026-09-09 12:51:25 -05:00
slprime 2bf5a1cf9f Fix Bookmark Fluid Factor Limit (#1025) 2026-09-09 19:07:19 +03:00
Ranzuandgithub-actions[bot] 2126ec78bc Destroy one pixel and add color localization (#1022)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-09 13:34:25 +03:00
slprime 5f6209366a Fix Detect Fluid Display (#1021) 2026-09-09 07:06:14 +03:00
JarnexisandMykhailo Balaker f5fc4e704d Add Ukrainian (uk_UA) localization and translate README (#996)
Co-authored-by: Mykhailo Balaker <[email protected]>
2026-09-08 18:52:48 +02:00
slprimeandgithub-actions[bot] 3d6c1c6e19 Implement Bookmark Tree Screen (#1018)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-08 13:56:58 +03:00
Ruling-0 b0c5f6fcda Bump MaterialLib to 0.10.0-mu 2026-08-29 22:33:14 -05:00
Ruling-0 953638be05 Merge remote-tracking branch 'origin/master' into mu 2026-08-29 22:19:17 -05:00
Ruling-0 e53d728baf Doc and code style pass 2026-08-20 17:53:20 -05:00
Ruling-0 58b63671ce Clean up ml resolver docs 2026-08-17 21:27:08 -05:00
Ruling-0 a1631b0f58 Resolve MaterialLib ml references in stack config entries 2026-08-17 20:18:14 -05:00
Ruling-0 eafd9ff4a3 Add MaterialLib compileOnly dependency 2026-08-17 20:18:14 -05:00
135 changed files with 10130 additions and 1994 deletions

No files matched your search

+2 -4
View File
@@ -33,12 +33,10 @@ body:
description: What Java version are you using? It's worth mentioning that if you play on Java9+ you should try update to latest minor release (e.g. prefer Java 17.0.6 over 17.0.2) of that version.
options:
- Java 8
- Java 9
- Java 11
- Java 17
- Java 19
- Java 20
- Java 21
- java 25
- java 26
- Other (Please Specify)
validations:
required: true
+3 -1
View File
@@ -28,6 +28,7 @@ whitelist.json
*.iws
src/main/resources/mixins.*([!.]).json
*.bat
*.sh
*.DS_Store
!gradlew.bat
.factorypath
@@ -35,4 +36,5 @@ addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
layout.json
layout.json
+2
View File
@@ -1,5 +1,7 @@
# NotEnoughItems Unofficial - By the GTNH Team
🌐 **Languages:** [English](README.md) | [Українська](README_uk.md)
A continuation of NotEnoughItems for 1.7.10 by the developers of Gregtech: New Horizons modpack, with features either inspired and/or backported from JustEnoughItems
We've tested this against all of the mods included in GTNH, as well as a limited set of other mods (like Reika's mods). Every effort has been made to maintain backwards compatibility, however the focus is on the mods contained in the GTNH modpack.
+93
View File
@@ -0,0 +1,93 @@
# NotEnoughItems Unofficial — від команди GTNH
🌐 **Мови:** [English](README.md) | [Українська](README_uk.md)
Продовження модифікації NotEnoughItems для 1.7.10 від розробників збірки GregTech: New Horizons із функціями, натхненними та/або бекпортованими з JustEnoughItems.
Ми протестували цю версію з усіма модами, що входять до GTNH, а також із обмеженим набором інших модів (як-от моди від Reika). Було докладено всіх зусиль для збереження зворотної сумісності, однак основну увагу приділено модам зі збірки GTNH.
Якщо у вас виникли проблеми з NEI за межами збірки GTNH, ви можете повідомити про них у [GitHub-репозиторії GTNH NEI](https://github.com/GTNewHorizons/NotEnoughItems).
## Нові можливості:
* Швидкодія
- Використовує паралельний потік для пошуку у списку предметів на кількох ядрах процесора, що в середньому пришвидшує пошук у 2–6 разів.
- Завантажує обробники рецептів паралельно.
* Текстове поле пошуку з більшістю очікуваних функцій — переміщення курсора вперед/назад, виділення тексту тощо.
* Закладки! Перебуваєте у процесі крафту? Додайте потрібний рецепт до закладок за допомогою клавіші «A» або налаштуйте власну клавішу.
* Перемикання видимості панелі закладок. Гаряча клавіша за замовчуванням — `B`. Меню «Підгрупи предметів» доступне лише тоді, коли панель закладок прихована.
* Кнопки утиліт та читів вирівнюються в ряд та переносяться на новий рядок залежно від розміру графічного інтерфейсу.
* Список предметів (`ItemList`) більше не генерується повторно з `ItemRegistry` під час кожного відкриття інвентарю.
* Вкладки у стилі JEI (або творчого режиму) [За бажанням]. Примітка: вимагає явної підтримки для рендерингу `ItemStack`, інакше використовуються перші дві літери назви обробника.
* Вкладки/обробники завантажуються з конфігураційного CSV-файлу в JAR-архіві (або за бажанням з папки `config`). Для авторів модів доступні IMC-повідомлення з NBT-даними `registerHandlerInfo` та `removeHandlerInfo` для додавання інформації про обробники з використанням тих самих полів, що й у CSV-файлі.
* Пошук за допомогою синтаксису `@[Мод]->[предмет]`. Наприклад: `@Mod.gregtech->iron ingot`.
* Перемикання між режимами рецептів, утиліт та читів за допомогою CTRL + ЛКМ по іконці гайкового ключа.
* Інструменти/предмети з GT5u та інструменти з GT6 тепер мають коректно працювати з функцією накладення та перенесення рецепта.
### Обробник інформаційних сторінок
Хочете додати інформацію про блок чи предмет, не створюючи для нього величезну підказку? Ви можете додати інформацію про будь-який блок чи предмет, зареєструвавши його в обробнику інформації.
Інформаційні сторінки відображаються під час пошуку рецептів або способів використання відповідного предмета. Усі відповідні предмети будуть циклічно змінюватися у верхній частині інформаційної сторінки.
Ваш мод може викликати:
```java
FMLInterModComms.sendMessage("NotEnoughItems", "addItemInfo", nbt);
```
Де `nbt` — це `NBTTagCompound`, відформатований, як описано нижче.
| Тег | Тип | Опис |
|------------------|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `filter` | `String` | Текстовий фільтр для визначення предметів, для яких має відображатися ця інформаційна сторінка.<br/>Можна використовувати `modname:itemname`, метадані, теги OreDictionary та регулярні вирази. Повний формат можна переглянути у `config/NEI/collapsibleitems.cfg`. |
| `page` / `pages` | `String` / `String NBTTagList` | Безпосередньо текст для відображення. Різницю між одно- та багатосторінковими повідомленнями див. нижче.<br/>Обидва формати підтримують автоматичний переклад наданих рядків. |
#### Односторінковий формат
Використовуйте текстовий тег `"page"`, якщо хочете додати лише одну сторінку інформації:
```java
NBTTagCompound tag = new NBTTagCompound();
tag.setString("filter", "minecraft:log 3");
tag.setString("page", "Exotic wood found in tropical climates.");
FMLInterModComms.sendMessage("NotEnoughItems", "addItemInfo", tag);
```
#### Багатосторінковий формат
Використовуйте тег `"pages"` як `NBTTagList` із рядків, якщо потрібно додати понад одну сторінку:
```java
NBTTagCompound tag = new NBTTagCompound();
tag.setString("filter", "minecraft:diamond_sword");
NBTTagList pages = new NBTTagList();
pages.appendTag(new NBTTagString("A powerful melee weapon."));
pages.appendTag(new NBTTagString("Can be enchanted for extra effects."));
tag.setTag("pages", pages);
FMLInterModComms.sendMessage("NotEnoughItems", "addItemInfo", tag);
```
* Кожен елемент списку — це одна сторінка.
* Сторінки з’являтимуться в NEI з кнопками для їх гортання.
#### Відповідність предметів
Усі предмети, що відповідають фільтру `filter`, відображатимуть вашу інформаційну сторінку. Повний формат фільтра див. у `config/NEI/collapsibleitems.cfg`.
#### Файл конфігурації інформації про предмети
Якщо ви творець збірки модів і хочете створити власні інформаційні сторінки для предметів, їх можна легко додати у файлі `config/NEI/informationpages.cfg`, де також наведено ще кілька прикладів.
## Інші примітки:
* Вилучено стиль TMI
* Вилучено збереження/завантаження стану інвентарю
## Ліцензія
Авторські права на модифікації від команди GTNH (c) 2019-2024 належать mitchej123 та команді GTNH.
Поширюється за ліцензією LGPL-3.0 або новішої версії — використовуйте цей код на власний розсуд, але, будь ласка, діліться з розробниками будь-якими внесеними вами змінами.
Окремі частини коду натхненні, запозичені або бекпортовані з [JEI](https://github.com/mezz/JustEnoughItems/tree/1.12) за ліцензією MIT.
Авторські права на оригінальний код (c) 2014-2015 належать mezz; код поширювався за ліцензією MIT.
+11 -5
View File
@@ -5,13 +5,14 @@ configurations.create('gtnhlibEmbed') {
}
dependencies {
api('com.github.GTNewHorizons:CodeChickenCore:1.4.17:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:GTNHLib:0.11.37:dev')
api('com.github.GTNewHorizons:CodeChickenCore:1.4.22:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:GTNHLib:0.11.54:dev')
shadowImplementation('org.apache.commons:commons-csv:1.10.0')
compileOnly('maven.modrinth:forge-essentials:7.5.1')
compileOnly('com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.14-alpha:dev')
compileOnly('com.ruling_0:MaterialLib:0.24.0-mu:dev')
antlr('org.antlr:antlr4:4.13.2')
shadowImplementation('org.antlr:antlr4-runtime:4.13.2')
@@ -23,13 +24,18 @@ dependencies {
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
testRuntimeOnly("org.lwjgl.lwjgl:lwjgl:2.9.3");
compileOnly("com.github.GTNewHorizons:GTNHLib:0.11.37") { transitive = false }
gtnhlibEmbed("com.github.GTNewHorizons:GTNHLib:0.11.37") { transitive = false }
compileOnly("com.github.GTNewHorizons:GTNHLib:0.11.54") { transitive = false }
gtnhlibEmbed("com.github.GTNewHorizons:GTNHLib:0.11.54") { transitive = false }
}
def embedArchiveOperations = gtnhGradle.archiveOperations
def gtnhlibEmbedContents = configurations.gtnhlibEmbed.elements.map { files ->
files.collect { embedArchiveOperations.zipTree(it.asFile) }
}
[tasks.jar, tasks.shadowJar].each {
it.configure {
from({ project.configurations.getByName('gtnhlibEmbed').collect { zipTree(it) } }) {
from(gtnhlibEmbedContents) {
include 'com/gtnewhorizon/gtnhlib/color/ColorResource.class'
include 'com/gtnewhorizon/gtnhlib/color/ColorResource$*.class'
}
+5
View File
@@ -186,6 +186,11 @@ modrinthRelations = required-project\:codechickencore-unofficial
# Leave this empty if you don't want to publish on CurseForge.
curseForgeProjectId = 358228
# Comma-separated supported CurseForge environments: client, server (case-insensitive).
# Use client or server for mods that only support one environment. Defaults to client,server.
# These tags describe where the mod can run, not whether installation is required on both sides.
curseForgeEnvironments = client,server
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
# Syntax: type1:name1;type2:name2;...
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
+10 -10
View File
@@ -1,12 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/97d46b5ff4df0bf61e95652a190a5567/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/9ef54a08e06b0754cee9fc95ed3a3134/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/97d46b5ff4df0bf61e95652a190a5567/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/9ef54a08e06b0754cee9fc95ed3a3134/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1df82b76031285ef2e6a4f59ffe9299d/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/c7d5be6a02945ac2e045048ee54a7d32/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/97d46b5ff4df0bf61e95652a190a5567/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/9ef54a08e06b0754cee9fc95ed3a3134/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/643f404f769811f5f265fd66f7af5d89/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/38256085a73e137cb4937ea9c4807fec/redirect
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9064f22734b42ac03e85df04b632e167/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/4d0277c49991306ac78048580fe34bcf/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a9fa3b809e484a1e1ac4308a5dda661a/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/aa99913fa6d767d899ffc96082da01ca/redirect
toolchainVersion=25
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+3 -3
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+12 -23
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,29 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
+1
View File
@@ -1,6 +1,7 @@
// Add any additional repositories for your dependencies here
repositories {
mavenLocal()
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.29'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.34'
}
@@ -7,8 +7,10 @@ import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.ChatAllowedCharacters;
import org.lwjgl.input.Keyboard;
@@ -30,6 +32,8 @@ public class AutoFocusWidget implements IContainerInputHandler {
protected boolean autofocus = false;
protected Point mouse;
protected Point pendingMouse;
protected GuiTextField textInputHolder;
public AutoFocusWidget() {
GuiContainerManager.addInputHandler(this);
@@ -38,32 +42,83 @@ public class AutoFocusWidget implements IContainerInputHandler {
public void load(GuiContainer gui) {
this.autofocus = LayoutManager.searchField.isVisible() && NEIClientConfig.searchWidgetAutofocus() != 0
&& isAllowedGuiAutoSearchFocus(gui);
this.mouse = getMousePosition();
this.mouse = null;
this.pendingMouse = null;
if (this.autofocus) {
beginTextInput();
}
}
protected void beginTextInput() {
if (this.textInputHolder == null) {
this.textInputHolder = new GuiTextField(Minecraft.getMinecraft().fontRenderer, 0, 0, 0, 0);
}
this.textInputHolder.setFocused(true);
}
protected void endTextInput() {
if (this.textInputHolder != null) {
this.textInputHolder.setFocused(false);
}
}
public void guiTick() {
if (!this.autofocus) {
return;
}
final Point current = getMousePosition();
if (this.mouse == null) {
if (current.equals(this.pendingMouse)) {
this.mouse = current;
}
this.pendingMouse = current;
} else if (!this.mouse.equals(current)) {
cancel();
}
}
protected void cancel() {
if (this.autofocus) {
this.autofocus = false;
endTextInput();
}
}
@Override
public boolean keyTyped(GuiContainer gui, char keyChar, int keyCode) {
if (this.autofocus) {
this.autofocus = false;
if (!this.mouse.equals(getMousePosition())) {
return false;
}
if (NEIClientConfig.searchWidgetAutofocus() == 2
&& GameSettings.isKeyDown(Minecraft.getMinecraft().gameSettings.keyBindInventory)) {
cancel();
return false;
}
if (keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER
|| keyCode == Keyboard.KEY_ESCAPE) {
cancel();
return false;
}
if (!ChatAllowedCharacters.isAllowedCharacter(keyChar)) {
return false;
}
this.autofocus = false;
LayoutManager.searchField.setFocus(true);
endTextInput();
final String oldText = LayoutManager.searchField.text();
LayoutManager.searchField.handleKeyPress(keyCode, keyChar);
return true;
return !LayoutManager.searchField.text().equals(oldText);
}
return false;
@@ -76,13 +131,13 @@ public class AutoFocusWidget implements IContainerInputHandler {
@Override
public boolean mouseClicked(GuiContainer gui, int mousex, int mousey, int button) {
this.autofocus = false;
cancel();
return false;
}
@Override
public boolean mouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {
this.autofocus = false;
cancel();
return false;
}
@@ -91,22 +146,22 @@ public class AutoFocusWidget implements IContainerInputHandler {
@Override
public void onMouseClicked(GuiContainer gui, int mousex, int mousey, int button) {
this.autofocus = false;
cancel();
}
@Override
public void onMouseDragged(GuiContainer gui, int mousex, int mousey, int button, long heldTime) {
this.autofocus = false;
cancel();
}
@Override
public void onMouseScrolled(GuiContainer gui, int mousex, int mousey, int scrolled) {
this.autofocus = false;
cancel();
}
@Override
public void onMouseUp(GuiContainer gui, int mousex, int mousey, int button) {
this.autofocus = false;
cancel();
}
protected boolean isAllowedGuiAutoSearchFocus(GuiContainer gui) {
@@ -572,9 +572,10 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
return;
} else if (this.sortableGroup != null) {
final int overRowIndex = grid.getRowIndex(mousey);
final int overRowIndex = Math.min(grid.getRowIndex(mousey), grid.getRows() - 1);
if (overRowIndex >= 0 && this.sortableGroup.groupId != this.grid.getRowGroupId(overRowIndex)) {
if (overRowIndex >= 0 && !isOverNamespaceControls(mousex, mousey)
&& !this.sortableGroup.matches(this.grid, this.grid.getRowGroupId(overRowIndex))) {
this.grid.moveGroup(this.sortableGroup, overRowIndex);
}
@@ -1022,14 +1023,18 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
this.mouseDownSlot = -1;
}
@Override
public boolean onMouseWheel(int shift, int mousex, int mousey) {
if ((!inEditingState() || this.sortableGroup != null) && new Rectangle4i(
private boolean isOverNamespaceControls(int mousex, int mousey) {
return new Rectangle4i(
namespacePrev.x,
namespacePrev.y,
namespaceNext.x + namespaceNext.w - namespacePrev.x,
namespacePrev.h).contains(mousex, mousey)) {
namespacePrev.h).contains(mousex, mousey);
}
@Override
public boolean onMouseWheel(int shift, int mousex, int mousey) {
if ((!inEditingState() || this.sortableGroup != null) && isOverNamespaceControls(mousex, mousey)) {
if (shift > 0) {
this.storage.prevNamespace();
@@ -1048,15 +1053,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;
@@ -1111,10 +1110,10 @@ public class BookmarkPanel extends PanelWidget<BookmarkGrid> {
}
if (super.onMouseWheel(shift, mousex, mousey)) {
final int overRowIndex = grid.getRowIndex(mousey);
final int overRowIndex = Math.min(grid.getRowIndex(mousey), grid.getRows() - 1);
if (this.sortableGroup != null && overRowIndex >= 0
&& this.sortableGroup.groupId != this.grid.getRowGroupId(overRowIndex)) {
&& !this.sortableGroup.matches(this.grid, this.grid.getRowGroupId(overRowIndex))) {
this.grid.moveGroup(this.sortableGroup, overRowIndex);
}
@@ -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();
@@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTBase.NBTPrimitive;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.StatCollector;
@@ -227,17 +228,17 @@ public class CollapsibleItems {
if (NEIClientConfig.world.nbt.hasKey(STATE_KEY)) {
NBTTagCompound states = NEIClientConfig.world.nbt.getCompoundTag(STATE_KEY);
@SuppressWarnings("unchecked")
final Map<String, NBTPrimitive> list = (Map<String, NBTPrimitive>) states.tagMap;
final Map<String, NBTBase> list = states.tagMap;
final Map<String, GroupItem> mapping = new HashMap<>();
for (GroupItem group : CollapsibleItems.groups) {
mapping.put(group.guid, group);
}
for (Map.Entry<String, NBTPrimitive> nbtEntry : list.entrySet()) {
for (Map.Entry<String, NBTBase> nbtEntry : list.entrySet()) {
if (mapping.containsKey(nbtEntry.getKey())) {
mapping.get(nbtEntry.getKey()).expanded = nbtEntry.getValue().func_150290_f() == 1;
mapping.get(nbtEntry.getKey()).expanded = ((NBTPrimitive) nbtEntry.getValue()).func_150290_f()
== 1;
}
}
}
@@ -17,6 +17,8 @@ public class ColorUtils {
buttonLabelNormal = color.rgb("buttonLabelNormal", "0xE0E0E0"),
buttonLabelHover = color.rgb("buttonLabelHover", "0xFFFFA0"),
buttonLabelDisabled = color.rgb("buttonLabelDisabled", "0x601010"),
buttonLabelInactive = color.rgb("buttonLabelInactive", "0xA0A0A0");
buttonLabelInactive = color.rgb("buttonLabelInactive", "0xA0A0A0"),
craftingTreeLine = color.argb("craftingTreeLine", "0xFF888888");
// spotless:on
}
@@ -0,0 +1,101 @@
package codechicken.nei;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.gui.GuiDraw.ITooltipLineHandler;
public abstract class CompositeTooltipLineHandler implements ITooltipLineHandler {
protected static final int SCREEN_MARGIN = 8 * 2 + 10;
protected static final int RESERVED_HEIGHT = 40;
protected final List<ItemsTooltipLineHandler> lines = new ArrayList<>();
protected final Dimension size = new Dimension();
protected boolean created = false;
protected int screenHeight = 0;
protected abstract void createLines();
protected boolean needsUpdate() {
return false;
}
@Override
public Dimension getSize() {
ensureCreated();
return this.size;
}
@Override
public void draw(int x, int y) {
if (this.size.height == 0) return;
for (ItemsTooltipLineHandler line : this.lines) {
line.draw(x, y);
y += line.getSize().height;
}
}
protected void ensureCreated() {
boolean update = needsUpdate();
update = this.screenHeight != (this.screenHeight = GuiDraw.displaySize().height) || update;
if (this.created && !update) {
return;
}
this.created = true;
this.lines.clear();
this.size.setSize(0, 0);
createLines();
fitToScreen();
for (ItemsTooltipLineHandler line : this.lines) {
this.size.width = Math.max(this.size.width, line.getSize().width);
this.size.height += line.getSize().height;
}
}
protected void fitToScreen() {
if (this.lines.isEmpty()) {
return;
}
final List<ItemsTooltipLineHandler> ordered = new ArrayList<>(this.lines);
ordered.sort(Comparator.comparingInt(ItemsTooltipLineHandler::getRows));
int budget = availableHeight() / ItemsTooltipLineHandler.SLOT_SIZE;
int rest = ordered.size();
for (ItemsTooltipLineHandler line : ordered) {
final int fair = Math.max(1, budget / rest);
if (line.getRows() > fair) {
line.setMaxRows(fair);
}
budget -= line.getRows();
rest--;
}
}
protected int availableHeight() {
return GuiDraw.displaySize().height - SCREEN_MARGIN
- RESERVED_HEIGHT
- this.lines.size() * ItemsTooltipLineHandler.headerHeight();
}
protected static int maxLineRows() {
final int height = GuiDraw.displaySize().height - SCREEN_MARGIN
- RESERVED_HEIGHT
- ItemsTooltipLineHandler.headerHeight();
return Math.max(1, height / ItemsTooltipLineHandler.SLOT_SIZE);
}
}
@@ -8,6 +8,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -16,6 +17,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
@@ -314,6 +316,10 @@ public class FavoriteRecipes {
load();
}
public static boolean hasManual() {
return manualFavorites.size() > 0;
}
public static boolean containsManual(ItemStack stack) {
return getManualFavorite(stack) != null;
}
@@ -322,6 +328,21 @@ public class FavoriteRecipes {
return getFavorite(stack) != null;
}
public static void toggleFavoriteItem(ItemStack stack) {
if (stack == null) return;
if (containsManual(stack)) {
setFavorite(stack, null);
} else {
setFavorite(
stack,
RecipeId.of(
stack,
FavoriteRecipes.FAVORITE_ITEM,
Arrays.asList(new ItemStack(Blocks.fire, 1), stack)));
}
}
public static void setFavorite(ItemStack stack, RecipeId recipeId) {
if (stack == null) return;
+18 -22
View File
@@ -7,40 +7,36 @@ import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
public class GuiNEIButton extends GuiButton {
protected static final ResourceLocation guiTex = new ResourceLocation("textures/gui/widgets.png");
private static final DrawableResource[] BACKGROUNDS = new DrawableResource[3];
static {
for (int state = 0; state < BACKGROUNDS.length; state++) {
BACKGROUNDS[state] = new DrawableBuilder(guiTex, 0, 46 + state * 20, 200, 20).setSlices(2, 2, 2, 2).build();
}
}
public GuiNEIButton(int i, int j, int k, int l, int i1, String s) {
super(i, j, k, l, i1, s);
}
protected DrawableResource getBackground(int mouseX, int mouseY, boolean mouseOver) {
return BACKGROUNDS[getHoverState(mouseOver)];
}
public void drawButton(Minecraft minecraft, int x, int y) {
if (!visible) return;
minecraft.renderEngine.bindTexture(guiTex);
GL11.glColor4f(1, 1, 1, 1);
boolean mouseOver = x >= xPosition && y >= yPosition && x < xPosition + width && y < yPosition + height;
int k = getHoverState(mouseOver);
drawTexturedModalRect(xPosition, yPosition, 0, 46 + k * 20, width / 2, height / 2); // top left
drawTexturedModalRect(xPosition + width / 2, yPosition, 200 - width / 2, 46 + k * 20, width / 2, height / 2); // top
// right
drawTexturedModalRect(
xPosition,
yPosition + height / 2,
0,
46 + k * 20 + 20 - height / 2,
width / 2,
height / 2); // bottom left
drawTexturedModalRect(
xPosition + width / 2,
yPosition + height / 2,
200 - width / 2,
46 + k * 20 + 20 - height / 2,
width / 2,
height / 2); // bottom right
mouseDragged(minecraft, x, y);
GL11.glColor4f(1, 1, 1, 1);
getBackground(x, y, mouseOver).draw(xPosition, yPosition, width, height);
mouseDragged(minecraft, x, y);
drawContent(minecraft, x, y, mouseOver);
}
@@ -55,7 +51,7 @@ public class GuiNEIButton extends GuiButton {
return ColorUtils.buttonLabelNormal.getColor();
}
protected void drawContent(Minecraft minecraft, int y, int x, boolean mouseOver) {
protected void drawContent(Minecraft minecraft, int x, int y, boolean mouseOver) {
FontRenderer fontrenderer = minecraft.fontRenderer;
int color = getTextColour(mouseOver);
@@ -6,11 +6,14 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fluids.FluidContainerRegistry;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
@@ -21,7 +24,6 @@ import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.ICraftingHandler;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.Recipe;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeInfo;
import codechicken.nei.recipe.StackInfo;
@@ -82,6 +84,42 @@ public class ItemCraftablesPanel
}
private static class CraftablesSnapshot {
final GuiContainer guiContainer;
final ItemStackAmount inventory;
final boolean showOnlyFavorites;
final int maxCandidates;
CraftablesSnapshot(GuiContainer guiContainer, ItemStackAmount inventory, boolean showOnlyFavorites,
int maxCandidates) {
this.guiContainer = guiContainer;
this.inventory = inventory;
this.showOnlyFavorites = showOnlyFavorites;
this.maxCandidates = maxCandidates;
}
}
private static class RecipeCandidate {
final IRecipeHandler handler;
final int recipeIndex;
final List<PositionedStack> ingredientStacks;
final ItemStack sortKeyItem;
RecipeCandidate(IRecipeHandler handler, int recipeIndex, List<PositionedStack> ingredientStacks,
ItemStack sortKeyItem) {
this.handler = handler;
this.recipeIndex = recipeIndex;
this.ingredientStacks = ingredientStacks;
this.sortKeyItem = sortKeyItem;
}
}
private static final Comparator<ItemStack> ITEM_SORT_COMPARATOR = Comparator
.comparing(FavoriteRecipes::containsManual).thenComparing(FavoriteRecipes::contains).reversed()
.thenComparing(ItemSorter.instance);
public static List<String> guiBlacklist = new ArrayList<>();
private final int MIN_DELAY = 600;
@@ -93,6 +131,16 @@ public class ItemCraftablesPanel
private long lastPlayerInventorySync = 0;
private int lastFavoritesCount = 0;
private volatile CraftablesSnapshot pendingSnapshot;
private final AtomicReference<Map<ItemStack, RecipeId>> pendingResult = new AtomicReference<>();
private final RestartableTask task = new RestartableTask("NEI Craftables Panel") {
@Override
public void execute() {
pendingResult.set(generateCraftables(ItemCraftablesPanel.this.pendingSnapshot));
}
};
public ItemCraftablesPanel() {
this.grid = new ItemsGrid<>() {
@@ -114,12 +162,7 @@ public class ItemCraftablesPanel
if (!ItemCraftablesPanel.this.availableRecipes.isEmpty()) {
final List<Map.Entry<ItemStack, RecipeId>> entries = ItemCraftablesPanel.this.availableRecipes
.entrySet().stream()
.sorted(
Map.Entry.comparingByKey(
Comparator.comparing(FavoriteRecipes::containsManual)
.thenComparing(FavoriteRecipes::contains).reversed()
.thenComparing(ItemSorter.instance)))
.entrySet().stream().sorted(Map.Entry.comparingByKey(ITEM_SORT_COMPARATOR))
.limit(maxSlotIndex).collect(Collectors.toList());
int slotIndex = 0;
int itemIndex = 0;
@@ -195,10 +238,20 @@ public class ItemCraftablesPanel
public void update() {
this.splittingLineColor = NEIClientConfig.getSetting("inventory.craftables.color").getHexValue();
applyPendingResult();
updateCraftables();
super.update();
}
private void applyPendingResult() {
final Map<ItemStack, RecipeId> result = this.pendingResult.getAndSet(null);
if (result != null && NEIClientConfig.showCraftablesPanelWidget()) {
this.availableRecipes = result;
this.grid.onGridChanged();
}
}
private void updateCraftables() {
if (!NEIClientConfig.showCraftablesPanelWidget()) {
@@ -219,8 +272,14 @@ public class ItemCraftablesPanel
this.lastPlayerInventory = inv;
this.lastGuiContainer = firstGui;
this.lastFavoritesCount = FavoriteRecipes.size();
this.availableRecipes = generateCraftables(firstGui);
this.grid.onGridChanged();
final int maxRows = NEIClientConfig.getIntSetting("inventory.craftables.useRows") + 2;
final int maxCandidates = Math.max(this.grid.columns, 1) * maxRows;
this.pendingSnapshot = new CraftablesSnapshot(
firstGui,
inv,
showOnlyFavorites,
maxCandidates);
this.task.restart();
}
} else {
@@ -244,17 +303,28 @@ public class ItemCraftablesPanel
return (firstGui instanceof GuiRecipe<?>gui) ? gui.firstGui : firstGui;
}
private Map<ItemStack, RecipeId> generateCraftables(GuiContainer firstGui) {
final List<IRecipeHandler> availableHandlers = getAvailableHandlers(firstGui);
private Map<ItemStack, RecipeId> generateCraftables(CraftablesSnapshot snapshot) {
final List<IRecipeHandler> availableHandlers = getAvailableHandlers(snapshot.guiContainer);
final Map<ItemStack, RecipeId> availableRecipes = new HashMap<>();
if (!availableHandlers.isEmpty()) {
final List<ItemStack> invStacks = ItemCraftablesPanel.this.lastPlayerInventory.values();
final Map<Item, List<ItemStack>> invByItem = indexInventoryByItem(snapshot.inventory.values());
for (IRecipeHandler handler : availableHandlers) {
availableRecipes.putAll(filterHandlerRecipes(handler, firstGui, invStacks));
if (snapshot.showOnlyFavorites) {
for (IRecipeHandler handler : availableHandlers) {
availableRecipes.putAll(filterHandlerRecipes(handler, invByItem, true));
}
} else {
final List<RecipeCandidate> candidates = Collections.synchronizedList(new ArrayList<>());
for (IRecipeHandler handler : availableHandlers) {
collectHandlerCandidates(handler, invByItem, candidates);
}
candidates.sort(Comparator.comparing(c -> c.sortKeyItem, ITEM_SORT_COMPARATOR));
candidates.subList(0, Math.min(candidates.size(), snapshot.maxCandidates)).parallelStream()
.forEach(candidate -> buildCandidate(candidate, availableRecipes));
}
}
return availableRecipes;
@@ -270,19 +340,126 @@ public class ItemCraftablesPanel
return availableHandlers;
}
private Map<ItemStack, RecipeId> filterHandlerRecipes(IRecipeHandler handler, GuiContainer firstGui,
List<ItemStack> invStacks) {
private static Map<Item, List<ItemStack>> indexInventoryByItem(List<ItemStack> invStacks) {
final Map<Item, List<ItemStack>> index = new HashMap<>();
for (ItemStack stack : invStacks) {
index.computeIfAbsent(stack.getItem(), i -> new ArrayList<>()).add(stack);
}
return index;
}
private void collectHandlerCandidates(IRecipeHandler handler, Map<Item, List<ItemStack>> invByItem,
List<RecipeCandidate> candidates) {
IntStream.range(0, handler.numRecipes()).parallel().forEach(recipeIndex -> {
final List<PositionedStack> ingredientStacks = handler.getIngredientStacks(recipeIndex);
if (existsIngredients(ingredientStacks, invByItem)) {
final ItemStack sortKeyItem = getCheapResultItem(handler, recipeIndex);
if (sortKeyItem != null) {
candidates.add(new RecipeCandidate(handler, recipeIndex, ingredientStacks, sortKeyItem));
}
}
});
}
private void buildCandidate(RecipeCandidate candidate, Map<ItemStack, RecipeId> availableRecipes) {
final ItemStack result = getCheapResult(candidate.handler, candidate.recipeIndex);
if (result != null) {
final RecipeId recipeId = RecipeId.of(candidate.handler, candidate.recipeIndex, candidate.ingredientStacks);
synchronized (availableRecipes) {
availableRecipes.put(result, recipeId);
}
}
}
private static ItemStack getCheapResultItem(IRecipeHandler handler, int recipeIndex) {
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
if (resultStack != null) {
return resultStack.item;
}
final List<PositionedStack> otherStacks = handler.getOtherStacks(recipeIndex);
for (PositionedStack otherStack : otherStacks) {
if (!FluidContainerRegistry.isContainer(otherStack.items[0])
|| StackInfo.getFluid(otherStack.items[0]) != null) {
return otherStack.item;
}
}
return otherStacks.isEmpty() ? null : otherStacks.get(0).item;
}
private static ItemStack getCheapResult(IRecipeHandler handler, int recipeIndex) {
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
final List<PositionedStack> results = resultStack != null ? Collections.singletonList(resultStack)
: handler.getOtherStacks(recipeIndex);
if (results.isEmpty()) {
return null;
}
ItemStack stack = null;
if (results.size() > 1) {
for (PositionedStack result : results) {
if (!FluidContainerRegistry.isContainer(result.item) || StackInfo.getFluid(result.item) != null) {
stack = result.item;
break;
}
}
}
if (stack == null) {
stack = results.get(0).item;
}
int stackSize = 0;
for (PositionedStack result : results) {
if (containsSameType(result, stack)) {
stackSize += StackInfo.getAmount(result.item);
}
}
return StackInfo.withAmount(stack, stackSize);
}
private static boolean containsSameType(PositionedStack pStack, ItemStack stack) {
final ItemStack normalized = StackInfo.withAmount(stack, 0);
for (ItemStack permutation : pStack.items) {
if (NEIServerUtils.areStacksSameTypeWithNBT(StackInfo.withAmount(permutation, 0), normalized)) {
return true;
}
}
return false;
}
private Map<ItemStack, RecipeId> filterHandlerRecipes(IRecipeHandler handler, Map<Item, List<ItemStack>> invByItem,
boolean showOnlyFavorites) {
final Map<ItemStack, RecipeId> availableRecipes = new HashMap<>();
final boolean showOnlyFavorites = NEIClientConfig.getBooleanSetting("inventory.craftables.favoritesOnly");
IntStream.range(0, handler.numRecipes()).parallel().forEach(recipeIndex -> {
if (existsIngredients(firstGui, handler.getIngredientStacks(recipeIndex), invStacks)) {
final Recipe recipe = Recipe.of(handler, recipeIndex);
final List<PositionedStack> ingredientStacks = handler.getIngredientStacks(recipeIndex);
if (!recipe.getResults().isEmpty()
&& (!showOnlyFavorites || FavoriteRecipes.getFavorite(recipe.getRecipeId()) != null)) {
synchronized (availableRecipes) {
availableRecipes.put(recipe.getResult(), recipe.getRecipeId());
if (existsIngredients(ingredientStacks, invByItem)) {
final ItemStack result = getCheapResult(handler, recipeIndex);
if (result != null) {
final RecipeId recipeId = RecipeId.of(handler, recipeIndex, ingredientStacks);
if (!showOnlyFavorites || FavoriteRecipes.getFavorite(recipeId) != null) {
synchronized (availableRecipes) {
availableRecipes.put(result, recipeId);
}
}
}
}
@@ -291,14 +468,27 @@ public class ItemCraftablesPanel
return availableRecipes;
}
private boolean existsIngredients(GuiContainer firstGui, List<PositionedStack> ingredients,
List<ItemStack> invStacks) {
private boolean existsIngredients(List<PositionedStack> ingredients, Map<Item, List<ItemStack>> invByItem) {
final Map<ItemStack, Integer> usedItems = new HashMap<>();
for (PositionedStack pStack : ingredients) {
final ItemStack used = invStacks.stream().filter(
is -> usedItems.getOrDefault(is, is.stackSize) >= pStack.item.stackSize && pStack.contains(is))
.findAny().orElse(null);
ItemStack used = null;
for (ItemStack permutation : pStack.items) {
final List<ItemStack> candidates = invByItem.get(permutation.getItem());
if (candidates == null) continue;
for (ItemStack is : candidates) {
if (usedItems.getOrDefault(is, is.stackSize) >= pStack.item.stackSize
&& NEIServerUtils.areStacksSameTypeCrafting(permutation, is)) {
used = is;
break;
}
}
if (used != null) break;
}
if (used != null) {
usedItems.put(used, usedItems.getOrDefault(used, used.stackSize) - pStack.item.stackSize);
+2 -1
View File
@@ -11,6 +11,7 @@ import java.util.regex.Pattern;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.StatCollector;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
@@ -103,7 +104,7 @@ public class ItemList {
}
for (String alias : ItemInfo.getAliases(item)) {
if (this.pattern.matcher(alias).find()) {
if (this.pattern.matcher(StatCollector.translateToLocal(alias)).find()) {
return true;
}
}
+1 -7
View File
@@ -75,13 +75,7 @@ public class ItemPanel extends PanelWidget<ItemsPanelGrid> {
}
public static void updateItemList(ArrayList<ItemStack> newItems) {
final ItemsPanelGrid grid = ItemPanels.itemPanel.getGrid();
if (grid.rawItems.size() != newItems.size()) {
grid.setPage(0);
}
grid.setItems(newItems);
ItemPanels.itemPanel.getGrid().setItems(newItems);
ItemPanels.itemPanel.realItems = newItems;
}
@@ -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);
+24 -2
View File
@@ -111,8 +111,14 @@ public class ItemZoom extends Widget implements IContainerInputHandler {
this.xPosition = (availableAreaWidth - this.scale * SLOT_SIZE) / 2;
this.availableAreaWidth = (int) (availableAreaWidth / screenScale);
if (availableAreaWidth / screenScale >= mouse.x) {
this.xPosition += gui.width * screenScale - availableAreaWidth;
final float rightSideShift = gui.width * screenScale - availableAreaWidth;
final boolean leftSideFree = availableAreaWidth / screenScale < mouse.x;
final boolean rightSideFree = mouse.x < gui.width - availableAreaWidth / screenScale;
final boolean showOnRight = leftSideFree && rightSideFree ? !preferLeftSide() : !leftSideFree;
if (showOnRight) {
this.xPosition += rightSideShift;
}
try {
@@ -128,6 +134,22 @@ public class ItemZoom extends Widget implements IContainerInputHandler {
}
}
private boolean preferLeftSide() {
switch (NEIClientConfig.getIntSetting("inventory.itemzoom.preferredSide")) {
case 1:
return true;
case 2:
return false;
}
final int leftCount = NEIClientConfig.isBookmarkPanelHidden()
|| ItemPanels.bookmarkPanel.getGrid().getPerPage() == 0 ? 0 : ItemPanels.bookmarkPanel.getGrid().size();
final int rightCount = ItemPanels.itemPanel.getGrid().getPerPage() == 0 ? 0
: ItemPanels.itemPanel.getGrid().size();
return leftCount <= rightCount;
}
private float getPointSize(GuiContainer gui) {
final float screenScale = 1.0f / getScreenScale();
final float availableAreaWidth = (gui.width - (gui.xSize + gui.width) / 2) * screenScale;
@@ -89,6 +89,10 @@ public class ItemsPanelGrid extends ItemsGrid<ItemsPanelGrid.ItemsPanelGridSlot,
public void refresh(GuiContainer gui) {
if (this.newItems != null) {
if (this.rawItems.size() != this.newItems.size()) {
this.page = 0;
}
this.groupItems.clear();
this.itemToGroup.clear();
this.forceExpand = false;
@@ -4,6 +4,7 @@ import static codechicken.lib.gui.GuiDraw.fontRenderer;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -17,56 +18,265 @@ import org.lwjgl.opengl.GL12;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.gui.GuiDraw.ITooltipLineHandler;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.NEIClientUtils.Alignment;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.StackInfo;
import codechicken.nei.util.ReadableNumberConverter;
public class ItemsTooltipLineHandler implements ITooltipLineHandler {
protected static final int SLOT_SIZE = 18;
protected static final int MAX_COLUMNS = 11;
protected static final int MARGIN_TOP = 2;
@FunctionalInterface
public interface AmountRenderer {
protected String label;
protected EnumChatFormatting labelColor = EnumChatFormatting.GRAY;
protected List<ItemStack> items;
protected int activeStackIndex = -1;
protected Dimension size = new Dimension();
protected boolean saveStackSize = false;
protected int columns = 0;
protected int count = 0;
protected int rows = 0;
protected int length = 0;
public ItemsTooltipLineHandler(String label, List<ItemStack> items) {
this(label, items, true, 5);
void draw(int x, int y, ItemStack stack, long amount);
}
public ItemsTooltipLineHandler(String label, List<ItemStack> items, boolean saveStackSize, int maxRows) {
this.label = label;
this.items = groupingItemStacks(items);
this.saveStackSize = saveStackSize;
this.length = this.items.size();
public static class NoAmountRenderer implements AmountRenderer {
if (this.length > 0) {
this.columns = Math.min(MAX_COLUMNS, this.length);
this.rows = Math.min(maxRows, (int) Math.ceil((float) this.length / this.columns));
@Override
public void draw(int x, int y, ItemStack stack, long amount) {
GuiContainerManager.drawItem(x, y, stack, true, "");
}
this.size.width = Math.max(this.columns * SLOT_SIZE, fontRenderer.getStringWidth(this.label) + 15);
this.size.height = this.rows * SLOT_SIZE + fontRenderer.FONT_HEIGHT + 2 + MARGIN_TOP;
protected static void drawAmountText(String text, Rectangle4i rect, int color, Alignment alignment) {
NEIClientUtils.drawNEIOverlayText(text, rect, 1, color, true, alignment);
}
}
this.count = Math.min(
this.length,
Math.min(
this.columns * this.rows,
this.length > MAX_COLUMNS * maxRows ? (MAX_COLUMNS * maxRows) : Integer.MAX_VALUE));
public static class TotalAmountRenderer extends NoAmountRenderer {
if (this.items.size() > this.count) {
String text = "+" + (this.items.size() - this.count);
this.count -= (int) Math.ceil((float) (fontRenderer.getStringWidth(text) - 2) / SLOT_SIZE);
private static class AmountText {
public final String text;
public final Alignment alignment;
public AmountText(String text, Alignment alignment) {
this.text = text;
this.alignment = alignment;
}
}
private final Map<ItemStack, AmountText> cache = new IdentityHashMap<>();
@Override
public void draw(int x, int y, ItemStack stack, long amount) {
super.draw(x, y, stack, amount);
drawAmount(new Rectangle4i(x, y, SLOT_SIZE - 2, SLOT_SIZE - 2), stack, amount);
}
protected void drawAmount(Rectangle4i rect, ItemStack stack, long amount) {
final AmountText amountText = this.cache.computeIfAbsent(stack, key -> {
final boolean isFluidDisplay = StackInfo.isFluidDisplayItem(key);
final String text = formatAmount(key, amount);
return new AmountText(
isFluidDisplay && !text.isEmpty() ? text + "L" : text,
isFluidDisplay ? Alignment.BottomLeft : Alignment.BottomRight);
});
if (!amountText.text.isEmpty()) {
drawAmountText(amountText.text, rect, 0xFFFFFF, amountText.alignment);
}
}
protected String formatAmount(ItemStack stack, long amount) {
return amount == 0 ? "" : ReadableNumberConverter.INSTANCE.toWideReadableForm(amount);
}
}
public static class StacksAmountRenderer extends NoAmountRenderer {
private static class StacksText {
public final String stacks;
public final String remainder;
public final int color;
public StacksText(String stacks, String remainder, int color) {
this.stacks = stacks;
this.remainder = remainder;
this.color = color;
}
}
private final Map<ItemStack, StacksText> cache = new IdentityHashMap<>();
@Override
public void draw(int x, int y, ItemStack stack, long amount) {
super.draw(x, y, stack, amount);
drawAmount(new Rectangle4i(x, y, SLOT_SIZE - 2, SLOT_SIZE - 2), stack, amount);
}
protected void drawAmount(Rectangle4i rect, ItemStack stack, long amount) {
final StacksText stacksText = this.cache.computeIfAbsent(stack, key -> formatAmount(key, amount));
if (!stacksText.stacks.isEmpty()) {
drawAmountText(stacksText.stacks, rect, stacksText.color, Alignment.TopLeft);
}
if (!stacksText.remainder.isEmpty()) {
drawAmountText(stacksText.remainder, rect, stacksText.color, Alignment.BottomRight);
}
}
private StacksText formatAmount(ItemStack stack, long amount) {
final long stackSize = StackInfo.isFluidDisplayItem(stack) ? FLUID_STACK_SIZE
: Math.max(1, stack.getMaxStackSize());
final long stacks = amount / stackSize;
final long remainder = amount % stackSize;
final String prefix = getPrefix(stack);
return new StacksText(
stacks > 0 ? prefix + "x" + ReadableNumberConverter.INSTANCE.toWideReadableForm(stacks) : "",
remainder > 0
? (stacks > 0 ? "+" : prefix)
+ ReadableNumberConverter.INSTANCE.toWideReadableForm(remainder)
: "",
getColor(stack));
}
protected String getPrefix(ItemStack stack) {
return "";
}
protected int getColor(ItemStack stack) {
return 0xFFFFFF;
}
}
protected static final int SLOT_SIZE = 18;
protected static final int ICON_SIZE = 16;
protected static final int ICON_OFFSET = 1;
protected static final int MAX_COLUMNS = 11;
protected static final int MARGIN_TOP = 2;
protected static final int LABEL_MARGIN = 15;
protected static final int NAME_MARGIN = 2;
protected static final int DEFAULT_MAX_ROWS = 5;
protected static final int FLUID_STACK_SIZE = 144;
protected final List<ItemStack> items;
protected final List<Long> amounts = new ArrayList<>();
protected final List<String> names = new ArrayList<>();
protected AmountRenderer amountRenderer;
protected String label;
protected EnumChatFormatting labelColor = EnumChatFormatting.GRAY;
protected int activeStackIndex = -1;
protected Dimension size = new Dimension();
protected int maxNamedRows = Integer.MAX_VALUE;
protected int length = 0;
protected int rows = 0;
protected int nameRows = 0;
protected int gridColumns = 0;
protected int gridCount = 0;
public static ItemsTooltipLineHandler of(String label, List<ItemStack> items, int maxRows, int maxNamedRows) {
return new ItemsTooltipLineHandler(label, items, true, maxRows, maxNamedRows);
}
public ItemsTooltipLineHandler(String label, List<ItemStack> items) {
this(label, items, true, DEFAULT_MAX_ROWS, 0);
}
public ItemsTooltipLineHandler(String label, List<ItemStack> items, boolean saveStackSize, int maxRows) {
this(label, items, saveStackSize, maxRows, 0);
}
protected ItemsTooltipLineHandler(String label, List<ItemStack> items, boolean saveStackSize, int maxRows,
int maxNamedRows) {
this.label = label;
this.items = groupingItemStacks(items);
this.amountRenderer = saveStackSize ? new TotalAmountRenderer() : new NoAmountRenderer();
this.length = this.items.size();
this.maxNamedRows = maxNamedRows;
setMaxRows(maxRows);
}
protected static int headerHeight() {
return fontRenderer.FONT_HEIGHT + 2 + MARGIN_TOP;
}
public ItemsTooltipLineHandler setMaxRows(int maxRows) {
if (this.length == 0) {
return this;
}
this.names.clear();
this.nameRows = 0;
if (!layoutWithNames(maxRows)) {
layoutAsGrid(maxRows);
}
this.size.height = this.rows * SLOT_SIZE + headerHeight();
return this;
}
public int getRows() {
return this.rows;
}
protected boolean layoutWithNames(int maxRows) {
final int maxNames = Math.min(this.maxNamedRows, this.length <= maxRows ? this.length : maxRows - 1);
final int[] lineWidth = new int[maxNames + 1];
lineWidth[0] = labelWidth();
for (int index = 0; index < maxNames; index++) {
final String name = this.items.get(index).getDisplayName();
this.names.add(name);
lineWidth[index + 1] = Math
.max(lineWidth[index], SLOT_SIZE + NAME_MARGIN + fontRenderer.getStringWidth(name));
}
for (int named = maxNames; named >= 0; named--) {
final int rest = this.length - named;
final int width = Math.max(lineWidth[named], Math.min(rest, MAX_COLUMNS) * SLOT_SIZE);
final int columns = Math.max(1, width / SLOT_SIZE);
final int restRows = (int) Math.ceil((float) rest / columns);
if (named + restRows <= maxRows) {
this.nameRows = named;
this.gridColumns = columns;
this.gridCount = rest;
this.rows = named + restRows;
this.size.width = width;
return true;
}
}
this.names.clear();
return false;
}
protected void layoutAsGrid(int maxRows) {
this.gridColumns = Math.min(MAX_COLUMNS, this.length);
this.rows = Math.min(maxRows, (int) Math.ceil((float) this.length / this.gridColumns));
this.gridCount = Math.min(this.length, this.gridColumns * this.rows);
this.size.width = Math.max(this.gridColumns * SLOT_SIZE, labelWidth());
if (this.gridCount < this.length) {
this.gridCount -= reservedColumns(this.length - this.gridCount);
}
}
protected int labelWidth() {
return fontRenderer.getStringWidth(this.label) + LABEL_MARGIN;
}
protected int reservedColumns(int hidden) {
return (int) Math.ceil((float) (fontRenderer.getStringWidth("+" + hidden) - 2) / SLOT_SIZE);
}
public ItemsTooltipLineHandler setAmountRenderer(AmountRenderer amountRenderer) {
this.amountRenderer = amountRenderer;
return this;
}
public void setActiveStack(ItemStack activeStack) {
@@ -79,6 +289,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();
}
@@ -94,7 +308,7 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler {
public void setLabel(String label) {
this.label = label;
this.size.width = Math.max(this.columns * SLOT_SIZE, fontRenderer.getStringWidth(this.label) + 15);
this.size.width = Math.max(this.size.width, labelWidth());
}
@Override
@@ -115,63 +329,79 @@ public class ItemsTooltipLineHandler implements ITooltipLineHandler {
final int zTranslation = GuiContainerManager.TOOLTIP_Z_OFFSET;
GL11.glTranslatef(xTranslation, yTranslation, zTranslation);
int indexShift = 0;
final int gridShift = gridIndexShift();
if (this.activeStackIndex != -1) {
indexShift = Math.max(0, Math.min(this.items.size() - this.count, this.activeStackIndex - this.count + 2));
for (int index = 0; index < this.nameRows; index++) {
drawSlot(0, index * SLOT_SIZE, index);
}
for (int index = 0; index < this.count && index + indexShift < this.items.size(); index++) {
ItemStack drawStack = this.items.get(index + indexShift);
int col = index % this.columns;
int row = index / this.columns;
for (int index = 0; index < this.gridCount; index++) {
drawSlot(
(index % this.gridColumns) * SLOT_SIZE,
(this.nameRows + index / this.gridColumns) * SLOT_SIZE,
this.nameRows + gridShift + index);
}
String stackSize = !this.saveStackSize || drawStack.stackSize == 0 ? ""
: ReadableNumberConverter.INSTANCE.toWideReadableForm(drawStack.stackSize);
NEIClientUtils.gl2DRenderContext(() -> {
final int textShift = ICON_OFFSET + Math.round((ICON_SIZE - fontRenderer.FONT_HEIGHT) / 2f);
if (this.activeStackIndex == index + indexShift) {
NEIClientUtils.gl2DRenderContext(
() -> GuiDraw.drawRect(col * SLOT_SIZE - 1, row * SLOT_SIZE - 1, 18, 18, 0x66555555));
for (int index = 0; index < this.nameRows; index++) {
fontRenderer.drawStringWithShadow(
EnumChatFormatting.GRAY + this.names.get(index),
SLOT_SIZE + NAME_MARGIN,
index * SLOT_SIZE + textShift,
0xFFFFFF);
}
drawItem(col * SLOT_SIZE, row * SLOT_SIZE, drawStack, stackSize);
}
final int hidden = this.length - this.nameRows - this.gridCount;
if (this.count < this.items.size()) {
final String text = "+" + (this.items.size() - this.count);
NEIClientUtils.gl2DRenderContext(() -> {
if (hidden > 0) {
final String text = "+" + hidden;
fontRenderer.drawStringWithShadow(
text,
MAX_COLUMNS * SLOT_SIZE - fontRenderer.getStringWidth(text) - 2,
(this.rows - 1) * SLOT_SIZE + (SLOT_SIZE - fontRenderer.FONT_HEIGHT) / 2,
0xee555555);
});
}
EnumChatFormatting.GRAY + text,
this.size.width - fontRenderer.getStringWidth(text) - 2,
(this.rows - 1) * SLOT_SIZE + textShift,
0xFFFFFF);
}
});
GL11.glTranslatef(-xTranslation, -yTranslation, -zTranslation);
GL11.glPopAttrib();
}
protected void drawItem(int x, int y, ItemStack drawStack, String stackSize) {
GuiContainerManager.drawItem(x, y, drawStack, true, stackSize);
protected int gridIndexShift() {
if (this.nameRows > 0 || this.activeStackIndex == -1) {
return 0;
}
return Math.max(0, Math.min(this.length - this.gridCount, this.activeStackIndex - this.gridCount + 2));
}
protected void drawSlot(int x, int y, int index) {
if (this.activeStackIndex == index) {
NEIClientUtils.gl2DRenderContext(() -> GuiDraw.drawRect(x, y, SLOT_SIZE, SLOT_SIZE, 0x66555555));
}
drawStackWithAmount(x, y, index);
}
protected void drawStackWithAmount(int x, int y, int index) {
this.amountRenderer.draw(x + ICON_OFFSET, y + ICON_OFFSET, this.items.get(index), this.amounts.get(index));
}
private List<ItemStack> groupingItemStacks(List<ItemStack> items) {
final List<ItemStack> result = new ArrayList<>();
for (Map.Entry<NBTTagCompound, Long> entry : ItemStackAmount.of(items).entrySet()) {
final ItemStack stack = StackInfo.loadFromNBT(entry.getKey(), Math.max(0, entry.getValue()));
if (stack != null && StackInfo.isFluidDisplayItem(stack)) {
stack.stackSize = 0;
}
result.add(stack);
result.add(StackInfo.loadFromNBT(entry.getKey(), 0));
this.amounts.add(Math.max(0, entry.getValue()));
}
if (result.isEmpty()) {
result.addAll(items);
for (ItemStack stack : items) {
result.add(stack);
this.amounts.add(0L);
}
}
return result;
@@ -803,6 +803,8 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
if (!isEnabled()) return;
AutoFocusWidget.instance.guiTick();
for (Widget widget : drawWidgets) widget.update();
}
@@ -61,6 +61,9 @@ public class NEICPH implements IClientPacketHandler {
case S2C.CACHE_BOOKMARK_DATA:
handleCacheBookmarkData(packet);
break;
case S2C.SYNC:
ServerSync.onReply(packet.readInt());
break;
}
}
@@ -200,6 +200,11 @@ public class NEIClientConfig {
tag.getTag("inventory.bookmarks.showRecipeMarkerMode").setComment("Show Recipe Marker").getIntValue(0);
API.addOption(new OptionCycled("inventory.bookmarks.showRecipeMarkerMode", 3, true));
tag.getTag("inventory.bookmarks.maxVerticalTooltipRows")
.setComment("Max rows in vertical crafting chain tooltip lists (0 - disable, always use grid)")
.getIntValue(12);
API.addOption(new OptionIntegerField("inventory.bookmarks.maxVerticalTooltipRows", 0, 50));
tag.getTag("inventory.bookmarks.recipeMarkerColor").setComment("Color of the icon marker")
.getHexValue(0xADADAD);
API.addOption(
@@ -212,9 +217,6 @@ public class NEIClientConfig {
.getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.bookmarks.showRecipeHandlerIcon", true));
tag.getTag("inventory.bookmarks.recipeChainDir").getIntValue(1);
API.addOption(new OptionCycled("inventory.bookmarks.recipeChainDir", 2, true));
tag.getTag("inventory.bookmarks.ignorePotionOverlap").setComment("Ignore overlap with potion effect HUD")
.getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.bookmarks.ignorePotionOverlap", true));
@@ -227,6 +229,10 @@ public class NEIClientConfig {
.getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.bookmarks.bookmarkRecipeWithCount", true));
tag.getTag("inventory.bookmarks.craftingTreeZoomWithoutModifier")
.setComment("Zoom Crafting Tree with mouse wheel without holding Ctrl").getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.bookmarks.craftingTreeZoomWithoutModifier", true));
tag.getTag("inventory.guirecipe.jeiStyleTabs").setComment("Enable/disable JEI Style Tabs")
.getBooleanValue(true);
API.addOption(new OptionToggleButtonBoubs("inventory.guirecipe.jeiStyleTabs", true));
@@ -266,6 +272,11 @@ public class NEIClientConfig {
.getBooleanValue(true);
API.addOption(new OptionToggleButton("inventory.guirecipe.scrollPages", true));
tag.getTag("inventory.guirecipe.freezeCycleOnHover")
.setComment("Stop cycling the recipe while hovering over an ingredient or a button")
.getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.guirecipe.freezeCycleOnHover", true));
tag.getTag("inventory.guirecipe.profile").getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.guirecipe.profile", true));
@@ -371,6 +382,11 @@ public class NEIClientConfig {
tag.getTag("inventory.itemzoom.onlySolid").getBooleanValue(false);
API.addOption(new OptionToggleButton("inventory.itemzoom.onlySolid", true));
tag.getTag("inventory.itemzoom.preferredSide")
.setComment("Which side to prefer showing the zoomed item on, when both sides are free.")
.getIntValue(0);
API.addOption(new OptionCycled("inventory.itemzoom.preferredSide", 3, true));
tag.getTag("inventory.itemzoom.helpText")
.setComment("Display name \"Item Zoom\" and the hotkey to toggle this mod below the zoomed item.")
.getBooleanValue(true);
@@ -873,6 +889,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);
@@ -1104,6 +1121,10 @@ public class NEIClientConfig {
return getIntSetting("inventory.bookmarks.showRecipeMarkerMode");
}
public static int getMaxVerticalTooltipRows() {
return getIntSetting("inventory.bookmarks.maxVerticalTooltipRows");
}
public static boolean showItemQuantityWidget() {
return getBooleanSetting("inventory.showItemQuantityWidget");
}
@@ -1152,8 +1173,8 @@ public class NEIClientConfig {
return getBooleanSetting("inventory.bookmarks.ignorePotionOverlap");
}
public static int recipeChainDir() {
return getIntSetting("inventory.bookmarks.recipeChainDir");
public static boolean craftingTreeZoomWithoutModifier() {
return getBooleanSetting("inventory.bookmarks.craftingTreeZoomWithoutModifier");
}
public static boolean optimizeGuiOverlapComputation() {
@@ -1164,6 +1185,10 @@ public class NEIClientConfig {
return getBooleanSetting("inventory.guirecipe.cycledIngredientsTooltip");
}
public static boolean freezeCycleOnHover() {
return getBooleanSetting("inventory.guirecipe.freezeCycleOnHover");
}
public static boolean requireShiftForOverlayRecipe() {
return getBooleanSetting("inventory.guirecipe.shiftOverlayRecipe");
}
@@ -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.isFluidDisplayItem(stack) ? 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);
});
}
@@ -169,7 +169,7 @@ public class NEIController implements IContainerSlotClickHandler, IContainerInpu
if (GuiInfo.hasCustomSlots(gui)) return false;
if (NEIClientUtils.shiftKey()) {
if (NEIClientUtils.shiftKey() && button != 2) { // MMB is used by other mods when an itemstack is picked.
// fix small mouse movement while clicking causing slotIndex to be -999
if (slotIndex == -999) {
final Point mouse = GuiDraw.getMousePosition();
@@ -14,11 +14,13 @@ import com.google.common.eventbus.Subscribe;
import codechicken.core.CommonUtils;
import codechicken.core.launch.CodeChickenCorePlugin;
import codechicken.nei.api.API;
import codechicken.nei.api.IConfigureNEI;
import codechicken.nei.asm.NEICorePlugin;
import codechicken.nei.config.IMCHandler;
import codechicken.nei.guihook.HideousLinkedList;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.stackinfo.MaterialLibStackStringifyHandler;
import cpw.mods.fml.client.FMLFileResourcePack;
import cpw.mods.fml.client.FMLFolderResourcePack;
import cpw.mods.fml.common.DummyModContainer;
@@ -102,6 +104,7 @@ public class NEIModContainer extends DummyModContainer {
gregTech5Loaded = Loader.isModLoaded("gregtech") && !Loader.isModLoaded("gregapi_post");
gtnhLibLoaded = Loader.isModLoaded("gtnhlib");
cubicChunksLoaded = Loader.isModLoaded("cubicchunks");
ServerHandler.preInit();
if (CommonUtils.isClient()) ClientHandler.preInit();
asmDataTable = event.getAsmData();
}
@@ -113,6 +116,10 @@ public class NEIModContainer extends DummyModContainer {
IMCForNEI.IMCSender();
}
ServerHandler.load();
if (Loader.isModLoaded("materiallib")) {
API.registerStackStringifyHandler(new MaterialLibStackStringifyHandler());
}
}
@Subscribe
@@ -92,9 +92,17 @@ public class NEISPH implements IServerPacketHandler {
case C2S.SEND_CHAT_ITEM_LINK:
handleSendChatItemLink(sender, packet);
break;
case C2S.SYNC:
handleSync(sender, packet.readInt());
break;
}
}
private void handleSync(EntityPlayerMP sender, int id) {
sender.sendContainerAndContentsToPlayer(sender.openContainer, sender.openContainer.getInventory());
new PacketCustom(channel, S2C.SYNC).writeInt(id).sendToPlayer(sender);
}
private void handleSendChatItemLink(EntityPlayerMP sender, PacketCustom packet) {
final NBTTagCompound payloadTag = packet.readNBTTagCompound();
NEIServerUtils.sendChatItemLink(sender, payloadTag);
@@ -24,6 +24,7 @@ public final class PacketIDs {
public static final int REQUEST_POTION_GUI = 24;
public static final int SET_DUMMY_SLOT = 25;
public static final int SEND_CHAT_ITEM_LINK = 26;
public static final int SYNC = 27;
}
public static final class S2C {
@@ -38,5 +39,6 @@ public final class PacketIDs {
public static final int SET_CREATIVE_PLUS_MODE = 23;
public static final int OPEN_POTION_GUI = 24;
public static final int CACHE_BOOKMARK_DATA = 25;
public static final int SYNC = 26;
}
}
@@ -4,16 +4,13 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IIcon;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.oredict.OreDictionary;
@@ -21,7 +18,11 @@ import org.lwjgl.opengl.GL11;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.item.FluidDrawer;
import codechicken.nei.item.FluidDrawer.FillDirection;
import codechicken.nei.recipe.Badge;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.StackInfo;
@@ -46,7 +47,8 @@ public class PositionedStack implements Cloneable {
protected boolean permutated = false;
protected String acceptsLabel;
protected List<String> tooltip;
protected Function<ItemStack, List<String>> tooltip;
protected List<Badge> badges;
public PositionedStack(Object object, int x, int y, boolean genPerms) {
@@ -119,11 +121,15 @@ public class PositionedStack implements Cloneable {
}
public void setTooltip(List<String> tooltip) {
this.tooltip = tooltip;
this.tooltip = tooltip == null ? null : item -> tooltip;
}
public void setTooltipProvider(Function<ItemStack, List<String>> provider) {
this.tooltip = provider;
}
public List<String> getTooltip() {
return this.tooltip;
return this.tooltip == null ? null : this.tooltip.apply(this.item);
}
public int getChance() {
@@ -139,7 +145,6 @@ public class PositionedStack implements Cloneable {
PositionedStack pStack = (PositionedStack) super.clone();
pStack.items = Arrays.stream(this.items).map(ItemStack::copy).toArray(ItemStack[]::new);
pStack.item = this.item == null ? null : this.item.copy();
pStack.tooltip = this.tooltip == null ? null : new ArrayList<>(this.tooltip);
pStack.badges = this.badges == null ? null : new ArrayList<>(this.badges);
return pStack;
} catch (CloneNotSupportedException e) {
@@ -158,7 +163,7 @@ public class PositionedStack implements Cloneable {
}
public List<ItemStack> getFilteredPermutations(ItemFilter additionalFilter) {
List<ItemStack> items = Arrays.asList(this.items);
List<ItemStack> items = new ArrayList<>(Arrays.asList(this.items));
items = filteringPermutations(items, item -> !ItemInfo.isHidden(item));
items = filteringPermutations(items, PresetsList.getItemFilter());
@@ -252,27 +257,70 @@ public class PositionedStack implements Cloneable {
public void draw(int mousex, int mousey) {}
}
public static class Fluid extends PositionedStack {
public static class Tank extends PositionedStack {
/** The tank size the fill level is measured against, in mB. 0 means "always render full". */
public int capacity = 0;
protected int capacity = 0;
protected boolean flowingTexture = false;
protected FillDirection fillDirection = FillDirection.AUTO;
protected DrawableResource overlay;
private ItemStack cachedFluidItem;
private FluidStack cachedFluidStack;
public Fluid(Object object, int x, int y, boolean genPerms) {
public Tank(Object object, int x, int y, boolean genPerms) {
super(object, x, y, genPerms);
}
public Fluid(Object object, int x, int y) {
public Tank(Object object, int x, int y) {
this(object, x, y, true);
}
public Fluid(Object object, int x, int y, int width, int height, int capacity) {
public Tank(Object object, int x, int y, int width, int height, int capacity) {
super(object, x, y, false);
this.width = width;
this.height = height;
this.capacity = capacity;
setCapacity(capacity);
}
public int getCapacity() {
return this.capacity;
}
public Tank setCapacity(int capacity) {
this.capacity = Math.max(capacity, 0);
return this;
}
public boolean hasFlowingTexture() {
return this.flowingTexture;
}
public Tank setFlowingTexture(boolean flowingTexture) {
this.flowingTexture = flowingTexture;
return this;
}
public FillDirection getFillDirection() {
return this.fillDirection;
}
public Tank setFillDirection(FillDirection fillDirection) {
this.fillDirection = fillDirection == null ? FillDirection.AUTO : fillDirection;
return this;
}
public DrawableResource getOverlay() {
return this.overlay;
}
public Tank setOverlay(String resourceLocation, int u, int v, int width, int height) {
return setOverlay(new DrawableBuilder(resourceLocation, u, v, width, height).build());
}
public Tank setOverlay(DrawableResource overlay) {
this.overlay = overlay;
return this;
}
protected FluidStack getFluidStack() {
@@ -313,62 +361,24 @@ public class PositionedStack implements Cloneable {
return;
}
final int tankCapacity = this.capacity > 0 ? this.capacity : fluidStack.amount;
int fillHeight = tankCapacity > 0
? (int) ((long) this.height * Math.min(fluidStack.amount, tankCapacity) / tankCapacity)
: 0;
FluidDrawer.drawTank(
this.relx,
this.rely,
this.width,
this.height,
this.capacity,
fluidStack,
this.flowingTexture,
this.fillDirection);
if (fluidStack.amount > 0 && fillHeight <= 0) {
fillHeight = 1;
if (this.overlay != null) {
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_LIGHTING);
this.overlay.draw(this.relx, this.rely);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_BLEND);
}
if (fillHeight > 0) {
drawFluid(this.relx, this.rely, this.width, this.height, fillHeight, fluidStack);
}
}
private static void drawFluid(int x, int y, int width, int height, int fillHeight, FluidStack fluidStack) {
final IIcon icon = fluidStack.getFluid().getIcon(fluidStack);
if (icon == null) {
return;
}
final int color = fluidStack.getFluid().getColor(fluidStack);
final float red = (color >> 16 & 0xFF) / 255F;
final float green = (color >> 8 & 0xFF) / 255F;
final float blue = (color & 0xFF) / 255F;
Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
GL11.glColor4f(red, green, blue, 1F);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_LIGHTING);
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
for (int tx = 0; tx < width; tx += 16) {
final int tileWidth = Math.min(16, width - tx);
final double u2 = icon.getMinU() + (icon.getMaxU() - icon.getMinU()) * tileWidth / 16D;
for (int ty = 0; ty < fillHeight; ty += 16) {
final int tileHeight = Math.min(16, fillHeight - ty);
final double v2 = icon.getMinV() + (icon.getMaxV() - icon.getMinV()) * tileHeight / 16D;
final int bottomY = y + height - ty;
final int topY = bottomY - tileHeight;
tessellator.addVertexWithUV(x + tx, bottomY, 0, icon.getMinU(), v2);
tessellator.addVertexWithUV(x + tx + tileWidth, bottomY, 0, u2, v2);
tessellator.addVertexWithUV(x + tx + tileWidth, topY, 0, u2, icon.getMinV());
tessellator.addVertexWithUV(x + tx, topY, 0, icon.getMinU(), icon.getMinV());
}
}
tessellator.draw();
GL11.glDisable(GL11.GL_BLEND);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glColor4f(1F, 1F, 1F, 1F);
}
}
}
@@ -445,7 +445,7 @@ public class SearchTokenParser {
final char ch = input.charAt(index);
if (!insideQuotes && delimiters.indexOf(ch) >= 0
&& (!space || index == 0 || input.charAt(index - 1) == ' ')) {
&& (!space || index == 0 || input.charAt(index - 1) == ' ' || lastEnd == index)) {
if (lastEnd == index) {
token += String.valueOf(ch);
@@ -10,6 +10,7 @@ import net.minecraftforge.event.world.WorldEvent;
import codechicken.core.CommonUtils;
import codechicken.lib.packet.PacketCustom;
import codechicken.nei.item.ItemFluidDisplay;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent.PlayerChangedDimensionEvent;
@@ -18,6 +19,7 @@ import cpw.mods.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent;
import cpw.mods.fml.common.gameevent.PlayerEvent.PlayerRespawnEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import cpw.mods.fml.common.registry.GameRegistry;
public class ServerHandler {
@@ -31,6 +33,14 @@ public class ServerHandler {
NEIActions.init();
}
public static void preInit() {
registerFluidDisplayItem();
}
private static void registerFluidDisplayItem() {
GameRegistry.registerItem(new ItemFluidDisplay(), "neiFluidDisplay");
}
@SubscribeEvent
public void tickEvent(TickEvent.WorldTickEvent event) {
if (event.phase == Phase.START && !event.world.isRemote
@@ -0,0 +1,36 @@
package codechicken.nei;
import codechicken.lib.packet.PacketCustom;
import codechicken.nei.PacketIDs.C2S;
/**
* Waits until the server has processed everything the client sent before. The server handles slot clicks and NEI
* packets in the order they came and answers after it has sent the slot updates, so once the answer is here the client
* container holds what the server computed.
*/
public final class ServerSync {
private static int lastId;
private static volatile int awaiting;
private ServerSync() {}
public static void request() {
if (!NEIClientConfig.hasSMPCounterPart()) {
awaiting = 0;
return;
}
awaiting = ++lastId;
new PacketCustom(NEICPH.channel, C2S.SYNC).writeInt(awaiting).sendToServer();
}
public static boolean pending() {
return awaiting != 0;
}
static void onReply(int id) {
if (id == awaiting) awaiting = 0;
}
}
@@ -9,6 +9,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -160,7 +161,7 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
// 1 - partitial hidden
// 2 - enabled
public int state = 2;
public int calculatedWidth;
public volatile int calculatedWidth = -1;
public final List<ItemStack> items;
public final List<SubsetTag> children = new ArrayList<>();
protected final SubsetSlot slot = new SubsetSlot();
@@ -206,7 +207,7 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
this.state = 2;
this.items.clear();
this.children.clear();
this.calculatedWidth = 0;
this.calculatedWidth = -1;
}
public void updateVisiblity(int mx, int my) {
@@ -261,6 +262,10 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
}
public void resize(Rectangle4i screen, Rectangle4i parent, boolean dropRight) {
if (calculatedWidth < 0) {
calculatedWidth = this.children.stream().mapToInt(SubsetTag::nameWidth).max().orElse(0);
}
int height = Math.min(slot.contentHeight(), screen.h);
int width = Math.max(calculatedWidth + 2, this.items.isEmpty() ? 0 : 16 + MARGIN * 2);
int scrollbarWidth = slot.scrollbarDim().width;
@@ -537,14 +542,22 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
return;
}
final AtomicInteger resolved = new AtomicInteger();
final AtomicInteger invalid = new AtomicInteger();
Stream.of(dir.listFiles()).filter(file -> !file.isDirectory()).forEach(file -> {
ClientHandler.loadSettingsFile(
"subsets/" + file.getName(),
lines -> parseFile(file.getName(), lines.collect(Collectors.toList())));
lines -> parseFile(file.getName(), lines.collect(Collectors.toList()), resolved, invalid));
});
if (resolved.get() + invalid.get() > 0) {
NEIClientConfig.logger.info("Resolved {} MaterialLib entries ({} invalid)", resolved.get(), invalid.get());
}
}
private static void parseFile(String resource, List<String> itemStrings) {
private static void parseFile(String resource, List<String> itemStrings, AtomicInteger resolved,
AtomicInteger invalid) {
final JsonParser parser = new JsonParser();
final int dotIndex = resource.lastIndexOf('.');
final String subsetNamespace = dotIndex > 0 ? resource.substring(0, dotIndex) : resource;
@@ -574,7 +587,13 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
final NBTBase nbt = NBTJson.toNbt(parser.parse(itemStr));
if (nbt instanceof NBTTagCompound tag) {
((ItemStackSet) processedTag.filter).add(StackInfo.loadFromNBT(tag));
final ItemStack stack = StackInfo.loadFromNBT(tag);
if (tag.getString("strId").startsWith(StackInfo.MATERIALLIB_PREFIX)) {
(stack != null ? resolved : invalid).incrementAndGet();
}
((ItemStackSet) processedTag.filter).add(stack);
} else {
throw new IllegalArgumentException(
"Expected NBTTagCompound but got " + nbt.getClass().getSimpleName());
@@ -686,12 +705,12 @@ public class SubsetWidget extends Button implements ItemFilterProvider {
for (SubsetTag tag : list) {
tag.children.sort(Comparator.comparing(SubsetTag::displayName));
tag.calculatedWidth = tag.children.stream().mapToInt(SubsetTag::nameWidth).max().orElse(0);
tag.calculatedWidth = -1;
}
root.children.removeIf(child -> child.children.isEmpty() && child.items.isEmpty());
root.children.sort(Comparator.comparing(SubsetTag::displayName));
root.calculatedWidth = root.children.stream().mapToInt(SubsetTag::nameWidth).max().orElse(0);
root.calculatedWidth = -1;
calculateVisibility(root);
} catch (Throwable e) {
+16 -2
View File
@@ -12,6 +12,7 @@ import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fluids.Fluid;
import org.lwjgl.input.Keyboard;
@@ -33,6 +34,7 @@ import codechicken.nei.SubsetWidget.SubsetTag;
import codechicken.nei.api.IRecipeFilter.IRecipeFilterProvider;
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
import codechicken.nei.config.Option;
import codechicken.nei.item.FluidDrawer;
import codechicken.nei.recipe.CatalystInfo;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiRecipe;
@@ -146,14 +148,14 @@ public class API {
* Hide an item from the item panel Damage values of OreDictionary.WILDCARD_VALUE and ItemStackMap.WILDCARD_TAG tags
* function as wildcards for their respective variables
*/
public static void hideItem(ItemStack item) {
public static synchronized void hideItem(ItemStack item) {
if (!ItemInfo.hiddenItems.contains(item)) {
ItemInfo.hiddenItems.add(item);
LayoutManager.markItemsDirty();
}
}
public static void hideItem(String rule) {
public static synchronized void hideItem(String rule) {
ItemFilter filter = ItemStackFilterParser.parse(rule);
if (filter != null) {
ItemInfo.hiddenItemsRules.filters.add(filter);
@@ -178,6 +180,8 @@ public class API {
/**
* Add an alias name that this item can be found by in the search field, in addition to its display name.
*
* @param aliases translation keys, localized at search time
*/
public static void setAliases(ItemStack item, String... aliases) {
final List<String> list = Arrays.asList(aliases).stream().filter(Objects::nonNull).map(String::trim)
@@ -375,6 +379,16 @@ public class API {
StackInfo.stackStringifyHandlers.add(handler);
}
/**
* Replaces the default rendering of a fluid in NEI fluid display slots and fluid tanks.
*
* @param fluid the fluid to render
* @param renderer the renderer, or null to restore the default rendering
*/
public static void registerFluidRenderer(Fluid fluid, IFluidRenderer renderer) {
FluidDrawer.registerFluidRenderer(fluid, renderer);
}
/**
* Adds an association between an ingredient and what it can craft. (i.e. Furnace ItemStack -> Smelting and Fuel
* Recipes) Allows players to see what ingredient they need to craft in order to make recipes from a recipe
@@ -0,0 +1,17 @@
package codechicken.nei.api;
import net.minecraftforge.fluids.Fluid;
/**
* Custom renderer for a fluid, used by NEI fluid display slots and fluid tanks in recipes.
* <p>
* Register with {@link API#registerFluidRenderer(Fluid, IFluidRenderer)}.
*/
public interface IFluidRenderer {
/**
* Renders one 16x16 tile of the fluid. Partial tiles of a tank are clipped by NEI. The amount overlay is drawn by
* NEI.
*/
void renderFluid(Fluid fluid, int x, int y);
}
@@ -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;
}
}
@@ -36,6 +36,7 @@ import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.RegistryNamespaced;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.common.IShearable;
import net.minecraftforge.oredict.OreDictionary;
@@ -60,6 +61,7 @@ import codechicken.nei.config.HandlerDumper;
import codechicken.nei.config.ItemPanelDumper;
import codechicken.nei.config.RegistryDumper;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.item.FluidDisplayRenderer;
import codechicken.nei.item.ItemFluidDisplay;
import codechicken.nei.recipe.BrewingRecipeHandler;
import codechicken.nei.recipe.RecipeItemInputHandler;
@@ -134,7 +136,7 @@ public class ItemInfo {
public static void preInit() {
addMobSpawnerItem();
addFluidDisplayItem();
setupFluidDisplayItemClient();
}
public static void load() {
@@ -320,8 +322,9 @@ public class ItemInfo {
GameDataManipulator.replaceItem(Block.getIdFromBlock(Blocks.mob_spawner), new ItemMobSpawner());
}
private static void addFluidDisplayItem() {
GameRegistry.registerItem(new ItemFluidDisplay(), "neiFluidDisplay");
private static void setupFluidDisplayItemClient() {
ItemFluidDisplay.INSTANCE.setCreativeTab(ItemFluidDisplay.tabFluids);
MinecraftForgeClient.registerItemRenderer(ItemFluidDisplay.INSTANCE, new FluidDisplayRenderer());
}
private static void addInfiniteHandlers() {
@@ -19,7 +19,6 @@ import codechicken.nei.BookmarkContainerInfo;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemQuantityField;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
import codechicken.nei.KeyManager;
import codechicken.nei.LayoutManager;
import codechicken.nei.NEIClientConfig;
@@ -32,11 +31,13 @@ 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;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.GuiUsageRecipe;
import codechicken.nei.recipe.HoveredRecipe;
import codechicken.nei.recipe.Recipe;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.RecipeHandlerRef;
@@ -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)) {
@@ -408,41 +415,37 @@ public abstract class ShortcutInputHandler {
private static Recipe getFocusedRecipe(ItemStack stackover, int mousex, int mousey, boolean useFavorites) {
final GuiContainer gui = NEIClientUtils.getGuiContainer();
Recipe recipe = null;
if (useFavorites) {
ItemsGridSlot itemSlot = ItemPanels.itemPanel.getSlotMouseOver(mousex, mousey);
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.historyPanel.getSlotMouseOver(mousex, mousey);
}
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.craftablesPanel.getSlotMouseOver(mousex, mousey);
}
if (itemSlot != null) {
final RecipeId recipeId = itemSlot.getRecipeId();
if (recipeId != null && (recipe = Recipe.of(recipeId)) != null) {
recipe.getIngredients().stream().forEach(ingr -> {
final List<ItemStack> permutations = ingr.getPermutations();
for (int index = 0; index < permutations.size(); index++) {
if (FavoriteRecipes.contains(permutations.get(index))) {
ingr.setActiveIndex(index);
break;
}
}
});
}
if (gui instanceof GuiRecipe<?>guiRecipe) {
final Recipe focusedRecipe = guiRecipe.getFocusedRecipe();
if (focusedRecipe != null) {
return focusedRecipe;
}
}
if (gui instanceof GuiRecipe<?>guiRecipe) {
recipe = guiRecipe.getFocusedRecipe();
if (!useFavorites) {
return null;
}
final HoveredRecipe hovered = HoveredRecipe.of(gui, stackover);
if (hovered == null || hovered.source != HoveredRecipe.Source.ITEM_PANEL) {
return null;
}
final Recipe recipe = Recipe.of(hovered.recipeId);
if (recipe != null) {
recipe.getIngredients().stream().forEach(ingr -> {
final List<ItemStack> permutations = ingr.getPermutations();
for (int index = 0; index < permutations.size(); index++) {
if (FavoriteRecipes.contains(permutations.get(index))) {
ingr.setActiveIndex(index);
break;
}
}
});
}
return recipe;
@@ -477,6 +480,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 +490,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 +505,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),
@@ -515,7 +525,9 @@ public abstract class ShortcutInputHandler {
}
final BookmarksGridSlot slot = ItemPanels.bookmarkPanel.getSlotMouseOver(mousex, mousey);
final RecipeId recipeId = getHotkeyRecipeId(gui, mousex, mousey, stack, slot);
final HoveredRecipe hovered = HoveredRecipe.of(gui, stack);
final RecipeId recipeId = hovered == null || hovered.source == HoveredRecipe.Source.OTHER
|| hovered.source == HoveredRecipe.Source.BOOKMARK && hovered.favorite ? null : hovered.recipeId;
if (slot != null) {
final BookmarkGroup group = slot.getGroup();
@@ -715,36 +727,4 @@ public abstract class ShortcutInputHandler {
}
}
private static RecipeId getHotkeyRecipeId(GuiContainer gui, int mousex, int mousey, ItemStack stack,
BookmarksGridSlot slot) {
if (slot != null) {
return slot.getType() == BookmarkItemType.INGREDIENT ? null : slot.getRecipeId();
}
ItemsGridSlot itemSlot = ItemPanels.itemPanel.getSlotMouseOver(mousex, mousey);
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.historyPanel.getSlotMouseOver(mousex, mousey);
}
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.craftablesPanel.getSlotMouseOver(mousex, mousey);
}
if (itemSlot != null) {
return itemSlot.getRecipeId();
}
if (gui instanceof GuiRecipe<?>guiRecipe) {
final Recipe focusedRecipe = guiRecipe.getFocusedRecipe();
if (focusedRecipe != null) {
return focusedRecipe.getRecipeId();
}
}
return null;
}
}
@@ -14,6 +14,7 @@ import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.NEW;
import java.util.Map;
@@ -22,11 +23,14 @@ import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import codechicken.lib.asm.ASMBlock;
@@ -98,6 +102,48 @@ public class NEITransformer implements IClassTransformer {
}
});
if (Boolean.getBoolean("nei.recipeOrigins")) {
// Replace the plain ArrayList CraftingManager's constructor builds its recipe list with, so every recipe
// added anywhere (including vanilla's own, added in that same constructor) is observed for
// codechicken.nei.recipe.debug.RecipeOrigins. Must be done at classload time, since by the time any mod
// code runs vanilla has already populated the list. Capturing a stack trace per recipe is slow, so this is
// opt-in via -Dnei.recipeOrigins=true.
transformer.add(
new MethodTransformer(
new ObfMapping("net/minecraft/item/crafting/CraftingManager", "<init>", "()V")) {
@Override
public void transform(MethodNode mv) {
ASMHelper.logger.debug("NEI: Hooking CraftingManager recipe list for origin tracking");
TypeInsnNode newInsn = null;
MethodInsnNode initInsn = null;
// only the first ArrayList construction, the recipe list field
for (AbstractInsnNode insn : mv.instructions.toArray()) {
if (newInsn == null) {
if (insn.getOpcode() == NEW
&& "java/util/ArrayList".equals(((TypeInsnNode) insn).desc)) {
newInsn = (TypeInsnNode) insn;
}
} else if (insn.getOpcode() == INVOKESPECIAL
&& "java/util/ArrayList".equals(((MethodInsnNode) insn).owner)
&& "<init>".equals(((MethodInsnNode) insn).name)) {
initInsn = (MethodInsnNode) insn;
break;
}
}
if (newInsn == null || initInsn == null) {
ASMHelper.logger.warn("NEI: CraftingManager recipe list not found, origins disabled");
return;
}
newInsn.desc = initInsn.owner = "codechicken/nei/recipe/debug/RecipeOrigins$TrackingList";
}
});
}
// put glint alpha into the buffer correctly for exporting
transformer.add(
new MethodReplacer(
@@ -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() {
@@ -448,8 +452,9 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
if (!this.isEmpty() && this.gridGenerator.maxAbsoluteSlotIndex > 0) {
final GroupingItem groupingItem = ItemPanels.bookmarkPanel.groupingItem;
final int sortableGroupId = ItemPanels.bookmarkPanel.sortableGroup != null
? ItemPanels.bookmarkPanel.sortableGroup.groupId
: -3;
&& ItemPanels.bookmarkPanel.sortableGroup.grid == this
? ItemPanels.bookmarkPanel.sortableGroup.groupId
: -3;
final int rowShift = this.page * this.rows;
Map<Integer, Integer> groups = this.gridGenerator.rowToGroupId;
@@ -791,10 +796,10 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
this.bookmarkItems.add(item);
}
sortableGroup.grid = this;
sortableGroup.groupId = groupId;
sortableGroup.update(this, groupId);
this.onItemsChanged();
this.getMask();// force generate grid
overGroupId = getRowGroupId(overRowIndex);
}
if (sortableGroup.groupId == overGroupId) {
@@ -950,7 +955,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 +1219,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 +1232,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();
@@ -1272,7 +1285,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
for (BookmarksGridSlot slot : getMask()) {
Rectangle4i rect = getSlotRect(slot.slotIndex);
if (sortableGroup != null && slot.getGroupId() == sortableGroup.groupId) {
if (sortableGroup != null && sortableGroup.matches(this, slot.getGroupId())) {
NEIClientUtils.drawRect(rect.x, rect.y, rect.w, rect.h, DRAG_COLOR);
} else if (sortableItem != null && sortableItem.items.contains(slot.itemIndex)) {
NEIClientUtils.drawRect(rect.x, rect.y, rect.w, rect.h, DRAG_COLOR);
@@ -1301,7 +1314,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
final SortableItem sortableItem = LayoutManager.bookmarkPanel.sortableItem;
for (BookmarksGridSlot slot : getMask()) {
if ((sortableGroup == null || slot.getGroupId() != sortableGroup.groupId)
if ((sortableGroup == null || !sortableGroup.matches(this, slot.getGroupId()))
&& (sortableItem == null || !sortableItem.items.contains(slot.itemIndex))) {
slot.afterDraw(getSlotRect(slot.slotIndex), mouseContext);
}
@@ -1318,7 +1331,7 @@ public class BookmarkGrid extends ItemsGrid<BookmarksGridSlot, BookmarkGrid.Book
for (BookmarksGridSlot slot : getMask()) {
if ((sortableGroup == null || slot.getGroupId() != sortableGroup.groupId)
if ((sortableGroup == null || !sortableGroup.matches(this, slot.getGroupId()))
&& (sortableItem == null || !sortableItem.items.contains(slot.itemIndex))) {
final BookmarkItem item = slot.getBookmarkItem();
@@ -340,7 +340,8 @@ public class BookmarkGridGenerator {
0,
chainItem.getItem(),
null,
group);
group,
this.grid);
} else {
return new BookmarksGridSlot(
slotIndex,
@@ -350,7 +351,8 @@ public class BookmarkGridGenerator {
chainItem.getCalculatedMultiplier(),
chainItem.getItem(),
chainItem.calculatedType,
group);
group,
this.grid);
}
}
@@ -42,7 +42,7 @@ public class BookmarkItem {
this.groupId = groupId;
this.stack = stack;
this.factor = StackInfo.isFluidDisplayItem(stack) ? Math.min(144, nbTag.getInteger("Count")) : 1;
this.factor = StackInfo.isFluidDisplayItem(stack) ? Math.max(nbTag.getInteger("Count"), 1) : 1;
}
public Builder factor(long factor) {
@@ -144,7 +144,7 @@ public class BookmarkItem {
private static synchronized String getItemGUID(ItemStack stack) {
final FluidStack fluidStack = StackInfo.getFluid(stack);
if (fluidStack != null) {
if (fluidStack != null && (fluidStack.amount > 0 || !StackInfo.isFluidContainer(stack))) {
return fluidStack.getFluid().getName() + ":" + fluidStack.tag;
} else {
@@ -23,6 +23,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
import codechicken.nei.ItemPanels;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.util.NBTJson;
@@ -79,10 +80,21 @@ public class BookmarkStorage {
public int getNamespaceSize() {
if (this.namespaces.get(this.namespaces.size() - 1).size() > 0) {
this.namespaces.add(new BookmarkGrid());
} else if (this.activeNamespaceIndex == this.namespaces.size() - 2 && getActiveGrid().size() == 0) {
this.namespaces.remove(this.namespaces.size() - 1);
final int lastIndex = this.namespaces.size() - 1;
final BookmarkGrid lastGrid = this.namespaces.get(lastIndex);
final SortableGroup sortableGroup = ItemPanels.bookmarkPanel.sortableGroup;
if (lastGrid.size() > 0) {
if (sortableGroup == null || sortableGroup.grid != lastGrid || !sortableGroup.aloneInGrid) {
this.namespaces.add(new BookmarkGrid());
}
} else if (lastIndex > 0 && this.activeNamespaceIndex != lastIndex) {
final BookmarkGrid prevGrid = this.namespaces.get(lastIndex - 1);
if (sortableGroup != null && sortableGroup.grid == prevGrid && sortableGroup.aloneInGrid
|| prevGrid == getActiveGrid() && prevGrid.size() == 0) {
this.namespaces.remove(lastIndex);
}
}
return this.namespaces.size();
@@ -198,6 +210,11 @@ public class BookmarkStorage {
final BookmarkItem item = BookmarkItem.of(parser.parse(itemStr).getAsJsonObject());
if (item != null) {
if (!grid.groups.containsKey(item.groupId)) {
item.groupId = BookmarkGrid.DEFAULT_GROUP_ID;
}
grid.addItem(item, false);
} else {
wasError = true;
@@ -8,7 +8,6 @@ import org.lwjgl.opengl.GL11;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
import codechicken.nei.ItemsGrid.MouseContext;
import codechicken.nei.NEIClientConfig;
@@ -65,7 +64,8 @@ public class BookmarksGridSlot extends ItemsGridSlot {
protected final int rowIndex;
public BookmarksGridSlot(int slotIndex, int itemIndex, long realMultiplier, long shiftAmount,
long calculatedMultiplier, BookmarkItem bookmarkItem, CalculatedType calculatedType, BookmarkGroup group) {
long calculatedMultiplier, BookmarkItem bookmarkItem, CalculatedType calculatedType, BookmarkGroup group,
BookmarkGrid grid) {
super(
slotIndex,
itemIndex,
@@ -96,7 +96,6 @@ public class BookmarksGridSlot extends ItemsGridSlot {
: CalculatedType.RESULT;
}
final BookmarkGrid grid = ItemPanels.bookmarkPanel.getGrid();
final BookmarkItem prevItem = grid.getCalculatedItem(
grid.gridGenerator.slotToItem.getOrDefault(grid.gridGenerator.itemToSlot.get(this.itemIndex) - 1, -1));
this.isFirstOutput = this.itemIndex >= 0 && this.bookmarkItem.type == BookmarkItemType.RESULT
@@ -13,13 +13,23 @@ public class SortableGroup {
public int groupId;
public BookmarkGrid grid;
public boolean aloneInGrid;
public int shiftX = 0;
public int shiftY = 0;
public SortableGroup(BookmarkGrid grid, int groupId) {
update(grid, groupId);
}
public void update(BookmarkGrid grid, int groupId) {
this.grid = grid;
this.groupId = groupId;
this.aloneInGrid = grid.bookmarkItems.stream().allMatch(item -> item.groupId == groupId);
}
public boolean matches(BookmarkGrid grid, int groupId) {
return this.grid == grid && this.groupId == groupId;
}
public void postDraw(int mousex, int mousey) {
@@ -0,0 +1,131 @@
package codechicken.nei.bookmark.tree;
import java.awt.geom.Point2D;
import org.lwjgl.input.Mouse;
class Camera {
private static final float ZOOM_MIN = 0.1f;
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(Point2D mouse) {
this.dragging = true;
this.lastDragX = (float) mouse.getX();
this.lastDragY = (float) mouse.getY();
}
public void drag(Point2D mouse) {
if (!this.dragging) {
return;
}
final float mouseX = (float) mouse.getX();
final float mouseY = (float) mouse.getY();
this.scrollX -= (mouseX - this.lastDragX) / this.zoomLevel;
this.scrollY -= (mouseY - this.lastDragY) / this.zoomLevel;
this.lastDragX = mouseX;
this.lastDragY = mouseY;
}
public void update(Point2D mouse, int treeWidth, int treeHeight) {
if (this.dragging && Mouse.isButtonDown(0)) {
drag(mouse);
}
clampToBounds(treeWidth, treeHeight);
}
public void endDrag() {
this.dragging = false;
}
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,724 @@
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.ColorUtils;
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(ColorUtils.craftingTreeLine.getColor(), 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;
public CraftingTreeCanvas(Camera camera) {
this.camera = 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(Point2D mouse) {
this.camera.startDrag(mouse);
}
public void drag(Point2D mouse) {
this.camera.drag(mouse);
}
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.update(mouse, 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,119 @@
package codechicken.nei.bookmark.tree;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.bookmark.BookmarkGroup;
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 Map<BookmarkGroup, CraftingTreeState> GROUP_STATES = new WeakHashMap<>();
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 boolean initialized;
public final Camera camera = new Camera();
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;
this.searchItemResult = null;
this.searchStatsResult = null;
this.camera.endDrag();
}
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 static CraftingTreeState of(BookmarkGroup group) {
return GROUP_STATES.computeIfAbsent(group, g -> new CraftingTreeState());
}
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.isFluidDisplayItem(this.emptyStack);
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,755 @@
package codechicken.nei.bookmark.tree;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
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.isFluidDisplayItem(stack);
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);
}
public ItemStack getStack() {
return StackInfo.withAmount(this.stack, stackSize);
}
@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, 10.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).setSlices(0, 0, 2, 0).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())
.setTrackTexture(new DrawableBuilder("nei:textures/gui/craftingtree.png", 26, 104, 24, 4).build())
.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.recipeId == null ? slot.getStack() : 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);
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<>();
final Set<RecipeId> countedRecipes = new HashSet<>();
for (BookmarkItem item : math.recipeResults) {
// a recipe adds one result item per output, count it only once
if (!countedRecipes.add(item.recipeId)) {
continue;
}
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) {}
}
@@ -74,7 +74,11 @@ public class IMCHandler {
tag.getInteger("imageX"),
tag.getInteger("imageY"),
tag.getInteger("imageWidth"),
tag.getInteger("imageHeight"));
tag.getInteger("imageHeight"),
tag.hasKey("imageTextureWidth") ? tag.getInteger("imageTextureWidth")
: HandlerInfo.DEFAULT_IMAGE_TEXTURE_SIZE,
tag.hasKey("imageTextureHeight") ? tag.getInteger("imageTextureHeight")
: HandlerInfo.DEFAULT_IMAGE_TEXTURE_SIZE);
}
if (!info.hasImageOrItem()) {
final String itemName = tag.getString("itemName");
@@ -15,13 +15,21 @@ public class DrawableBuilder {
private int paddingBottom = 0;
private int paddingLeft = 0;
private int paddingRight = 0;
private int sliceLeft = 0;
private int sliceRight = 0;
private int sliceTop = 0;
private int sliceBottom = 0;
public DrawableBuilder(String resourceLocation, int u, int v, int width, int height) {
this(new ResourceLocation(resourceLocation), u, v, width, height);
}
public DrawableBuilder(ResourceLocation resource, int u, int v, int width, int height) {
this.u = u;
this.v = v;
this.width = width;
this.height = height;
this.resourceLocation = new ResourceLocation(resourceLocation);
this.resourceLocation = resource;
}
public DrawableBuilder setTextureSize(int width, int height) {
@@ -38,6 +46,14 @@ public class DrawableBuilder {
return this;
}
public DrawableBuilder setSlices(int sliceLeft, int sliceRight, int sliceTop, int sliceBottom) {
this.sliceLeft = sliceLeft;
this.sliceRight = sliceRight;
this.sliceTop = sliceTop;
this.sliceBottom = sliceBottom;
return this;
}
public DrawableBuilder trim(int trimTop, int trimBottom, int trimLeft, int trimRight) {
this.u += trimLeft;
this.v += trimTop;
@@ -58,6 +74,10 @@ public class DrawableBuilder {
paddingLeft,
paddingRight,
textureWidth,
textureHeight);
textureHeight,
sliceLeft,
sliceRight,
sliceTop,
sliceBottom);
}
}
@@ -1,7 +1,7 @@
package codechicken.nei.drawable;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
@@ -19,9 +19,42 @@ public class DrawableResource extends Image {
private final int paddingLeft;
private final int paddingRight;
private final int sliceLeft;
private final int sliceRight;
private final int sliceTop;
private final int sliceBottom;
public DrawableResource(ResourceLocation resourceLocation, int u, int v, int width, int height, int paddingTop,
int paddingBottom, int paddingLeft, int paddingRight, int textureWidth, int textureHeight) {
this(
resourceLocation,
u,
v,
width,
height,
paddingTop,
paddingBottom,
paddingLeft,
paddingRight,
textureWidth,
textureHeight,
0,
0,
0,
0);
}
// negative padding lets the texture overflow the box it is drawn into
public DrawableResource(ResourceLocation resourceLocation, int u, int v, int width, int height, int paddingTop,
int paddingBottom, int paddingLeft, int paddingRight, int textureWidth, int textureHeight, int sliceLeft,
int sliceRight, int sliceTop, int sliceBottom) {
super(u, v, width, height);
this.sliceLeft = sliceLeft;
this.sliceRight = sliceRight;
this.sliceTop = sliceTop;
this.sliceBottom = sliceBottom;
this.resourceLocation = resourceLocation;
this.textureWidth = textureWidth;
this.textureHeight = textureHeight;
@@ -40,6 +73,18 @@ public class DrawableResource extends Image {
return height + paddingTop + paddingBottom;
}
public int getTextureWidth() {
return this.textureWidth;
}
public int getTextureHeight() {
return this.textureHeight;
}
public void bindTexture() {
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
}
public void draw(int xOffset, int yOffset) {
draw(xOffset, yOffset, 0, 0, 0, 0);
}
@@ -50,19 +95,29 @@ public class DrawableResource extends Image {
final int height = this.height - maskBottom - maskTop;
GL11.glEnable(GL11.GL_TEXTURE_2D);
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
drawModalRectWithCustomSizedTexture(xOffset + maskLeft, yOffset + maskTop, maskLeft, maskTop, width, height);
bindTexture();
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
addQuad(tessellator, xOffset + maskLeft, yOffset + maskTop, maskLeft, maskTop, width, height);
tessellator.draw();
if (!is2DTexture) GL11.glDisable(GL11.GL_TEXTURE_2D);
}
public void draw(int xOffset, int yOffset, int width, int height, int sliceLeft, int sliceRight, int sliceTop,
int sliceBottom) {
final int textureWidth = getWidth();
final int textureHeight = getHeight();
final int middleWidth = textureWidth - sliceLeft - sliceRight;
final int middleHeight = textureHeight - sliceTop - sliceBottom;
final int tileWidth = width - sliceLeft - sliceRight;
final int tileHeight = height - sliceTop - sliceBottom;
public void draw(int xOffset, int yOffset, int width, int height) {
if (width == getWidth() && height == getHeight()) {
draw(xOffset, yOffset);
return;
}
final int sliceLeft = this.sliceLeft;
final int sliceRight = this.sliceRight;
final int sliceTop = this.sliceTop;
final int sliceBottom = this.sliceBottom;
final int middleWidth = this.width - sliceLeft - sliceRight;
final int middleHeight = this.height - sliceTop - sliceBottom;
final int tileWidth = width - this.paddingLeft - this.paddingRight - sliceLeft - sliceRight;
final int tileHeight = height - this.paddingTop - this.paddingBottom - sliceTop - sliceBottom;
if (middleWidth > 0 && middleHeight > 0 && tileWidth > 0 && tileHeight > 0) {
final boolean is2DTexture = GL11.glGetBoolean(GL11.GL_TEXTURE_2D);
@@ -72,16 +127,32 @@ public class DrawableResource extends Image {
final int remainderHeight = tileHeight - (tileHeightCount * middleHeight);
GL11.glEnable(GL11.GL_TEXTURE_2D);
Minecraft.getMinecraft().getTextureManager().bindTexture(this.resourceLocation);
bindTexture();
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
// middle parts
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
final int tileWidthSize = (tileW == tileWidthCount) ? remainderWidth : middleWidth;
final int x = xOffset + sliceLeft + tileW * middleWidth;
// border top
addQuad(tessellator, x, yOffset, sliceLeft, 0, tileWidthSize, sliceTop);
// border bottom
addQuad(
tessellator,
x,
yOffset + sliceTop + tileHeight,
sliceLeft,
sliceTop + middleHeight,
tileWidthSize,
sliceBottom);
// middle parts
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
drawModalRectWithCustomSizedTexture(
addQuad(
tessellator,
x,
yOffset + sliceTop + tileH * middleHeight,
sliceLeft,
@@ -91,114 +162,75 @@ public class DrawableResource extends Image {
}
}
// border top
if (sliceTop > 0) {
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
final int tileWidthSize = (tileW == tileWidthCount) ? remainderWidth : middleWidth;
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileW * middleWidth,
yOffset,
sliceLeft,
0,
tileWidthSize,
sliceTop);
}
}
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
final int y = yOffset + sliceTop + tileH * middleHeight;
// border bottom
if (sliceBottom > 0) {
for (int tileW = 0; tileW <= tileWidthCount; tileW++) {
final int tileWidthSize = (tileW == tileWidthCount) ? remainderWidth : middleWidth;
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileW * middleWidth,
yOffset + sliceTop + tileHeight,
sliceLeft,
sliceTop + middleHeight,
tileWidthSize,
sliceBottom);
}
}
// border left
if (sliceLeft > 0) {
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
drawModalRectWithCustomSizedTexture(
xOffset,
yOffset + sliceTop + tileH * middleHeight,
0,
sliceTop,
sliceLeft,
tileHeightSize);
}
}
// border right
if (sliceRight > 0) {
for (int tileH = 0; tileH <= tileHeightCount; tileH++) {
final int tileHeightSize = (tileH == tileHeightCount) ? remainderHeight : middleHeight;
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileWidth,
yOffset + sliceTop + tileH * middleHeight,
sliceLeft + middleWidth,
sliceTop,
sliceRight,
tileHeightSize);
}
// border left
addQuad(tessellator, xOffset, y, 0, sliceTop, sliceLeft, tileHeightSize);
// border right
addQuad(
tessellator,
xOffset + sliceLeft + tileWidth,
y,
sliceLeft + middleWidth,
sliceTop,
sliceRight,
tileHeightSize);
}
// angle left-top
if (sliceLeft > 0 && sliceTop > 0) {
drawModalRectWithCustomSizedTexture(xOffset, yOffset, 0, 0, sliceLeft, sliceTop);
}
addQuad(tessellator, xOffset, yOffset, 0, 0, sliceLeft, sliceTop);
// angle right-top
if (sliceRight > 0 && sliceTop > 0) {
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileWidth,
yOffset,
sliceLeft + middleWidth,
0,
sliceRight,
sliceTop);
}
addQuad(
tessellator,
xOffset + sliceLeft + tileWidth,
yOffset,
sliceLeft + middleWidth,
0,
sliceRight,
sliceTop);
// angle left-bottom
if (sliceLeft > 0 && sliceBottom > 0) {
drawModalRectWithCustomSizedTexture(
xOffset,
yOffset + sliceTop + tileHeight,
0,
sliceTop + middleHeight,
sliceLeft,
sliceBottom);
}
addQuad(
tessellator,
xOffset,
yOffset + sliceTop + tileHeight,
0,
sliceTop + middleHeight,
sliceLeft,
sliceBottom);
// angle right-bottom
if (sliceRight > 0 && sliceBottom > 0) {
drawModalRectWithCustomSizedTexture(
xOffset + sliceLeft + tileWidth,
yOffset + sliceTop + tileHeight,
sliceLeft + middleWidth,
sliceTop + middleHeight,
sliceRight,
sliceBottom);
}
addQuad(
tessellator,
xOffset + sliceLeft + tileWidth,
yOffset + sliceTop + tileHeight,
sliceLeft + middleWidth,
sliceTop + middleHeight,
sliceRight,
sliceBottom);
tessellator.draw();
if (!is2DTexture) GL11.glDisable(GL11.GL_TEXTURE_2D);
}
}
private void drawModalRectWithCustomSizedTexture(int xOffset, int yOffset, int u, int v, int width, int height) {
Gui.func_146110_a(
xOffset + this.paddingLeft,
yOffset + this.paddingTop,
this.x + u,
this.y + v,
width,
height,
this.textureWidth,
this.textureHeight);
private void addQuad(Tessellator tessellator, int xOffset, int yOffset, int u, int v, int width, int height) {
if (width <= 0 || height <= 0) return;
final float scaleU = 1.0F / this.textureWidth;
final float scaleV = 1.0F / this.textureHeight;
final int x = xOffset + this.paddingLeft;
final int y = yOffset + this.paddingTop;
final float u0 = (this.x + u) * scaleU;
final float u1 = (this.x + u + width) * scaleU;
final float v0 = (this.y + v) * scaleV;
final float v1 = (this.y + v + height) * scaleV;
tessellator.addVertexWithUV(x, y + height, 0.0D, u0, v1);
tessellator.addVertexWithUV(x + width, y + height, 0.0D, u1, v1);
tessellator.addVertexWithUV(x + width, y, 0.0D, u1, v0);
tessellator.addVertexWithUV(x, y, 0.0D, u0, v0);
}
}
@@ -7,6 +7,7 @@ import net.minecraft.item.ItemStack;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.IRecipeFilter;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.recipe.IRecipeHandler;
public class RecipeFilter implements IRecipeFilter {
@@ -78,7 +79,7 @@ public class RecipeFilter implements IRecipeFilter {
if (pStack == null) return false;
for (ItemStack stack : pStack.items) {
if (this.filter.matches(stack)) {
if (!ItemInfo.isHidden(stack) && this.filter.matches(stack)) {
return true;
}
}
@@ -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);
}
@@ -2,17 +2,19 @@ package codechicken.nei.item;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IIcon;
import net.minecraftforge.client.IItemRenderer;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import org.lwjgl.opengl.GL11;
import codechicken.nei.item.FluidDrawer.FillDirection;
import codechicken.nei.util.ReadableNumberConverter;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class FluidDisplayRenderer implements IItemRenderer {
@Override
@@ -31,50 +33,27 @@ public class FluidDisplayRenderer implements IItemRenderer {
return;
}
renderIcon(item, fluidDisplay);
renderIcon(item);
renderAmountOverlay(item, fluidDisplay);
}
private void renderIcon(ItemStack item, ItemFluidDisplay fluidDisplay) {
final IIcon icon = fluidDisplay.getIconIndex(item);
if (icon == null) {
return;
}
final int color = fluidDisplay.getColorFromItemStack(item, 0);
final float red = (color >> 16 & 0xFF) / 255F;
final float green = (color >> 8 & 0xFF) / 255F;
final float blue = (color & 0xFF) / 255F;
Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
GL11.glColor4f(red, green, blue, 1F);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.addVertexWithUV(0, 16, 0, icon.getMinU(), icon.getMaxV());
tessellator.addVertexWithUV(16, 16, 0, icon.getMaxU(), icon.getMaxV());
tessellator.addVertexWithUV(16, 0, 0, icon.getMaxU(), icon.getMinV());
tessellator.addVertexWithUV(0, 0, 0, icon.getMinU(), icon.getMinV());
tessellator.draw();
GL11.glColor4f(1F, 1F, 1F, 1F);
GL11.glDisable(GL11.GL_BLEND);
private void renderIcon(ItemStack item) {
final Fluid fluid = FluidRegistry.getFluid(item.getItemDamage());
FluidDrawer.drawFluidArea(fluid != null ? fluid : FluidRegistry.WATER, 0, 0, 16, 16, false, FillDirection.UP);
}
private void renderAmountOverlay(ItemStack item, ItemFluidDisplay fluidDisplay) {
final FluidStack fluidStack = fluidDisplay.getFluid(item);
if (fluidStack == null || fluidStack.amount <= 0) {
final long amount = fluidDisplay.getAmountLong(item);
if (amount <= 0) {
return;
}
String amountString = "";
if (fluidStack.amount < 10_000) {
amountString = String.valueOf(fluidStack.amount) + "L";
if (amount < 10_000) {
amountString = String.valueOf(amount) + "L";
} else {
amountString = ReadableNumberConverter.INSTANCE.toWideReadableForm(fluidStack.amount) + "L";
amountString = ReadableNumberConverter.INSTANCE.toWideReadableForm(amount) + "L";
}
final FontRenderer fontRender = Minecraft.getMinecraft().fontRenderer;
@@ -0,0 +1,189 @@
package codechicken.nei.item;
import java.util.IdentityHashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.util.IIcon;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import org.lwjgl.opengl.GL11;
import codechicken.nei.api.IFluidRenderer;
import codechicken.nei.scroll.GuiHelper;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class FluidDrawer {
public enum FillDirection {
AUTO,
UP,
DOWN,
LEFT,
RIGHT;
public boolean isHorizontal() {
return this == LEFT || this == RIGHT;
}
public FillDirection resolve(Fluid fluid) {
if (this != AUTO) return this;
return fluid.isGaseous() ? DOWN : UP;
}
}
private static final Map<Fluid, IFluidRenderer> fluidRenderers = new IdentityHashMap<>();
private FluidDrawer() {}
public static void registerFluidRenderer(Fluid fluid, IFluidRenderer renderer) {
if (renderer == null) {
fluidRenderers.remove(fluid);
} else {
fluidRenderers.put(fluid, renderer);
}
}
public static void drawTank(int x, int y, int width, int height, int capacity, FluidStack fluidStack,
boolean flowing, FillDirection direction) {
if (fluidStack.amount <= 0) {
return;
}
final Fluid fluid = fluidStack.getFluid();
direction = direction.resolve(fluid);
final int tankSize = direction.isHorizontal() ? width : height;
final int tankCapacity = capacity > 0 ? capacity : fluidStack.amount;
final int fillSize = Math
.max(1, (int) ((long) tankSize * Math.min(fluidStack.amount, tankCapacity) / tankCapacity));
final int fillWidth = direction.isHorizontal() ? Math.min(fillSize, width) : width;
final int fillHeight = direction.isHorizontal() ? height : Math.min(fillSize, height);
final int fillX = direction == FillDirection.LEFT ? x + width - fillWidth : x;
final int fillY = direction == FillDirection.DOWN ? y : y + height - fillHeight;
GL11.glDisable(GL11.GL_LIGHTING);
drawFluidArea(fluid, fillX, fillY, fillWidth, fillHeight, flowing, direction);
GL11.glEnable(GL11.GL_LIGHTING);
}
/**
* Fills the area with fluid tiles. The tiles start from the side the fluid fills from, so a partial tile ends up at
* the fluid surface.
*/
public static void drawFluidArea(Fluid fluid, int x, int y, int width, int height, boolean flowing,
FillDirection direction) {
direction = direction.resolve(fluid);
final boolean anchorRight = direction == FillDirection.LEFT;
final boolean anchorBottom = direction != FillDirection.DOWN;
final IFluidRenderer renderer = fluidRenderers.get(fluid);
if (renderer == null) {
drawDefaultFluid(fluid, x, y, width, height, flowing, anchorRight, anchorBottom);
} else {
drawCustomFluid(renderer, fluid, x, y, width, height, anchorRight, anchorBottom);
}
}
private static void drawCustomFluid(IFluidRenderer renderer, Fluid fluid, int x, int y, int width, int height,
boolean anchorRight, boolean anchorBottom) {
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
GL11.glDisable(GL11.GL_LIGHTING);
final boolean clip = width % 16 != 0 || height % 16 != 0;
if (clip) {
GuiHelper.pushScissorFrame(x, y, width, height);
}
try {
for (int tx = 0; tx < width; tx += 16) {
final int tileX = anchorRight ? x + width - tx - 16 : x + tx;
for (int ty = 0; ty < height; ty += 16) {
final int tileY = anchorBottom ? y + height - ty - 16 : y + ty;
renderer.renderFluid(fluid, tileX, tileY);
}
}
} finally {
if (clip) {
GuiHelper.popScissorFrame();
}
GL11.glPopAttrib();
GL11.glColor4f(1F, 1F, 1F, 1F);
}
}
private static void drawDefaultFluid(Fluid fluid, int x, int y, int width, int height, boolean flowing,
boolean anchorRight, boolean anchorBottom) {
IIcon icon = flowing ? fluid.getFlowingIcon() : null;
if (icon == null) {
icon = fluid.getStillIcon();
}
if (icon == null) {
icon = FluidRegistry.WATER.getStillIcon();
}
if (icon == null) {
return;
}
final int color = fluid.getColor();
final float red = (color >> 16 & 0xFF) / 255F;
final float green = (color >> 8 & 0xFF) / 255F;
final float blue = (color & 0xFF) / 255F;
Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
GL11.glColor4f(red, green, blue, 1F);
GL11.glEnable(GL11.GL_BLEND);
OpenGlHelper
.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
final double iconMinU = icon.getMinU();
final double iconMaxU = icon.getMaxU();
final double iconMinV = icon.getMinV();
final double iconMaxV = icon.getMaxV();
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
for (int tx = 0; tx < width; tx += 16) {
final int tileWidth = Math.min(16, width - tx);
// a cropped tile keeps the texture edge that stays seamless against the neighbouring tile
final double uSize = (iconMaxU - iconMinU) * tileWidth / 16D;
final double uMin = anchorRight ? iconMaxU - uSize : iconMinU;
final double uMax = anchorRight ? iconMaxU : iconMinU + uSize;
final int leftX = anchorRight ? x + width - tx - tileWidth : x + tx;
final int rightX = leftX + tileWidth;
for (int ty = 0; ty < height; ty += 16) {
final int tileHeight = Math.min(16, height - ty);
final double vSize = (iconMaxV - iconMinV) * tileHeight / 16D;
final double vMin = anchorBottom ? iconMaxV - vSize : iconMinV;
final double vMax = anchorBottom ? iconMaxV : iconMinV + vSize;
final int bottomY = anchorBottom ? y + height - ty : y + ty + tileHeight;
final int topY = bottomY - tileHeight;
tessellator.addVertexWithUV(leftX, bottomY, 0, uMin, vMax);
tessellator.addVertexWithUV(rightX, bottomY, 0, uMax, vMax);
tessellator.addVertexWithUV(rightX, topY, 0, uMax, vMin);
tessellator.addVertexWithUV(leftX, topY, 0, uMin, vMin);
}
}
tessellator.draw();
GL11.glDisable(GL11.GL_BLEND);
GL11.glColor4f(1F, 1F, 1F, 1F);
}
}
@@ -10,7 +10,6 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IIcon;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
@@ -40,36 +39,33 @@ public class ItemFluidDisplay extends Item implements IFluidContainerItem {
};
public ItemFluidDisplay() {
setMaxStackSize(1);
setUnlocalizedName("nei.fluidDisplay");
setMaxStackSize(1);
setHasSubtypes(true);
INSTANCE = this;
setCreativeTab(tabFluids);
MinecraftForgeClient.registerItemRenderer(this, new FluidDisplayRenderer());
}
public static ItemStack createStack(FluidStack fluidStack) {
if (fluidStack == null || fluidStack.getFluid() == null) {
return fluidStack == null ? null : createStack(fluidStack.getFluid(), fluidStack.amount);
}
public static ItemStack createStack(Fluid fluid, long amount) {
if (fluid == null) {
return null;
}
int fluidId;
try {
fluidId = FluidRegistry.getFluidID(fluidStack.getFluid());
fluidId = FluidRegistry.getFluidID(fluid);
} catch (Exception e) {
NEIClientConfig.logger.error("Failed to get fluid id for: " + fluidStack.getFluid().getName(), e);
NEIClientConfig.logger.error("Failed to get fluid id for: " + fluid.getName(), e);
return null;
}
final ItemStack stack = new ItemStack(INSTANCE, 1, fluidId);
final NBTTagCompound nbtTag = new NBTTagCompound();
if (fluidStack.tag != null) {
nbtTag.setTag("FluidTag", fluidStack.tag.copy());
}
nbtTag.setInteger("neiFluidDisplayAmount", fluidStack.amount);
nbtTag.setLong("neiFluidDisplayAmount", amount);
stack.setTagCompound(nbtTag);
return stack;
}
@@ -77,7 +73,7 @@ public class ItemFluidDisplay extends Item implements IFluidContainerItem {
@Override
public FluidStack getFluid(ItemStack stack) {
if (stack == null || !(stack.getItem() instanceof ItemFluidDisplay) || !stack.hasTagCompound()) {
if (stack == null || !(stack.getItem() instanceof ItemFluidDisplay)) {
return null;
}
@@ -88,23 +84,25 @@ public class ItemFluidDisplay extends Item implements IFluidContainerItem {
}
final NBTTagCompound nbTag = stack.getTagCompound();
final FluidStack fluidStack = new FluidStack(fluid, nbTag.getInteger("neiFluidDisplayAmount"));
if (nbTag.hasKey("FluidTag")) {
fluidStack.tag = nbTag.getCompoundTag("FluidTag");
}
final FluidStack fluidStack = new FluidStack(
fluid,
(int) Math.min(nbTag != null ? nbTag.getLong("neiFluidDisplayAmount") : 0, Integer.MAX_VALUE));
return fluidStack;
}
@Override
public int getCapacity(ItemStack stack) {
public long getAmountLong(ItemStack stack) {
if (stack == null || !(stack.getItem() instanceof ItemFluidDisplay) || !stack.hasTagCompound()) {
return 0;
}
return stack.getTagCompound().getInteger("neiFluidDisplayAmount");
return stack.getTagCompound().getLong("neiFluidDisplayAmount");
}
@Override
public int getCapacity(ItemStack stack) {
return (int) Math.min(getAmountLong(stack), Integer.MAX_VALUE);
}
@Override
@@ -180,7 +178,7 @@ public class ItemFluidDisplay extends Item implements IFluidContainerItem {
@Override
@SideOnly(Side.CLIENT)
public void getSubItems(Item aItem, CreativeTabs aTab, List<ItemStack> aList) {
for (int i = 0, j = FluidRegistry.getMaxID(); i < j; i++) {
for (int i = 0, j = FluidRegistry.getMaxID(); i <= j; i++) {
final Fluid fluid = FluidRegistry.getFluid(i);
if (fluid == null) {
continue;
@@ -20,6 +20,7 @@ public class AcceptsFollowingTooltipLineHandler extends ItemsTooltipLineHandler
super(NEIClientUtils.translate("recipe.accepts"), items, false, maxRows);
this.tooltipGUID = tooltipGUID;
setActiveStack(activeStack);
setAmountRenderer(new FavoriteAmountRenderer());
}
public static AcceptsFollowingTooltipLineHandler of(Object tooltipGUID, List<ItemStack> items,
@@ -38,12 +39,15 @@ public class AcceptsFollowingTooltipLineHandler extends ItemsTooltipLineHandler
return null;
}
@Override
protected void drawItem(int x, int y, ItemStack drawStack, String stackSize) {
super.drawItem(x, y, drawStack, stackSize);
protected static class FavoriteAmountRenderer extends NoAmountRenderer {
if (FavoriteRecipes.containsManual(drawStack)) {
NEIClientUtils.drawNEIOverlayText("F", x, y);
@Override
public void draw(int x, int y, ItemStack stack, long amount) {
super.draw(x, y, stack, amount);
if (FavoriteRecipes.containsManual(stack)) {
NEIClientUtils.drawNEIOverlayText("F", x, y);
}
}
}
@@ -5,13 +5,18 @@ import java.util.List;
import java.util.Map;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.Slot;
import net.minecraft.inventory.SlotCrafting;
import net.minecraft.item.ItemStack;
import codechicken.nei.FastTransferManager;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemStackAmount;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.NEIServerUtils;
import codechicken.nei.RestartableTask;
import codechicken.nei.ServerSync;
import codechicken.nei.bookmark.BookmarkItem;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.chain.RecipeChainIterator;
@@ -26,6 +31,7 @@ public class AutoCraftingManager {
@Override
public void execute() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
final ItemStack[] layout = snapshotLayout(guiContainer);
final List<BookmarkItem> initialItems = prepareInitialItems(math, getInventoryItems(guiContainer));
boolean processed = false;
boolean changed = false;
@@ -69,6 +75,11 @@ public class AutoCraftingManager {
} while (changed && !interrupted(guiContainer));
if (!interrupted(guiContainer)) {
restoreLayout(guiContainer, layout);
ServerSync.request();
}
} finally {
StackInfo.pauseItemDamageSound(false);
}
@@ -134,4 +145,70 @@ public class AutoCraftingManager {
return inventory;
}
private static ItemStack[] snapshotLayout(GuiContainer guiContainer) {
final List<Slot> slots = guiContainer.inventorySlots.inventorySlots;
final ItemStack[] layout = new ItemStack[slots.size()];
for (int index = 0; index < slots.size(); index++) {
final Slot slot = slots.get(index);
if (slot.getHasStack() && !(slot instanceof SlotCrafting)) {
layout[index] = slot.getStack().copy();
}
}
return layout;
}
private static void restoreLayout(GuiContainer guiContainer, ItemStack[] layout) {
final List<Slot> slots = guiContainer.inventorySlots.inventorySlots;
final EntityPlayer player = guiContainer.mc.thePlayer;
for (int index = 0; index < slots.size() && player.inventory.getItemStack() == null
&& guiContainer.mc.currentScreen == guiContainer; index++) {
final ItemStack expected = layout[index];
final Slot target = slots.get(index);
if (expected == null || inPlace(slots, layout, index)
|| !target.isItemValid(expected)
|| target.getHasStack() && !target.canTakeStack(player)) {
continue;
}
final Slot source = findMisplaced(slots, layout, expected, player);
if (source == null) continue;
FastTransferManager.clickSlot(guiContainer, source.slotNumber);
FastTransferManager.clickSlot(guiContainer, target.slotNumber);
if (player.inventory.getItemStack() != null) {
FastTransferManager.clickSlot(guiContainer, source.slotNumber);
}
}
FastTransferManager.dropHeldItem(guiContainer);
}
private static boolean inPlace(List<Slot> slots, ItemStack[] layout, int index) {
final ItemStack stack = slots.get(index).getStack();
return stack != null && layout[index] != null
&& NEIServerUtils.areStacksSameTypeCraftingWithNBT(layout[index], stack);
}
private static Slot findMisplaced(List<Slot> slots, ItemStack[] layout, ItemStack expected, EntityPlayer player) {
for (int index = 0; index < slots.size(); index++) {
final Slot slot = slots.get(index);
if (slot.getHasStack() && !(slot instanceof SlotCrafting)
&& !inPlace(slots, layout, index)
&& slot.canTakeStack(player)
&& NEIServerUtils.areStacksSameTypeCraftingWithNBT(expected, slot.getStack())) {
return slot;
}
}
return null;
}
}
@@ -1,6 +1,7 @@
package codechicken.nei.recipe;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -20,10 +21,13 @@ import codechicken.lib.inventory.InventoryUtils;
import codechicken.nei.FastTransferManager;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.ServerSync;
import codechicken.nei.api.IOverlayHandler;
public class DefaultOverlayHandler implements IOverlayHandler {
private static final long SYNC_TIMEOUT_MILLIS = 5000;
public static class DistributedIngred {
public DistributedIngred(ItemStack item) {
@@ -113,7 +117,8 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
boolean craft = false;
while (multiplier > 0) {
while (multiplier > 0 && firstGui.mc.currentScreen == firstGui
&& presenceOverlay(firstGui, handler, recipeIndex).stream().allMatch(s -> s.isPresent())) {
final int transfer = transferRecipe(firstGui, handler, recipeIndex, multiplier);
if (transfer <= 0) {
@@ -122,6 +127,10 @@ public class DefaultOverlayHandler implements IOverlayHandler {
multiplier -= transfer;
if (!craftingSlot.getHasStack() && !awaitServer(firstGui)) {
break;
}
if (craftingSlot.getHasStack() && craftingSlot.canTakeStack(firstGui.mc.thePlayer)) {
FastTransferManager.clickSlot(firstGui, craftingSlot.slotNumber, 0, 1);
@@ -139,9 +148,30 @@ public class DefaultOverlayHandler implements IOverlayHandler {
return craft;
}
private boolean awaitServer(GuiContainer gui) {
final long deadline = System.currentTimeMillis() + SYNC_TIMEOUT_MILLIS;
ServerSync.request();
while (ServerSync.pending()) {
if (gui.mc.currentScreen != gui || System.currentTimeMillis() > deadline) {
return false;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
return false;
}
}
return true;
}
protected boolean clearIngredients(GuiContainer gui, IRecipeHandler handler) {
for (Slot slot : getCraftMatrixSlots(gui, handler)) {
if (gui.mc.currentScreen != gui) return false;
if (slot.canTakeStack(gui.mc.thePlayer)) {
FastTransferManager.clickSlot(gui, slot.slotNumber, 0, 1);
if (slot.getHasStack()) return false;
@@ -206,7 +236,9 @@ public class DefaultOverlayHandler implements IOverlayHandler {
protected void moveIngredients(GuiContainer gui, List<IngredientDistribution> assignedIngredients, int multiplier) {
final Slot craftingSlot = getCraftingResultSlot(gui);
for (Slot slot : gui.inventorySlots.inventorySlots) {
for (Slot slot : getSourceSlots(gui)) {
if (gui.mc.currentScreen != gui) return;
if (slot == craftingSlot || !slot.getHasStack()
|| !canMoveFrom(slot, gui)
|| !slot.canTakeStack(gui.mc.thePlayer))
@@ -240,6 +272,10 @@ public class DefaultOverlayHandler implements IOverlayHandler {
FastTransferManager.clickSlot(gui, slot.slotNumber);
}
// A right click with an empty or foreign cursor would take half of the dest stack
final ItemStack held = gui.mc.thePlayer.inventory.getItemStack();
if (held == null || !canStack(distrib.permutation, held)) break;
FastTransferManager.clickSlot(gui, dest.slotNumber, 1);
stackSize--;
}
@@ -370,7 +406,7 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
protected void findInventoryQuantities(GuiContainer gui, List<DistributedIngred> ingredStacks) {
for (Slot slot : gui.inventorySlots.inventorySlots) /* work out how much we have to go round */ {
for (Slot slot : getSourceSlots(gui)) /* work out how much we have to go round */ {
if (slot.getHasStack() && canMoveFrom(slot, gui)) {
final ItemStack pstack = slot.getStack();
final DistributedIngred istack = findIngred(ingredStacks, pstack);
@@ -408,6 +444,25 @@ public class DefaultOverlayHandler implements IOverlayHandler {
}
}
protected List<Slot> getSourceSlots(GuiContainer gui) {
final List<Slot> slots = new ArrayList<>(gui.inventorySlots.inventorySlots);
slots.sort(Comparator.comparingLong(slot -> getRemainingDurability(slot.getStack())));
return slots;
}
private static long getRemainingDurability(ItemStack stack) {
if (stack == null || stack.getMaxStackSize() != 1) return Long.MAX_VALUE;
final NBTTagCompound tagCompound = stack.getTagCompound();
if (tagCompound != null && tagCompound.hasKey("GT.ToolStats")) {
final NBTTagCompound toolStats = tagCompound.getCompoundTag("GT.ToolStats");
return toolStats.getLong("MaxDamage") - toolStats.getLong("Damage");
}
return stack.isItemStackDamageable() ? stack.getMaxDamage() - stack.getItemDamage() : Long.MAX_VALUE;
}
private int getGTToolDamagePerContainerCraft(ItemStack aStack) {
try {
@@ -1,121 +1,242 @@
package codechicken.nei.recipe;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Collections;
import java.util.List;
import net.minecraft.init.Items;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.Item;
import net.minecraft.item.ItemDye;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.RecipeFireworks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import codechicken.lib.gui.GuiDraw;
import codechicken.nei.InventoryCraftingDummy;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.guihook.GuiContainerManager;
public class FireworkRecipeHandler extends ShapelessRecipeHandler {
public class CachedFireworkRecipe extends CachedShapelessRecipe {
final LinkedList<Object> itemList = new LinkedList<>();
public final Object[] baseIngredients;
public final Object extraIngred;
public final int recipeType;
public final Object groupId;
public CachedFireworkRecipe(Object[] base, Object extra, int type) {
super(new ItemStack(Items.fireworks));
this.baseIngredients = base;
this.extraIngred = extra;
public CachedFireworkRecipe(List<ItemStack> ingredients, ItemStack result, int type, Object groupId) {
super(ingredients, result);
this.recipeType = type;
cycle();
}
public void cycle() {
itemList.clear();
itemList.addAll(Arrays.asList(baseIngredients));
int extras = (cycleticks / 40) % (10 - itemList.size());
for (int i = 0; i < extras; i++) itemList.add(extraIngred);
setIngredients(itemList);
List<PositionedStack> ingreds = getIngredients();
for (int i = 0; i < 9; i++)
inventoryCrafting.setInventorySlotContents(i, i < ingreds.size() ? ingreds.get(i).item : null);
if (!recipeFireworks.matches(inventoryCrafting, null)) throw new RuntimeException("Invalid Recipe?");
setResult(recipeFireworks.getCraftingResult(null));
this.groupId = groupId;
}
}
// indexed by the "Type" explosion tag
private static final ItemStack[] SHAPES = { null, new ItemStack(Items.fire_charge),
new ItemStack(Items.gold_nugget), new ItemStack(Items.skull, 1, Short.MAX_VALUE),
new ItemStack(Items.feather) };
private final InventoryCrafting inventoryCrafting = new InventoryCraftingDummy();
private final RecipeFireworks recipeFireworks = new RecipeFireworks();
public final ArrayList<CachedFireworkRecipe> mfireworks = new ArrayList<>();
private ItemStack usageIngredient = null;
public FireworkRecipeHandler() {
super();
stackorder = new int[][] { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 }, { 1, 1 }, { 2, 1 }, { 0, 2 }, { 1, 2 },
{ 2, 2 } };
loadAllFireworks();
}
private void loadAllFireworks() {
// charges
Item[] shapes = new Item[] { null, Items.fire_charge, Items.gold_nugget, Items.feather, Items.skull };
Item[] effects = new Item[] { null, Items.diamond, Items.glowstone_dust };
for (Item shape : shapes)
for (Item effect : effects) genRecipe(Items.gunpowder, shape, effect, Items.dye, Items.dye, 0);
// fireworks
genRecipe(Items.gunpowder, Items.paper, Items.firework_charge, 2);
genRecipe(Items.gunpowder, Items.gunpowder, Items.paper, Items.firework_charge, 2);
genRecipe(Items.gunpowder, Items.gunpowder, Items.gunpowder, Items.paper, Items.firework_charge, 2);
// setup a valid charge to use for the recolour recipe
for (int i = 0; i < 9; i++) inventoryCrafting.setInventorySlotContents(i, null);
inventoryCrafting.setInventorySlotContents(0, new ItemStack(Items.gunpowder));
inventoryCrafting.setInventorySlotContents(1, new ItemStack(Items.dye));
recipeFireworks.matches(inventoryCrafting, null);
ItemStack charge = recipeFireworks.getCraftingResult(null);
genRecipe(charge, Items.dye, Items.dye, 1);
@Override
public Object getRecipeGroupId(int recipe) {
return this.arecipes.get(recipe) instanceof CachedFireworkRecipe firework ? firework.groupId : null;
}
private void genRecipe(Object... params) {
int numIngreds = 0;
for (int i = 0; i < params.length - 2; i++) if (params[i] != null) numIngreds++;
private void loadChargeRecipes() {
final List<List<ItemStack>> dyeSets = new ArrayList<>();
for (int i = 0; i < params.length - 1; i++)
if (params[i] instanceof Item) params[i] = new ItemStack((Item) params[i], 1, Short.MAX_VALUE);
for (int dye = 0; dye < ItemDye.field_150922_c.length; dye++) {
dyeSets.add(Collections.singletonList(new ItemStack(Items.dye, 1, dye)));
}
Object[] ingreds = new Object[numIngreds];
for (int i = 0, j = 0; i < params.length - 2; i++) if (params[i] != null) ingreds[j++] = params[i];
for (ItemStack shape : SHAPES) {
for (int effect = 0; effect < 4; effect++) {
addRecipes(getChargeBase(shape, (effect & 1) != 0, (effect & 2) != 0), dyeSets, 0);
}
}
mfireworks
.add(new CachedFireworkRecipe(ingreds, params[params.length - 2], (Integer) params[params.length - 1]));
final ItemStack charge = craft(Arrays.asList(new ItemStack(Items.gunpowder), new ItemStack(Items.dye)));
addRecipes(Collections.singletonList(charge), dyeSets, 1);
}
private void loadRocketRecipes() {
final ItemStack charge = craft(Arrays.asList(new ItemStack(Items.gunpowder), new ItemStack(Items.dye)));
final List<List<ItemStack>> chargeSets = new ArrayList<>();
for (int count = 0; count < 9; count++) {
chargeSets.add(Collections.nCopies(count, charge));
}
for (int flight = 1; flight <= 3; flight++) {
addRecipes(getRocketBase(flight), chargeSets, 2);
}
}
private void loadExactChargeRecipes(NBTTagCompound explosion) {
final byte type = explosion.getByte("Type");
if (explosion.hasKey("FadeColors")) {
final NBTTagCompound baseTag = new NBTTagCompound();
final ItemStack charge = new ItemStack(Items.firework_charge);
baseTag.setTag("Explosion", explosion.copy());
baseTag.getCompoundTag("Explosion").removeTag("FadeColors");
charge.setTagCompound(baseTag);
addRecipes(Collections.singletonList(charge), getDyeSets(explosion.getIntArray("FadeColors")), 1);
} else if (type >= 0 && type < SHAPES.length) {
addRecipes(
getChargeBase(SHAPES[type], explosion.getBoolean("Flicker"), explosion.getBoolean("Trail")),
getDyeSets(explosion.getIntArray("Colors")),
0);
}
}
private void loadExactRocketRecipes(NBTTagCompound fireworks) {
final NBTTagList explosions = fireworks.getTagList("Explosions", 10);
final List<ItemStack> charges = new ArrayList<>();
for (int i = 0; i < explosions.tagCount(); i++) {
final ItemStack charge = new ItemStack(Items.firework_charge);
charge.setTagCompound(new NBTTagCompound());
charge.getTagCompound().setTag("Explosion", explosions.getCompoundTagAt(i));
charges.add(charge);
}
addRecipes(getRocketBase(fireworks.getByte("Flight")), Collections.singletonList(charges), 2);
}
private static List<ItemStack> getChargeBase(ItemStack shape, boolean flicker, boolean trail) {
final List<ItemStack> base = new ArrayList<>();
base.add(new ItemStack(Items.gunpowder));
if (shape != null) {
base.add(shape);
}
if (flicker) {
base.add(new ItemStack(Items.glowstone_dust));
}
if (trail) {
base.add(new ItemStack(Items.diamond));
}
return base;
}
private static List<ItemStack> getRocketBase(int flight) {
final List<ItemStack> base = new ArrayList<>();
for (int i = 0; i < flight; i++) {
base.add(new ItemStack(Items.gunpowder));
}
base.add(new ItemStack(Items.paper));
return base;
}
private static List<List<ItemStack>> getDyeSets(int[] colors) {
final List<ItemStack> dyes = new ArrayList<>();
for (int color : colors) {
for (int dye = 0; dye < ItemDye.field_150922_c.length; dye++) {
if (ItemDye.field_150922_c[dye] == color) {
dyes.add(new ItemStack(Items.dye, 1, dye));
break;
}
}
}
if (dyes.isEmpty() || dyes.size() != colors.length) {
return Collections.emptyList();
}
if (Arrays.stream(colors).distinct().count() > 1) {
return Collections.singletonList(dyes);
}
final List<List<ItemStack>> dyeSets = new ArrayList<>();
for (int count = 1; count < 9; count++) {
dyeSets.add(Collections.nCopies(count, dyes.get(0)));
}
return dyeSets;
}
private void addRecipes(List<ItemStack> base, List<List<ItemStack>> extraSets, int type) {
final Object groupId = new Object();
for (List<ItemStack> extras : extraSets) {
final List<ItemStack> ingredients = new ArrayList<>(base);
ingredients.addAll(extras);
final ItemStack result = craft(ingredients);
if (result != null) {
final CachedFireworkRecipe recipe = new CachedFireworkRecipe(ingredients, result, type, groupId);
if (usageIngredient == null || recipe.contains(recipe.ingredients, usageIngredient)) {
arecipes.add(recipe);
}
}
}
}
private ItemStack craft(List<ItemStack> ingredients) {
if (ingredients.size() > 9) {
return null;
}
for (int i = 0; i < 9; i++) {
inventoryCrafting.setInventorySlotContents(i, i < ingredients.size() ? ingredients.get(i) : null);
}
return recipeFireworks.matches(inventoryCrafting, null) ? recipeFireworks.getCraftingResult(inventoryCrafting)
: null;
}
@Override
public void loadCraftingRecipes(ItemStack result) {
for (CachedFireworkRecipe recipe : mfireworks) {
if (recipe.result.item.getItem() == result.getItem()) {
recipe.cycle();
arecipes.add(recipe);
final NBTTagCompound tag = result.getTagCompound();
if (result.getItem() == Items.firework_charge) {
if (tag != null) {
loadExactChargeRecipes(tag.getCompoundTag("Explosion"));
}
if (arecipes.isEmpty()) {
loadChargeRecipes();
}
} else if (result.getItem() == Items.fireworks) {
if (tag != null) {
loadExactRocketRecipes(tag.getCompoundTag("Fireworks"));
}
if (arecipes.isEmpty()) {
loadRocketRecipes();
}
}
// show random recolouring recipes as well
}
@Override
public void loadCraftingRecipes(String outputId, Object... results) {
if (outputId.equals("crafting") && getClass() == FireworkRecipeHandler.class) {
arecipes.addAll(mfireworks);
loadChargeRecipes();
loadRocketRecipes();
} else {
super.loadCraftingRecipes(outputId, results);
}
@@ -123,20 +244,9 @@ public class FireworkRecipeHandler extends ShapelessRecipeHandler {
@Override
public void loadUsageRecipes(ItemStack ingredient) {
for (CachedFireworkRecipe recipe : mfireworks) {
if (recipe.contains(recipe.ingredients, ingredient)) {
recipe.cycle();
arecipes.add(recipe);
}
}
}
@Override
public void onUpdate() {
if (!NEIClientUtils.shiftKey()) {
cycleticks++;
if (cycleticks % 20 == 0) for (CachedRecipe crecipe : arecipes) ((CachedFireworkRecipe) crecipe).cycle();
}
usageIngredient = ingredient;
loadChargeRecipes();
loadRocketRecipes();
}
@Override
@@ -147,11 +257,8 @@ public class FireworkRecipeHandler extends ShapelessRecipeHandler {
@Override
public List<String> handleTooltip(GuiRecipe<?> gui, List<String> currenttip, int recipe) {
currenttip = super.handleTooltip(gui, currenttip, recipe);
Point mousepos = GuiDraw.getMousePosition();
Point relMouse = new Point(mousepos.x - gui.guiLeft, mousepos.y - gui.guiTop);
Point recipepos = gui.getRecipePosition(recipe);
if (currenttip.isEmpty() && GuiContainerManager.getStackMouseOver(gui) == null
&& new Rectangle(recipepos.x, recipepos.y, 166, 55).contains(relMouse))
&& new Rectangle(0, 0, 166, 55).contains(gui.getRecipeMousePosition(recipe)))
currenttip.add(
NEIClientUtils.translate(
"recipe.firework.tooltip" + ((CachedFireworkRecipe) arecipes.get(recipe)).recipeType));
@@ -1,9 +1,9 @@
package codechicken.nei.recipe;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -12,14 +12,9 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.item.ItemStack;
import codechicken.lib.gui.GuiDraw;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType;
import codechicken.nei.bookmark.BookmarksGridSlot;
import codechicken.nei.recipe.Recipe.RecipeId;
public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
@@ -35,6 +30,7 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
public static GuiRecipe<?> createRecipeGui(String outputId, boolean open, Object... results) {
final Minecraft mc = NEIClientUtils.mc();
final RecipeId recipeId;
List<ItemStack> pinnedIngredients = null;
for (int i = 0; i < results.length; i++) {
if (results[i] instanceof ItemStack stack) {
@@ -46,6 +42,12 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
recipeId = getRecipeId(mc.currentScreen, (ItemStack) results[0]);
} else if ("recipeId".equals(outputId)) {
recipeId = (RecipeId) results[1];
// optional third argument: ingredients to pin the found recipe with
if (results.length > 2 && results[2] instanceof List<?>list) {
pinnedIngredients = extractIngredients(list);
}
} else {
recipeId = getCurrentRecipeId(mc.currentScreen);
}
@@ -65,13 +67,25 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
mc.displayGuiScreen(gui);
}
gui.openTargetRecipe(recipeId);
gui.openTargetRecipe(recipeId, pinnedIngredients);
return gui;
}
return null;
}
private static List<ItemStack> extractIngredients(List<?> list) {
final List<ItemStack> ingredients = new ArrayList<>(list.size());
for (Object item : list) {
if (item instanceof ItemStack stack) {
ingredients.add(stack);
}
}
return ingredients;
}
public static ArrayList<ICraftingHandler> getCraftingHandlers(String outputId, Object... results) {
ArrayList<ICraftingHandler> craftinghandlers = GuiCraftingRecipe.craftinghandlers;
ArrayList<ICraftingHandler> serialCraftingHandlers = GuiCraftingRecipe.serialCraftingHandlers;
@@ -118,39 +132,8 @@ public class GuiCraftingRecipe extends GuiRecipe<ICraftingHandler> {
}
public static RecipeId getRecipeId(GuiScreen gui, ItemStack stackover) {
if (gui instanceof GuiRecipe<?>guiRecipe) {
final Recipe focusedRecipe = guiRecipe.getFocusedRecipe();
if (focusedRecipe != null) {
return focusedRecipe.getRecipeId();
}
}
final Point mouseover = GuiDraw.getMousePosition();
final BookmarksGridSlot panelSlot = ItemPanels.bookmarkPanel.getSlotMouseOver(mouseover.x, mouseover.y);
if (panelSlot != null) {
return panelSlot.getType() == BookmarkItemType.INGREDIENT || panelSlot.getRecipeId() == null
? FavoriteRecipes.getFavorite(stackover)
: panelSlot.getRecipeId();
}
ItemsGridSlot itemSlot = ItemPanels.itemPanel.getSlotMouseOver(mouseover.x, mouseover.y);
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.historyPanel.getSlotMouseOver(mouseover.x, mouseover.y);
}
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.craftablesPanel.getSlotMouseOver(mouseover.x, mouseover.y);
}
if (itemSlot != null) {
return itemSlot.getRecipeId();
}
return null;
final HoveredRecipe hovered = HoveredRecipe.of(gui, stackover);
return hovered != null ? hovered.recipeId : null;
}
private GuiCraftingRecipe(ArrayList<ICraftingHandler> handlers) {
@@ -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) {
@@ -70,7 +70,7 @@ public class GuiOverlayButton extends GuiRecipeButton {
if (format == ItemOverlayFormat.BACKGROUND) {
NEIClientUtils.gl2DRenderContext(
() -> drawRect(
() -> GuiDraw.drawRect(
this.slot.relx,
this.slot.rely,
this.slot.width,
@@ -177,12 +177,20 @@ public class GuiOverlayButton extends GuiRecipeButton {
hotkeys.put(
NEIClientUtils.getKeyName(NEIClientUtils.SHIFT_HASH, NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGrid"));
hotkeys.put(
NEIClientUtils.getKeyName(
NEIClientUtils.SHIFT_HASH + NEIClientUtils.CTRL_HASH,
NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGridOnce"));
}
} else if (canFillCraftingGrid()) {
hotkeys.put(
NEIMouseUtils.getKeyName(NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGrid"));
hotkeys.put(
NEIClientUtils.getKeyName(NEIClientUtils.CTRL_HASH, NEIMouseUtils.MOUSE_BTN_LMB),
NEIClientUtils.translate("recipe.overlay.fillCraftingGridOnce"));
}
hotkeys.put(
@@ -262,11 +270,8 @@ public class GuiOverlayButton extends GuiRecipeButton {
.map(state -> state.getSlot().item).collect(Collectors.toList());
if (!items.isEmpty()) {
this.missedMaterialsTooltipLineHandler = new ItemsTooltipLineHandler(
NEIClientUtils.translate("recipe.overlay.missing"),
items,
true,
Integer.MAX_VALUE);
this.missedMaterialsTooltipLineHandler = ItemsTooltipLineHandler
.of(NEIClientUtils.translate("recipe.overlay.missing"), items, Integer.MAX_VALUE, 0);
} else {
this.missedMaterialsTooltipLineHandler = null;
}
@@ -332,7 +337,7 @@ public class GuiOverlayButton extends GuiRecipeButton {
if (!requireShiftForOverlayRecipe() || shift) {
if (this.canFillCraftingGrid) {
this.handlerRef.fillCraftingGrid(this.firstGui, 0);
this.handlerRef.fillCraftingGrid(this.firstGui, NEIClientUtils.controlKey() ? 1 : 0);
}
} else if (hasOverlay() && useOverlayRenderer()) {
this.handlerRef.useOverlayRenderer(this.firstGui);
@@ -8,7 +8,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.WeakHashMap;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
@@ -52,6 +52,7 @@ import codechicken.nei.guihook.IContainerTooltipHandler;
import codechicken.nei.guihook.IGuiClientSide;
import codechicken.nei.guihook.IGuiHandleMouseWheel;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.ScrollBar;
import codechicken.nei.scroll.ScrollBar.OverflowType;
import codechicken.nei.scroll.ScrollBar.ScrollPlace;
@@ -70,15 +71,23 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
0,
0,
176 + TRANSPARENCY_BORDER * 2,
166 + TRANSPARENCY_BORDER * 2).build();
166 + TRANSPARENCY_BORDER * 2)
.setSlices(
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER)
.build();
private static final int BUTTON_WIDTH = 13;
private static final int BUTTON_HEIGHT = 12;
protected static final ScrollBar VERTICAL_SCROLLBAR = new ScrollBar().setTrackWidth(14)
.setOverflowType(OverflowType.AUTO).setScrollPlace(ScrollPlace.END)
.setTrackTexture(new DrawableBuilder("nei:textures/nei_sprites.png", 42, 90, 13, 21).build(), 9, 9)
.setThumbTexture(new DrawableBuilder("nei:textures/nei_sprites.png", 22, 96, 6, 9).build(), 3, 2)
.setTrackTexture(
new DrawableBuilder("nei:textures/nei_sprites.png", 42, 90, 13, 21).setSlices(0, 0, 9, 9).build())
.setThumbTexture(
new DrawableBuilder("nei:textures/nei_sprites.png", 22, 96, 6, 9).setSlices(0, 0, 3, 2).build())
.setThumbPadding(1, 6, 0, 6);
public static final List<IRecipeFilterProvider> recipeFilterers = new LinkedList<>();
@@ -294,73 +303,118 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
/**
* Many old mods assumed a fixed NEI window height of {@code 166} pixels. Now that this is no longer the case, their
* tooltip and click zone handling is broken. This helper class fixes these old mods by hacking the {@link #height}
* property's value so that these old mods' calculations return the correct value for the new height.
* Many old mods assumed a fixed NEI window size of {@code 176x166} pixels, with exactly one recipe shown at a time,
* flush against the top-left corner of the window. Now that none of these assumptions hold (the window has a
* flexible size, several recipes can be stacked in the same scrollable list, and the recipe area is inset from the
* window's edges), these old mods' tooltip and click zone handling is broken. This helper class fixes these old
* mods by hacking the {@link #width}, {@link #height}, {@link #guiLeft} and {@link #guiTop} properties' values so
* that these old mods' calculations return the correct value for whichever recipe they are currently being asked
* about.
*
* <p>
* New and old mods often assume that the handler is only drawn when {@link GuiRecipe} is the currently active
* screen in {@link net.minecraft.client.Minecraft}. This is no longer true since the addition of recipe tooltips
* which need to render the recipe in other containers on mouse hover, this class temporarily switches the active
* screen to {@code this} while rendering them to avoid ClassCastExceptions and other similar crashes.
* Since each recipe handled by a {@link RecipeWidget} can sit at a different real position, this must be
* constructed with that widget's top-left edge every time a legacy entry point ({@code mouseClicked},
* {@code handleTooltip}, ...) is invoked for it, rather than once per {@link GuiRecipe}.
*
* <p>
* This class is an {@link AutoCloseable} so that it can be used with try-with-resources, which will ensure that
* {@link #height} and {@link net.minecraft.client.Minecraft#currentScreen} is returned to the correct value
* {@link #width}, {@link #height}, {@link #guiLeft} and {@link #guiTop} are returned to the correct value
* afterwards.
*/
private class CompatibilityHacks implements AutoCloseable {
public static final class CompatibilityHacks implements AutoCloseable {
// The historical NEI recipe screen's fixed size: a single top paging widget 16px tall, no horizontal inset,
// and a 176x166 box. Legacy handlers either read guiLeft/guiTop directly (expecting them to point 16px above
// their recipe, flush on the left) or recompute them from width/height with these same two constants.
private static final int LEGACY_TOP_GAP = 16;
private static final int LEGACY_BOX_WIDTH = 176;
private static final int LEGACY_BOX_HEIGHT = 166;
// layoutWindow() places the recipe container at (guiLeft + 3, guiTop + 32); RecipePageManager then places
// each left-aligned recipe widget another 2px to the right of that. This is the inverse of both, undoing the
// container's own inset so a legacy handler sees its recipe flush with guiLeft as it expects.
private static final int CONTAINER_LEFT_INSET = 3 + 2;
private final GuiRecipe<?> gui;
private final int trueWidth;
private final int trueHeight;
private final int trueGuiLeft;
private final int trueGuiTop;
private CompatibilityHacks() {
trueHeight = height;
trueGuiTop = guiTop;
/**
* @param widget The recipe widget a legacy entry point ({@code mouseClicked}, {@code handleTooltip}, ...) is
* about to be invoked for.
*/
public CompatibilityHacks(GuiRecipe<?> gui, RecipeWidget widget) {
this.gui = gui;
this.trueWidth = gui.width;
this.trueHeight = gui.height;
this.trueGuiLeft = gui.guiLeft;
this.trueGuiTop = gui.guiTop;
isHeightHackApplied = NEIClientConfig.heightHackHandlerRegex.stream()
.map(pattern -> pattern.matcher(handler.original.getHandlerId())).anyMatch(Matcher::matches);
if (isHeightHackApplied) {
// guiTop is the top edge of the recipe screen on the y-axis. Old recipe
// handlers expect a single paging
// widget at the top and at the bottom of the recipe screen with a height of
// 16px each, but GTNH NEI
// moved the bottom paging widget to the top following JEI's design, increasing
// the "forbidden" zone at
// the top to 32px. To fix these old recipe handlers we move guiTop down 16px so
// that they can keep
// working with the old 16px gap.
guiTop += 16;
final String handlerId = gui.handler.original.getHandlerId();
gui.isHeightHackApplied = NEIClientConfig.heightHackHandlerRegex.stream()
.anyMatch(pattern -> pattern.matcher(handlerId).matches());
// The old NEI recipe screen had a fixed width and height and was always
// centered on the screen. Legacy
// recipe handlers use the calculation ((height - 166) / 2) to compute the
// y-value of the top edge of
// the NEI window. GTNH NEI changes the layout so that the recipe screen length
// now has a flexible
// length and is no longer centered vertically. In order for the top-of-screen
// calculation to return the
// correct result, we have to hack the height field with an inverse of the
// calculation using the actual
// top of the recipe screen stored in guiTop.
height = (2 * guiTop) + 166;
// For reference, in case we ever need to modify width as well:
// the legacy calculation used for width is ((width - 176) / 2), which should
// evaluate to be equal to
// guiWidth (the x-value of the left edge of the NEI recipe screen). So if we
// wanted to override width
// as well, we'd do this:
// width = (2 * guiWidth) + 176;
if (gui.isHeightHackApplied) {
gui.guiTop = recipeTop(widget) - LEGACY_TOP_GAP;
gui.guiLeft = widget.x - CONTAINER_LEFT_INSET;
gui.height = (2 * gui.guiTop) + LEGACY_BOX_HEIGHT;
gui.width = (2 * gui.guiLeft) + LEGACY_BOX_WIDTH;
}
}
// recipiesPerPage() == 2 is the IRecipeHandler default: a handler only ends up with this value if it never
// overrode the method, or if it overrode it and chose 2 on purpose. We only care about the latter case, so
// we require the override to actually come from the handler's own class rather than being inherited as-is.
private static final Map<Class<?>, Boolean> overridesRecipiesPerPage = new WeakHashMap<>();
/**
* The real top edge of this widget's recipe, as a legacy handler following the {@link #LEGACY_TOP_GAP}
* convention would expect to find it at guiTop. A few handlers (e.g. ImmersiveEngineering) are left over from
* the old {@code recipiesPerPage() == 2} convention, where two recipes shared one physical page: their own
* {@code mouseClicked}/{@code handleTooltip} still add a hardcoded "recipe height * (recipe % 2)" on top of
* guiTop to find the odd one of the pair. Since every recipe is its own independent widget now, we pre-subtract
* that same offset here so their own addition cancels out and lands back on this widget's real top.
*
* <p>
* This only applies when the handler itself overrides {@link IRecipeHandler#recipiesPerPage()} to return
* {@code 2}: that method defaults to {@code 2} on the interface, so most height-hacked handlers return it
* without ever having implemented this pairing (e.g. BuildCraftCompat never overrides it at all, and only reads
* it elsewhere to pick a cosmetic sub-name), and applying this compensation to them would break them. A handler
* that went out of its way to declare {@code return 2;} itself is making a deliberate statement about this
* legacy convention that a handler which simply never mentions the method isn't.
*/
private static int recipeTop(RecipeWidget widget) {
final RecipeHandlerRef ref = widget.getRecipeHandlerRef();
final HandlerInfo handlerInfo = widget.getHandlerInfo();
final int top = widget.y + handlerInfo.getYShift();
if (ref.handler.recipiesPerPage() == 2 && declaresRecipiesPerPage(ref.handler)) {
return top - handlerInfo.getHeight() * (ref.recipeIndex % 2);
}
return top;
}
private static boolean declaresRecipiesPerPage(IRecipeHandler handler) {
return overridesRecipiesPerPage.computeIfAbsent(handler.getClass(), clazz -> {
try {
return clazz.getMethod("recipiesPerPage").getDeclaringClass() != IRecipeHandler.class;
} catch (NoSuchMethodException e) {
return false;
}
});
}
@Override
public void close() {
guiTop = trueGuiTop;
height = trueHeight;
this.gui.guiLeft = this.trueGuiLeft;
this.gui.guiTop = this.trueGuiTop;
this.gui.width = this.trueWidth;
this.gui.height = this.trueHeight;
isHeightHackApplied = false;
this.gui.isHeightHackApplied = false;
}
}
@@ -523,7 +577,14 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
public int openTargetRecipe(RecipeId recipeId) {
int refIndex = -1;
return openTargetRecipe(recipeId, null);
}
/**
* @param pinnedIngredients when not null, the found recipe is paused with these ingredients shown
*/
public int openTargetRecipe(RecipeId recipeId, List<ItemStack> pinnedIngredients) {
int recipeIndex = -1;
int recipetype = 0;
this.recipeId = recipeId;
@@ -537,10 +598,9 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
recipetype = j;
if (!this.recipeId.getIngredients().isEmpty()) {
refIndex = SearchRecipeHandler.findFirst(
recipeIndex = SearchRecipeHandler.findFirst(
localHandler,
recipeIndex -> this.recipeId
.equalsIngredients(localHandler.getIngredientStacks(recipeIndex)));
ri -> this.recipeId.equalsIngredients(localHandler.getIngredientStacks(ri)));
}
break;
@@ -549,17 +609,21 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
}
setRecipePage(recipetype);
this.handlerPages.gotoRefIndex(Math.max(0, refIndex));
this.handlerPages.gotoRecipeIndex(Math.max(0, recipeIndex));
return refIndex;
if (pinnedIngredients != null && recipeIndex >= 0) {
this.handlerPages.pinRecipe(recipeIndex, pinnedIngredients);
}
return recipeIndex;
}
public Recipe getFocusedRecipe() {
final Point mouse = GuiDraw.getMousePosition();
final Widget activeWidget = this.container.getWidgetUnderMouse(mouse.x, mouse.y);
if (activeWidget instanceof NEIRecipeWidget recipeWidget && recipeWidget.isFocusedRecipe(mouse.x, mouse.y)) {
return recipeWidget.getRecipe();
if (activeWidget instanceof RecipeWidget recipeWidget && recipeWidget.isFocusedRecipe(mouse.x, mouse.y)) {
return Recipe.of(recipeWidget.getRecipeHandlerRef());
}
return null;
@@ -569,7 +633,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final Point mouse = GuiDraw.getMousePosition();
final Widget activeWidget = this.container.getWidgetUnderMouse(mouse.x, mouse.y);
if (activeWidget instanceof NEIRecipeWidget recipeWidget) {
if (activeWidget instanceof RecipeWidget recipeWidget) {
final PositionedStack hovered = recipeWidget.getPositionedStackMouseOver(mouse.x, mouse.y);
if (hovered != null) {
@@ -586,7 +650,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final EmptyContainer slotcontainer = (EmptyContainer) inventorySlots;
slotcontainer.setActiveStack(null);
if (activeWidget instanceof NEIRecipeWidget recipeWidget) {
if (activeWidget instanceof RecipeWidget recipeWidget) {
final PositionedStack hovered = recipeWidget.getPositionedStackMouseOver(mousex, mousey);
if (hovered != null) {
@@ -687,10 +751,8 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
return;
}
try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) {
if (this.container.handleClick(mousex, mousey, button)) {
return;
}
if (this.container.handleClick(mousex, mousey, button)) {
return;
}
if (this.recipeTabs.mouseClicked(mousex, mousey, button)) {
@@ -738,11 +800,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
public void mouseScrolled(int scroll) {
// Height hacking is not necessary here since mouse scrolling is a new feature,
// added in
// GTNH NEI. So no old mods will use this. Though not hacking the height here
// does mean that
// the value of the height field will be different from in other mouseover
// methods, which
// could be confusing...
// GTNH NEI. So no old mods will use this.
// First, invoke scroll handling over recipe handler tabbar. Makes sure it is
// not overwritten by recipe
@@ -755,10 +813,8 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
return;
}
try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) {
if (this.container.onMouseWheel(scroll, mouse.x, mouse.y)) {
return;
}
if (this.container.onMouseWheel(scroll, mouse.x, mouse.y)) {
return;
}
// If shift is held, try switching to the next recipe handler. Replicates the
@@ -810,9 +866,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
currenttip = this.recipeCatalyst.handleTooltip(mousex, mousey, currenttip);
}
try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) {
currenttip = this.container.handleTooltip(mousex, mousey, currenttip);
}
currenttip = this.container.handleTooltip(mousex, mousey, currenttip);
this.recipeTabs.handleTooltip(mousex, mousey, currenttip);
@@ -844,7 +898,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
List<String> currenttip) {
final Widget activeWidget = this.container.getWidgetUnderMouse(mousex, mousey);
if (activeWidget instanceof NEIRecipeWidget recipeWidget) {
if (activeWidget instanceof RecipeWidget recipeWidget) {
currenttip = recipeWidget.handleItemTooltip(itemstack, mousex, mousey, currenttip);
}
@@ -858,9 +912,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
hotkeys = this.recipeCatalyst.handleHotkeys(mousex, mousey, hotkeys);
}
try (CompatibilityHacks compatibilityHacks = new CompatibilityHacks()) {
hotkeys = this.container.handleHotkeys(mousex, mousey, hotkeys);
}
hotkeys = this.container.handleHotkeys(mousex, mousey, hotkeys);
return hotkeys;
}
@@ -891,7 +943,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
this.recipetype = this.currenthandlers.indexOf(this.handler.original);
setRecipePage(this.recipetype);
this.handlerPages.changePage(currentPage);
this.handlerPages.changePage(currentPage - this.handlerPages.getCurrentPageIndex());
refreshContainer();
}
@@ -914,11 +966,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
this.guiLeft - TRANSPARENCY_BORDER,
this.guiTop - TRANSPARENCY_BORDER,
this.xSize + TRANSPARENCY_BORDER * 2,
this.ySize + TRANSPARENCY_BORDER * 2,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER);
this.ySize + TRANSPARENCY_BORDER * 2);
drawJEITabs(mouseX, mouseY);
}
@@ -1014,8 +1062,23 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
return this.firstGuiGeneral;
}
public Point getRecipePosition(int recipe) {
return getRefIndexPosition(getRecipeIndices().indexOf(recipe));
public Point getRecipePosition(int recipeIndex) {
for (Widget widget : this.container.getWidgets()) {
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.containsRecipeIndex(recipeIndex)) {
return new Point(
recipeWidget.x - this.guiLeft,
recipeWidget.y - this.guiTop + recipeWidget.getHandlerInfo().getYShift());
}
}
return new Point(0, 0);
}
public Point getRecipeMousePosition(int recipeIndex) {
final Point mouse = GuiDraw.getMousePosition();
final Point recipePosition = getRecipePosition(recipeIndex);
return new Point(mouse.x - this.guiLeft - recipePosition.x, mouse.y - this.guiTop - recipePosition.y);
}
protected Point getRefIndexPosition(int refIndex) {
@@ -1027,7 +1090,7 @@ public abstract class GuiRecipe<H extends IRecipeHandler> extends GuiContainer i
final List<Widget> children = this.container.getWidgets();
if (refIndex >= 0 && refIndex < children.size()
&& children.get(refIndex) instanceof NEIRecipeWidget recipeWidget) {
&& children.get(refIndex) instanceof RecipeWidget recipeWidget) {
return new Point(
recipeWidget.x - this.guiLeft,
recipeWidget.y - this.guiTop + recipeWidget.getHandlerInfo().getYShift());
@@ -15,12 +15,16 @@ public abstract class GuiRecipeButton extends GuiNEIButton {
public static class UpdateRecipeButtonsEvent extends GuiScreenEvent {
public List<GuiRecipeButton> buttonList;
public NEIRecipeWidget recipeWidget;
public RecipeHandlerRef handlerRef;
public int width;
public int height;
public UpdateRecipeButtonsEvent(GuiRecipe<?> gui, NEIRecipeWidget recipeWidget,
public UpdateRecipeButtonsEvent(GuiRecipe<?> gui, RecipeHandlerRef handlerRef, int width, int height,
List<GuiRecipeButton> buttonList) {
super(gui);
this.recipeWidget = recipeWidget;
this.handlerRef = handlerRef;
this.width = width;
this.height = height;
this.buttonList = new ArrayList<>(buttonList);
}
@@ -29,16 +33,17 @@ public abstract class GuiRecipeButton extends GuiNEIButton {
public HandlerInfo handlerInfo;
public Pre(GuiRecipe<?> gui, NEIRecipeWidget recipeWidget, HandlerInfo handlerInfo) {
super(gui, recipeWidget, new ArrayList<>());
public Pre(GuiRecipe<?> gui, RecipeHandlerRef handlerRef, int width, int height, HandlerInfo handlerInfo) {
super(gui, handlerRef, width, height, new ArrayList<>());
this.handlerInfo = handlerInfo;
}
}
public static class Post extends UpdateRecipeButtonsEvent {
public Post(GuiRecipe<?> gui, NEIRecipeWidget recipeWidget, List<GuiRecipeButton> buttonList) {
super(gui, recipeWidget, buttonList);
public Post(GuiRecipe<?> gui, RecipeHandlerRef handlerRef, int width, int height,
List<GuiRecipeButton> buttonList) {
super(gui, handlerRef, width, height, buttonList);
}
}
}
@@ -55,11 +55,17 @@ public class GuiRecipeCatalyst extends ScrollContainer {
0,
0,
28 + TRANSPARENCY_BORDER * 2,
28 + TRANSPARENCY_BORDER * 2).setTextureSize(28 + TRANSPARENCY_BORDER * 2, 28 + TRANSPARENCY_BORDER * 2)
28 + TRANSPARENCY_BORDER * 2)
.setTextureSize(28 + TRANSPARENCY_BORDER * 2, 28 + TRANSPARENCY_BORDER * 2)
.setSlices(
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER)
.build();
private static final DrawableResource FG_TEXTURE = new DrawableBuilder("nei:textures/slot.png", 0, 0, 18, 18)
.setTextureSize(18, 18).build();
.setTextureSize(18, 18).setSlices(1, 1, 1, 1).build();
private static final ScrollBar VERTICAL_SCROLLBAR = ScrollBar.defaultVerticalBar()
.setOverflowType(ScrollBar.OverflowType.AUTO).setTrackPadding(0, BORDER_PADDING, -13, BORDER_PADDING)
@@ -155,20 +161,12 @@ public class GuiRecipeCatalyst extends ScrollContainer {
this.x - TRANSPARENCY_BORDER,
this.y - TRANSPARENCY_BORDER,
this.w + TRANSPARENCY_BORDER * 2,
this.h + TRANSPARENCY_BORDER * 2,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER,
BORDER_PADDING + TRANSPARENCY_BORDER);
this.h + TRANSPARENCY_BORDER * 2);
FG_TEXTURE.draw(
this.x + this.paddingInlineStart - 1,
this.y + this.paddingBlockStart - 1,
this.w - this.paddingInlineStart - this.paddingInlineEnd + 2,
this.h - this.paddingBlockStart - this.paddingBlockEnd + 2,
1,
1,
1,
1);
this.h - this.paddingBlockStart - this.paddingBlockEnd + 2);
super.draw(mx, my);
}
@@ -181,6 +181,24 @@ public abstract class GuiRecipeTab extends Widget {
return res;
}
public static String getHandlerTitle(String handlerName) {
String title = findRecipeName(GuiCraftingRecipe.craftinghandlers, handlerName);
if (title == null) title = findRecipeName(GuiCraftingRecipe.serialCraftingHandlers, handlerName);
if (title == null) title = findRecipeName(GuiUsageRecipe.usagehandlers, handlerName);
if (title == null) title = findRecipeName(GuiUsageRecipe.serialUsageHandlers, handlerName);
return title != null ? title : handlerName;
}
private static String findRecipeName(List<? extends IRecipeHandler> handlers, String handlerName) {
for (int index = 0; index < handlers.size(); index++) {
final IRecipeHandler handler = handlers.get(index);
if (handlerName.equals(getHandlerInfo(handler).getHandlerName())) return handler.getRecipeName().trim();
}
return null;
}
public static String getHandlerMod(String name, String name2) {
HandlerInfo info = getHandlerInfo(name, name2);
if (info == null) return "Unknown";
@@ -12,6 +12,7 @@ public class HandlerInfo {
public static int DEFAULT_HEIGHT = 65;
public static int DEFAULT_WIDTH = 166;
public static final int DEFAULT_IMAGE_TEXTURE_SIZE = 256;
@Deprecated
public static int DEFAULT_MAX_PER_PAGE = 1;
@@ -70,9 +71,22 @@ public class HandlerInfo {
}
public boolean setImage(String resourceLocation, int imageX, int imageY, int imageWidth, int imageHeight) {
return setImage(
resourceLocation,
imageX,
imageY,
imageWidth,
imageHeight,
DEFAULT_IMAGE_TEXTURE_SIZE,
DEFAULT_IMAGE_TEXTURE_SIZE);
}
public boolean setImage(String resourceLocation, int imageX, int imageY, int imageWidth, int imageHeight,
int textureWidth, int textureHeight) {
if (hasImageOrItem()) return false;
this.image = new DrawableBuilder(resourceLocation, imageX, imageY, imageWidth, imageHeight).build();
this.image = new DrawableBuilder(resourceLocation, imageX, imageY, imageWidth, imageHeight)
.setTextureSize(textureWidth, textureHeight).build();
return true;
}
@@ -211,6 +225,20 @@ public class HandlerInfo {
return this;
}
public Builder setDisplayImage(ResourceLocation location, int imageX, int imageY, int imageWidth,
int imageHeight, int textureWidth, int textureHeight) {
this.info.itemStack = null;
this.info.setImage(
location.toString(),
imageX,
imageY,
imageWidth,
imageHeight,
textureWidth,
textureHeight);
return this;
}
@Deprecated
public Builder setUseCustomScroll(boolean useCustomScroll) {
this.info.setUseCustomScroll(useCustomScroll);
@@ -0,0 +1,87 @@
package codechicken.nei.recipe;
import java.awt.Point;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.item.ItemStack;
import codechicken.lib.gui.GuiDraw;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType;
import codechicken.nei.bookmark.BookmarksGridSlot;
import codechicken.nei.recipe.Recipe.RecipeId;
public final class HoveredRecipe {
public enum Source {
RECIPE_GUI,
BOOKMARK,
ITEM_PANEL,
OTHER
}
public final RecipeId recipeId;
public final Source source;
public final boolean favorite;
private HoveredRecipe(RecipeId recipeId, Source source, boolean favorite) {
this.recipeId = recipeId;
this.source = source;
this.favorite = favorite;
}
public static HoveredRecipe of(GuiScreen gui, ItemStack stackover) {
if (gui instanceof GuiRecipe<?>guiRecipe) {
final Recipe focusedRecipe = guiRecipe.getFocusedRecipe();
if (focusedRecipe != null) {
return new HoveredRecipe(focusedRecipe.getRecipeId(), Source.RECIPE_GUI, false);
}
}
final Point mouseover = GuiDraw.getMousePosition();
final BookmarksGridSlot bookmarkSlot = ItemPanels.bookmarkPanel.getSlotMouseOver(mouseover.x, mouseover.y);
if (bookmarkSlot != null) {
if (bookmarkSlot.getType() != BookmarkItemType.INGREDIENT && bookmarkSlot.getRecipeId() != null) {
return new HoveredRecipe(bookmarkSlot.getRecipeId(), Source.BOOKMARK, false);
}
return favorite(stackover, Source.BOOKMARK);
}
ItemsGridSlot itemSlot = ItemPanels.itemPanel.getSlotMouseOver(mouseover.x, mouseover.y);
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.historyPanel.getSlotMouseOver(mouseover.x, mouseover.y);
}
if (itemSlot == null) {
itemSlot = ItemPanels.itemPanel.craftablesPanel.getSlotMouseOver(mouseover.x, mouseover.y);
}
if (itemSlot != null) {
if (itemSlot.getRecipeId() != null) {
return new HoveredRecipe(itemSlot.getRecipeId(), Source.ITEM_PANEL, false);
}
return favorite(stackover, Source.ITEM_PANEL);
}
if (gui instanceof GuiRecipe<?>guiRecipe && guiRecipe.getSlotAtPosition(mouseover.x, mouseover.y) != null) {
return favorite(stackover, Source.RECIPE_GUI);
}
return favorite(stackover, Source.OTHER);
}
private static HoveredRecipe favorite(ItemStack stackover, Source source) {
final RecipeId recipeId = FavoriteRecipes.getFavorite(stackover);
return recipeId != null ? new HoveredRecipe(recipeId, source, true) : null;
}
}
@@ -91,6 +91,14 @@ public interface IRecipeHandler {
*/
PositionedStack getResultStack(int recipe);
/**
* @param recipe The recipe index to get the group id for.
* @return An id shared by every recipe in this recipe's group, or {@code null} if this recipe isn't grouped.
*/
default Object getRecipeGroupId(int recipe) {
return null;
}
/**
* A tick function called for updating progress bars and cycling damage items.
*/
@@ -1,688 +0,0 @@
package codechicken.nei.recipe;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.stream.Stream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.KeyManager;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.NEIServerUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.Widget;
import codechicken.nei.api.IGuiContainerOverlay;
import codechicken.nei.api.ShortcutInputHandler;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.GuiRecipeButton.UpdateRecipeButtonsEvent;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.debug.DebugHandlerWidget;
import codechicken.nei.util.NEIMouseUtils;
public class NEIRecipeWidget extends Widget {
protected AcceptsFollowingTooltipLineHandler acceptsFollowingTooltipLineHandler;
protected final Map<PositionedStack, Integer> favoriteIndexes = new WeakHashMap<>();
protected final Map<PositionedStack, List<ItemStack>> permutations = new WeakHashMap<>();
protected final Map<PositionedStack, List<Badge>> badgeCache = new WeakHashMap<>();
protected int favoriteRevision = -1;
protected boolean update = true;
protected int cycleticks = 0;
protected int lastcycle = -1;
protected final RecipeHandlerRef handlerRef;
protected final HandlerInfo handlerInfo;
protected boolean showAsWidget = false;
protected List<GuiRecipeButton> recipeButtons = null;
public NEIRecipeWidget(RecipeHandlerRef handlerRef) {
this.handlerRef = handlerRef;
this.handlerInfo = GuiRecipeTab.getHandlerInfo(this.handlerRef.handler);
update();
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
public void update() {
if (this.showAsWidget || !this.handlerInfo.isAllowOverflowY()) {
final int recipeHeight = this.handlerRef.handler.getRecipeHeight(this.handlerRef.recipeIndex);
this.w = Math.max(166, this.handlerInfo.getWidth());
this.h = recipeHeight > 0 ? recipeHeight : this.handlerInfo.getHeight();
this.h += this.handlerInfo.getYShift();
}
this.update = true;
}
public List<GuiRecipeButton> getRecipeButtons() {
if (this.recipeButtons == null) {
if (!getOutputs().isEmpty()) {
this.recipeButtons = initButtons();
} else {
this.recipeButtons = Collections.emptyList();
}
}
return Collections.unmodifiableList(this.recipeButtons);
}
protected List<GuiRecipeButton> getRecipeButtonsIfInit() {
if (this.recipeButtons == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(this.recipeButtons);
}
public RecipeHandlerRef getRecipeHandlerRef() {
return this.handlerRef;
}
public Recipe getRecipe() {
return Recipe.of(this.handlerRef);
}
public HandlerInfo getHandlerInfo() {
return this.handlerInfo;
}
public void showAsWidget(boolean show) {
this.showAsWidget = show;
}
protected List<GuiRecipeButton> initButtons() {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe != null) {
final UpdateRecipeButtonsEvent.Pre preEvent = new UpdateRecipeButtonsEvent.Pre(
guiRecipe,
this,
this.handlerInfo);
if (MinecraftForge.EVENT_BUS.post(preEvent)) {
return preEvent.buttonList;
} else {
final UpdateRecipeButtonsEvent.Post postEvent = new UpdateRecipeButtonsEvent.Post(
guiRecipe,
this,
getDefaultButtons());
MinecraftForge.EVENT_BUS.post(postEvent);
return postEvent.buttonList;
}
} else {
return getDefaultButtons();
}
}
protected List<GuiRecipeButton> getDefaultButtons() {
GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
final List<GuiRecipeButton> buttons = new ArrayList<>();
final boolean showFavorites = NEIClientConfig.favoritesEnabled() && handlerInfo.getShowFavoritesButton();
final boolean showOverlay = handlerInfo.getShowOverlayButton();
final int x = Math.min(166, this.w) - GuiRecipeButton.BUTTON_WIDTH;
int y = this.h - GuiRecipeButton.BUTTON_HEIGHT - 6;
if (guiContainer instanceof IGuiContainerOverlay overlay) {
guiContainer = overlay.getFirstScreen();
}
if (showOverlay) {
buttons.add(new GuiOverlayButton(guiContainer, this.handlerRef, x, y));
y -= GuiRecipeButton.BUTTON_HEIGHT + 1;
}
if (showFavorites) {
buttons.add(new GuiFavoriteButton(handlerRef, x, y));
}
return buttons;
}
@Override
public void draw(int mouseX, int mouseY) {
final int yShift = this.handlerInfo.getYShift();
if (this.update) {
this.update = false;
if (!NEIClientUtils.shiftKey() && ((this.cycleticks++) / 20) != this.lastcycle || this.lastcycle == -1) {
this.lastcycle = this.cycleticks / 20;
updatePermutations();
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.onPermutationsChanged();
}
}
for (GuiRecipeButton button : getRecipeButtons()) {
button.update();
}
}
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT);
GL11.glTranslatef(this.x, this.y + yShift, 0);
this.handlerRef.handler.drawBackground(this.handlerRef.recipeIndex);
RenderHelper.enableGUIStandardItemLighting();
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
GL11.glColor4f(1, 1, 1, 1);
GuiContainerManager.enableMatrixStackLogging();
for (PositionedStack pStack : getInputs()) {
if (!this.permutations.containsKey(pStack)) {
updatePermutationsFor(pStack);
}
drawItem(pStack, mouseX, mouseY, yShift, true);
}
for (PositionedStack pStack : getCatalysts()) {
if (!this.permutations.containsKey(pStack)) {
updatePermutationsFor(pStack);
}
drawItem(pStack, mouseX, mouseY, yShift, true);
}
for (PositionedStack pStack : getOutputs()) {
drawItem(pStack, mouseX, mouseY, yShift, false);
}
GuiContainerManager.disableMatrixStackLogging();
this.handlerRef.handler.drawForeground(this.handlerRef.recipeIndex);
final GuiRecipeButton overlayButton = forEachButtons(
button -> (this.showAsWidget ? button instanceof GuiOverlayButton : button.contains(mouseX, mouseY))
? button
: null,
null);
if (overlayButton != null) {
NEIClientUtils.gl2DRenderContext(() -> overlayButton.drawItemOverlay());
}
GL11.glTranslatef(-this.x, -this.y - yShift, 0);
GL11.glPopAttrib();
if (!this.showAsWidget) {
final Minecraft mc = NEIClientUtils.mc();
for (GuiRecipeButton button : getRecipeButtons()) {
button.xPosition += this.x;
button.yPosition += this.y;
button.drawButton(mc, mouseX, mouseY);
button.xPosition -= this.x;
button.yPosition -= this.y;
}
}
DebugHandlerWidget.instance.drawGuiPlaceholder(this);
}
protected void drawItem(PositionedStack pStack, int mouseX, int mouseY, int yShift, boolean input) {
pStack.draw(mouseX - this.x, mouseY - this.y - yShift);
if (this.handlerInfo.getShowBadge()) {
drawBadge(pStack, input);
}
if (pStack.contains(mouseX - this.x, mouseY - this.y - yShift)) {
NEIClientUtils.gl2DRenderContext(
() -> GuiDraw.drawRect(pStack.relx, pStack.rely, pStack.width, pStack.height, 0x80FFFFFF));
}
}
protected List<Badge> getBadges(PositionedStack pStack, boolean input) {
return this.badgeCache.computeIfAbsent(pStack, k -> {
final List<Badge> badges = k.getBadges();
if (badges == null) {
if (input) {
if (StackInfo.getAmount(pStack.item) == 0) {
return Collections.singletonList(Badge.notConsumed());
} else if (pStack.getChance() == 0) {
return Collections.singletonList(Badge.notConsumedParallel());
} else if (pStack.getChance() != PositionedStack.CHANCE_FULL) {
return Collections.singletonList(
Badge.consumeChance(pStack.getChance() / (float) PositionedStack.CHANCE_FULL));
}
} else if (pStack.getChance() != PositionedStack.CHANCE_FULL) {
return Collections.singletonList(
Badge.outputChance(pStack.getChance() / (float) PositionedStack.CHANCE_FULL));
}
return Collections.emptyList();
}
return badges;
});
}
protected void drawBadge(PositionedStack pStack, boolean input) {
final List<Badge> badges = getBadges(pStack, input);
for (Badge badge : badges) {
badge.draw(new Rectangle4i(pStack.relx, pStack.rely, pStack.width, pStack.height));
}
}
@Override
public boolean handleKeyPress(int keyID, char keyChar) {
if (NEIClientConfig.favoritesEnabled()
&& KeyManager.isHashDown("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH)) {
final Point mouse = GuiDraw.getMousePosition();
final int yShift = this.handlerInfo.getYShift();
PositionedStack pStackOver = null;
for (PositionedStack pStack : getInputs()) {
if (pStack.contains(mouse.x - this.x, mouse.y - this.y - yShift)) {
pStackOver = pStack;
break;
}
}
if (pStackOver != null) {
if (FavoriteRecipes.containsManual(pStackOver.item)) {
FavoriteRecipes.setFavorite(pStackOver.item, null);
} else {
FavoriteRecipes.setFavorite(
pStackOver.item,
RecipeId.of(
pStackOver.item,
FavoriteRecipes.FAVORITE_ITEM,
Arrays.asList(new ItemStack(Blocks.fire, 1), pStackOver.item)));
}
return true;
}
}
return false;
}
@Override
public void lastKeyTyped(int keyID, char keyChar) {
final Point mouse = GuiDraw.getMousePosition();
forEachButtons(button -> {
if (button.contains(mouse.x, mouse.y)) {
button.lastKeyTyped(keyChar, keyID);
}
return null;
}, null);
}
@Override
public boolean handleClick(int mouseX, int mouseY, int button) {
if (ShortcutInputHandler.handleMouseClick(getStackMouseOver(mouseX, mouseY))) {
return true;
}
if (button == 0) {
final Minecraft mc = NEIClientUtils.mc();
if (forEachButtons(guibutton -> {
if (guibutton.mousePressed(mc, mouseX, mouseY)) {
NEIClientUtils.playClickSound();
guibutton.mouseReleased(mouseX, mouseY);
return true;
}
return null;
}, false)) {
return true;
}
}
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return false;
}
return this.handlerRef.handler.mouseClicked(guiRecipe, button, this.handlerRef.recipeIndex);
}
@Override
public List<String> handleTooltip(int mouseX, int mouseY, List<String> tooltip) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return tooltip;
}
final List<String> localTooltip = this.handlerRef.handler
.handleTooltip(guiRecipe, tooltip, this.handlerRef.recipeIndex);
return forEachButtons(
button -> button.contains(mouseX, mouseY) ? button.handleTooltip(localTooltip) : null,
localTooltip);
}
@Override
public List<String> handleItemTooltip(ItemStack itemstack, int mousex, int mousey, List<String> tooltip) {
final GuiRecipe<?> guiRecipe = getGuiRecipe();
if (guiRecipe == null) {
return tooltip;
}
tooltip = this.handlerRef.handler.handleItemTooltip(guiRecipe, itemstack, tooltip, this.handlerRef.recipeIndex);
if (itemstack != null) {
final PositionedStack hovered = getPositionedStackMouseOver(mousex, mousey);
if (hovered != null) {
if (this.handlerInfo.getShowBadge()) {
final List<Badge> badges = getBadges(hovered, getOutputs().indexOf(hovered) == -1);
for (Badge badge : badges) {
if (badge.getTooltip() != null && !badge.getTooltip().isEmpty()) {
tooltip.addAll(badge.getTooltip());
}
}
}
final List<String> customTooltip = hovered.getTooltip();
if (customTooltip != null && !customTooltip.isEmpty()) {
tooltip.addAll(customTooltip);
}
}
if (hovered == null || !NEIClientConfig.showCycledIngredientsTooltip()
|| this.permutations.getOrDefault(hovered, Collections.emptyList()).size() <= 1
|| getOutputs().indexOf(hovered) != -1) {
this.acceptsFollowingTooltipLineHandler = null;
} else if (this.acceptsFollowingTooltipLineHandler == null
|| this.acceptsFollowingTooltipLineHandler.tooltipGUID != hovered) {
this.acceptsFollowingTooltipLineHandler = AcceptsFollowingTooltipLineHandler
.of(hovered, this.permutations.get(hovered), hovered.item);
if (this.acceptsFollowingTooltipLineHandler != null && hovered.getAcceptsLabel() != null) {
this.acceptsFollowingTooltipLineHandler.setLabel(hovered.getAcceptsLabel());
}
}
} else if (this.acceptsFollowingTooltipLineHandler != null) {
this.acceptsFollowingTooltipLineHandler = null;
}
if (this.acceptsFollowingTooltipLineHandler != null) {
tooltip.add(GuiDraw.TOOLTIP_HANDLER + GuiDraw.getTipLineId(this.acceptsFollowingTooltipLineHandler));
}
return tooltip;
}
@Override
public Map<String, String> handleHotkeys(int mouseX, int mouseY, Map<String, String> hotkeys) {
final int yShift = this.handlerInfo.getYShift();
final Map<String, String> buttonHotkeys = forEachButtons(
button -> button.contains(mouseX, mouseY) ? button.handleHotkeys(mouseX, mouseY, hotkeys) : null,
hotkeys);
if (this.acceptsFollowingTooltipLineHandler != null) {
buttonHotkeys.put(
NEIClientUtils.getKeyName(
NEIClientUtils.SHIFT_HASH,
NEIMouseUtils.MOUSE_BTN_NONE + NEIMouseUtils.MOUSE_SCROLL),
NEIClientUtils.translate("recipe.accepts.scroll"));
}
if (getInputs().stream().anyMatch(
pStack -> this.permutations.getOrDefault(pStack, Collections.emptyList()).size() > 1
&& pStack.contains(mouseX - this.x, mouseY - this.y - yShift))) {
hotkeys.put(
KeyManager.getKeyName("bookmark.favorite_item", NEIClientUtils.SHIFT_HASH),
NEIClientUtils.translate("recipe.favorite.toggle"));
}
return buttonHotkeys;
}
@Override
public boolean onMouseWheel(int scroll, int mx, int my) {
if (forEachButtons(button -> button.contains(mx, my) ? button.mouseScrolled(scroll) : null, false)) {
return true;
}
if (scrollPermutations(scroll, mx, my)) {
return true;
}
final GuiRecipe<?> guiRecipe = getGuiRecipe();
return guiRecipe != null
&& this.handlerRef.handler.mouseScrolled(guiRecipe, scroll, this.handlerRef.recipeIndex);
}
protected boolean scrollPermutations(int scroll, int mx, int my) {
if (!NEIClientUtils.shiftKey()) return false;
final PositionedStack overStack = getPositionedStackMouseOver(mx, my);
if (overStack != null && this.permutations.getOrDefault(overStack, Collections.emptyList()).size() > 1) {
final List<ItemStack> items = this.permutations.get(overStack);
final int stackIndex = indexOf(items, overStack.item);
final ItemStack stack = items.get((items.size() - scroll + stackIndex) % items.size());
Stream.concat(getInputs().stream(), getCatalysts().stream()).filter(pStack -> pStack.containsWithNBT(stack))
.forEach(pStack -> pStack.setPermutationToRender(stack));
if (this.acceptsFollowingTooltipLineHandler != null) {
this.acceptsFollowingTooltipLineHandler
.setActiveStack(((PositionedStack) this.acceptsFollowingTooltipLineHandler.tooltipGUID).item);
}
return true;
}
return false;
}
protected int indexOf(List<ItemStack> list, ItemStack stack) {
for (int i = 0; i < list.size(); i++) {
if (NEIServerUtils.areStacksSameTypeCraftingWithNBT(list.get(i), stack)) {
return i;
}
}
return -1;
}
protected <R> R forEachButtons(Function<GuiRecipeButton, R> callback, R defaultValue) {
for (GuiRecipeButton button : getRecipeButtonsIfInit()) {
button.xPosition += this.x;
button.yPosition += this.y;
final R result = callback.apply(button);
button.xPosition -= this.x;
button.yPosition -= this.y;
if (result != null) {
return result;
}
}
return defaultValue;
}
public boolean isFocusedRecipe(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStackOver : getOutputs()) {
if (pStackOver.contains(mx - this.x, my - this.y - yShift)) {
return true;
}
}
return false;
}
public PositionedStack getPositionedStackMouseOver(int mx, int my) {
final int yShift = this.handlerInfo.getYShift();
for (PositionedStack pStack : getInputs()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
for (PositionedStack pStack : getCatalysts()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
for (PositionedStack pStack : getOutputs()) {
if (pStack.contains(mx - this.x, my - this.y - yShift)) {
return pStack;
}
}
return null;
}
@Override
public ItemStack getStackMouseOver(int mx, int my) {
final PositionedStack pStack = getPositionedStackMouseOver(mx, my);
return pStack != null ? pStack.item : null;
}
protected GuiRecipe<?> getGuiRecipe() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
if (guiContainer instanceof GuiRecipe<?>guiRecipe) {
return guiRecipe;
}
return null;
}
protected GuiContainer getFirstGui() {
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
if (guiContainer instanceof GuiRecipe<?>guiRecipe) {
return guiRecipe.firstGui;
}
return guiContainer;
}
protected void updatePermutations() {
final int rev = FavoriteRecipes.getRevision();
if (this.favoriteRevision != rev) {
this.favoriteRevision = rev;
this.acceptsFollowingTooltipLineHandler = null;
this.favoriteIndexes.clear();
this.permutations.clear();
this.badgeCache.clear();
}
for (PositionedStack pStack : getInputs()) {
updatePermutationsFor(pStack);
}
for (PositionedStack pStack : getCatalysts()) {
updatePermutationsFor(pStack);
}
if (this.acceptsFollowingTooltipLineHandler != null) {
this.acceptsFollowingTooltipLineHandler
.setActiveStack(((PositionedStack) this.acceptsFollowingTooltipLineHandler.tooltipGUID).item);
}
}
private void updatePermutationsFor(PositionedStack pStack) {
List<ItemStack> perms = this.permutations.get(pStack);
int favoriteIndex = this.favoriteIndexes.getOrDefault(pStack, -1);
boolean needsUpdate = false;
if (perms == null) {
perms = pStack.getFilteredPermutations();
favoriteIndex = -1;
if (perms.size() > 1) {
for (ItemStack item : perms) {
if (!FavoriteRecipes.containsManual(item)) break;
favoriteIndex++;
}
}
this.permutations.put(pStack, perms);
this.favoriteIndexes.put(pStack, favoriteIndex);
needsUpdate = true;
}
if (perms.size() > 1 || needsUpdate) {
final int size = favoriteIndex != -1 ? favoriteIndex + 1 : perms.size();
if (size > 0) {
pStack.setPermutationToRender(perms.get(this.lastcycle % size));
}
}
}
protected List<PositionedStack> getInputs() {
return this.handlerRef.handler.getIngredientStacks(this.handlerRef.recipeIndex);
}
protected List<PositionedStack> getOutputs() {
final PositionedStack pStackResult = this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex);
return pStackResult != null ? Arrays.asList(pStackResult)
: this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex);
}
protected List<PositionedStack> getCatalysts() {
if (this.handlerRef.handler.getResultStack(this.handlerRef.recipeIndex) == null) {
return Collections.emptyList();
}
return this.handlerRef.handler.getOtherStacks(this.handlerRef.recipeIndex);
}
}
@@ -4,7 +4,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import net.minecraft.item.Item;
@@ -43,7 +45,13 @@ public class Recipe {
if (!isShapedRecipe()) {
// sort to ignore ingredients position in shapeless recipes
this.ingredients.sort(Comparator.comparing(StackInfo::getItemStackGUID));
final Map<ItemStack, String> guids = new IdentityHashMap<>();
for (ItemStack ingredient : this.ingredients) {
guids.put(ingredient, StackInfo.getItemStackGUID(ingredient));
}
this.ingredients.sort(Comparator.comparing(guids::get));
}
}
@@ -52,7 +60,10 @@ public class Recipe {
}
public static RecipeId of(IRecipeHandler handler, int recipeIndex) {
final List<PositionedStack> ingredients = handler.getIngredientStacks(recipeIndex);
return of(handler, recipeIndex, handler.getIngredientStacks(recipeIndex));
}
public static RecipeId of(IRecipeHandler handler, int recipeIndex, List<PositionedStack> ingredients) {
final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName();
PositionedStack pStackResult = handler.getResultStack(recipeIndex);
@@ -1,5 +1,6 @@
package codechicken.nei.recipe;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
@@ -36,12 +37,12 @@ public class RecipeHandlerRef {
.getCraftingHandlers("recipeId", recipeId.getResult(), recipeId);
for (ICraftingHandler handler : handlers) {
int refIndex = SearchRecipeHandler.findFirst(
int recipeIndex = SearchRecipeHandler.findFirst(
handler,
recipeIndex -> recipeId.equalsIngredients(handler.getIngredientStacks(recipeIndex)));
index -> recipeId.equalsIngredients(handler.getIngredientStacks(index)));
if (refIndex >= 0) {
return new RecipeHandlerRef(handler, refIndex);
if (recipeIndex >= 0) {
return new RecipeHandlerRef(handler, recipeIndex);
}
}
@@ -66,10 +67,6 @@ public class RecipeHandlerRef {
return firstGui != null && firstGui.inventorySlots != null ? firstGui : null;
}
public NEIRecipeWidget getRecipeWidget() {
return new NEIRecipeWidget(this);
}
public IOverlayHandler getOverlayHandler(GuiContainer gui) {
final GuiContainer firstGui = prepareGui(gui);
@@ -171,6 +168,8 @@ public class RecipeHandlerRef {
return transferAlghoritmCache.computeIfAbsent(overlayHandler.getClass(), clazz -> {
try {
final Method transferRecipe = overlayHandler.getClass()
.getMethod("transferRecipe", GuiContainer.class, IRecipeHandler.class, int.class, int.class);
if (overlayHandler instanceof DefaultOverlayHandler) {
final Class<?> overlayRecipeClazz = overlayHandler.getClass()
@@ -182,14 +181,11 @@ public class RecipeHandlerRef {
boolean.class)
.getDeclaringClass();
if (overlayRecipeClazz.equals(DefaultOverlayHandler.class)) {
return true;
}
return overlayRecipeClazz.equals(DefaultOverlayHandler.class)
|| !transferRecipe.getDeclaringClass().equals(DefaultOverlayHandler.class);
}
return !overlayHandler.getClass()
.getMethod("transferRecipe", GuiContainer.class, IRecipeHandler.class, int.class, int.class)
.isDefault();
return !transferRecipe.isDefault();
} catch (NoSuchMethodException e) {}
return false;
@@ -7,14 +7,10 @@ import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
import codechicken.nei.FavoriteRecipes;
import codechicken.nei.ItemPanels;
import codechicken.nei.ItemsGrid.ItemsGridSlot;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.api.ShortcutInputHandler;
import codechicken.nei.bookmark.BookmarkItem.BookmarkItemType;
import codechicken.nei.bookmark.BookmarksGridSlot;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.guihook.IContainerInputHandler;
@@ -58,7 +54,9 @@ public class RecipeItemInputHandler implements IContainerInputHandler, IContaine
List<String> currenttip) {
if (itemstack != null) {
final RecipeId recipeId = getHoveredRecipeId(gui, itemstack, mousex, mousey);
final HoveredRecipe hovered = HoveredRecipe.of(gui, itemstack);
final RecipeId recipeId = hovered != null && showRecipeTooltip(hovered, mousex, mousey) ? hovered.recipeId
: null;
if (recipeId == null) {
recipeTooltipLineHandler = null;
@@ -76,49 +74,21 @@ public class RecipeItemInputHandler implements IContainerInputHandler, IContaine
return currenttip;
}
private RecipeId getHoveredRecipeId(GuiContainer gui, ItemStack itemstack, int mousex, int mousey) {
private boolean showRecipeTooltip(HoveredRecipe hovered, int mousex, int mousey) {
return switch (hovered.source) {
case BOOKMARK -> !hovered.favorite && showRecipeTooltipInBookmarks(mousex, mousey);
case ITEM_PANEL -> NEIClientConfig.showRecipeTooltipInPanel();
case RECIPE_GUI -> hovered.favorite && NEIClientConfig.showRecipeTooltipInGui();
default -> false;
};
}
if (ItemPanels.bookmarkPanel.contains(mousex, mousey)) {
final BookmarksGridSlot slot = ItemPanels.bookmarkPanel.getSlotMouseOver(mousex, mousey);
if (slot != null && slot.getRecipeId() != null
&& slot.getType() != BookmarkItemType.INGREDIENT
&& NEIClientConfig.getRecipeTooltipsMode() != 0) {
final int tooltipMode = NEIClientConfig.getRecipeTooltipsMode();
final BookmarkViewMode viewMode = slot.getGroup().viewMode;
if (tooltipMode == 3 || viewMode == BookmarkViewMode.DEFAULT && tooltipMode == 1
|| viewMode == BookmarkViewMode.TODO_LIST && tooltipMode == 2) {
return slot.getRecipeId();
}
}
return null;
}
if (NEIClientConfig.showRecipeTooltipInPanel()) {
ItemsGridSlot panelSlot = ItemPanels.itemPanel.getSlotMouseOver(mousex, mousey);
if (panelSlot == null) {
panelSlot = ItemPanels.itemPanel.historyPanel.getSlotMouseOver(mousex, mousey);
}
if (panelSlot == null) {
panelSlot = ItemPanels.itemPanel.craftablesPanel.getSlotMouseOver(mousex, mousey);
}
if (panelSlot != null) {
return panelSlot.getRecipeId();
}
}
if (gui instanceof GuiRecipe<?>guiRecipe
&& new Rectangle4i(gui.guiLeft, gui.guiTop, gui.xSize, gui.ySize).contains(mousex, mousey)
&& NEIClientConfig.showRecipeTooltipInGui()
&& guiRecipe.getFocusedRecipe() == null) {
return FavoriteRecipes.getFavorite(itemstack);
}
return null;
private boolean showRecipeTooltipInBookmarks(int mousex, int mousey) {
final BookmarksGridSlot slot = ItemPanels.bookmarkPanel.getSlotMouseOver(mousex, mousey);
final int tooltipMode = NEIClientConfig.getRecipeTooltipsMode();
final BookmarkViewMode viewMode = slot.getGroup().viewMode;
return tooltipMode == 3 || viewMode == BookmarkViewMode.DEFAULT && tooltipMode == 1
|| viewMode == BookmarkViewMode.TODO_LIST && tooltipMode == 2;
}
@Override
@@ -0,0 +1,295 @@
package codechicken.nei.recipe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumChatFormatting;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.CompositeTooltipLineHandler;
import codechicken.nei.ItemStackAmount;
import codechicken.nei.ItemsTooltipLineHandler;
import codechicken.nei.ItemsTooltipLineHandler.StacksAmountRenderer;
import codechicken.nei.ItemsTooltipLineHandler.TotalAmountRenderer;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.PositionedStack;
import codechicken.nei.api.IGuiContainerOverlay;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.Recipe.RecipeIngredient;
import codechicken.nei.util.ReadableNumberConverter;
public class RecipeItemsTooltipLineHandler extends CompositeTooltipLineHandler {
protected static class ChanceTotalAmountRenderer extends TotalAmountRenderer {
protected final Set<ItemStack> approximate;
public ChanceTotalAmountRenderer(Set<ItemStack> approximate) {
this.approximate = approximate;
}
@Override
protected String formatAmount(ItemStack stack, long amount) {
final String text = super.formatAmount(stack, amount);
if (!this.approximate.contains(stack)) {
return text;
} else if (!text.isEmpty()) {
return EnumChatFormatting.GOLD + "~" + text;
} else if (amount == 0) {
return EnumChatFormatting.GOLD + "~0";
}
return text;
}
}
protected static class ChanceStacksAmountRenderer extends StacksAmountRenderer {
protected static final int APPROXIMATE_COLOR = 0xFFAA00;
protected final Set<ItemStack> approximate;
public ChanceStacksAmountRenderer(Set<ItemStack> approximate) {
this.approximate = approximate;
}
@Override
protected void drawAmount(Rectangle4i rect, ItemStack stack, long amount) {
if (amount == 0) {
Badge.notConsumed().draw(rect);
} else {
super.drawAmount(rect, stack, amount);
}
}
@Override
protected String getPrefix(ItemStack stack) {
return this.approximate.contains(stack) ? "~" : "";
}
@Override
protected int getColor(ItemStack stack) {
return this.approximate.contains(stack) ? APPROXIMATE_COLOR : super.getColor(stack);
}
}
protected final RecipeId recipeId;
protected final long multiplier;
protected boolean useInventory = false;
protected String title = "";
public RecipeItemsTooltipLineHandler(RecipeId recipeId, long multiplier) {
this.recipeId = recipeId;
this.multiplier = multiplier;
}
public RecipeItemsTooltipLineHandler setUseInventory(boolean useInventory) {
this.useInventory = useInventory;
this.created = false;
return this;
}
public RecipeId getRecipeId() {
return this.recipeId;
}
public long getMultiplier() {
return this.multiplier;
}
public boolean isUseInventory() {
return this.useInventory;
}
public String getTitle() {
ensureCreated();
return this.title;
}
protected static int getIngredientOrder(ItemStack stack) {
return StackInfo.isFluidDisplayItem(stack) ? 2 : stack.stackSize == 0 ? 0 : 1;
}
@Override
protected void createLines() {
final RecipeHandlerRef handlerRef = RecipeHandlerRef.of(this.recipeId);
if (handlerRef == null) {
return;
}
final Recipe recipe = Recipe.of(handlerRef);
final Set<NBTTagCompound> approximateResults = new HashSet<>();
final Set<NBTTagCompound> approximateIngredients = new HashSet<>();
final List<ItemStack> results = multiplyItems(recipe.getResults(), false, approximateResults);
final List<ItemStack> ingredients = multiplyItems(recipe.getIngredients(), true, approximateIngredients);
final ItemStackAmount ingredientsAmount = ItemStackAmount.of(ingredients);
ingredients.sort(Comparator.comparingInt(RecipeItemsTooltipLineHandler::getIngredientOrder));
this.title = handlerRef.handler.getRecipeName().trim() + " (x"
+ ReadableNumberConverter.INSTANCE.toWideReadableForm(this.multiplier)
+ ")";
addResultLine(results, approximateResults);
addRequiredLine(ingredients, approximateIngredients);
if (this.useInventory) {
final List<ItemStack> needed = subtractItems(ingredientsAmount, getInventoryItems());
final ItemStackAmount neededAmount = ItemStackAmount.of(needed);
final ItemStackAmount consumedAmount = ItemStackAmount.of(ingredientsAmount);
consumedAmount.removeIf(entry -> entry.getValue() <= 0);
if (!neededAmount.equals(consumedAmount)) {
addNeededLine(
needed,
approximateIngredients,
needed.size() == 1 || ingredientsAmount.size() + neededAmount.size() <= maxLineRows());
}
}
}
protected void addResultLine(List<ItemStack> items, Set<NBTTagCompound> approximate) {
final String label = NEIClientUtils.translate("recipe.items.results");
final int maxRows = maxLineRows();
final int maxNamedRows = items.size() == 1 ? NEIClientConfig.getMaxVerticalTooltipRows() : 0;
final ItemsTooltipLineHandler line = ItemsTooltipLineHandler.of(label, items, maxRows, maxNamedRows);
if (line.isEmpty()) {
return;
}
line.setLabelColor(EnumChatFormatting.GREEN);
final Set<ItemStack> approximateItems = Collections.newSetFromMap(new IdentityHashMap<>());
for (ItemStack stack : line.getItems()) {
if (approximate.contains(StackInfo.itemStackToNBT(stack, false))) {
approximateItems.add(stack);
}
}
line.setAmountRenderer(new ChanceTotalAmountRenderer(approximateItems));
this.lines.add(line);
}
protected void addRequiredLine(List<ItemStack> items, Set<NBTTagCompound> approximate) {
final String label = NEIClientUtils.translate("recipe.items.ingredients");
final int maxRows = maxLineRows();
final int maxNamedRows = NEIClientConfig.getMaxVerticalTooltipRows();
final ItemsTooltipLineHandler line = ItemsTooltipLineHandler.of(label, items, maxRows, maxNamedRows);
if (line.isEmpty()) {
return;
}
line.setLabelColor(EnumChatFormatting.YELLOW);
final Set<ItemStack> approximateItems = Collections.newSetFromMap(new IdentityHashMap<>());
for (ItemStack stack : line.getItems()) {
if (approximate.contains(StackInfo.itemStackToNBT(stack, false))) {
approximateItems.add(stack);
}
}
line.setAmountRenderer(new ChanceStacksAmountRenderer(approximateItems));
this.lines.add(line);
}
protected void addNeededLine(List<ItemStack> items, Set<NBTTagCompound> approximate, boolean vertical) {
final String label = NEIClientUtils.translate("recipe.items.ingredients_needed");
final int maxRows = maxLineRows();
final int maxNamedRows = vertical ? NEIClientConfig.getMaxVerticalTooltipRows() : 0;
final ItemsTooltipLineHandler line = ItemsTooltipLineHandler.of(label, items, maxRows, maxNamedRows);
if (line.isEmpty()) {
return;
}
line.setLabelColor(EnumChatFormatting.RED);
final Set<ItemStack> approximateItems = Collections.newSetFromMap(new IdentityHashMap<>());
for (ItemStack stack : line.getItems()) {
if (approximate.contains(StackInfo.itemStackToNBT(stack, false))) {
approximateItems.add(stack);
}
}
line.setAmountRenderer(new ChanceStacksAmountRenderer(approximateItems));
this.lines.add(line);
}
protected List<ItemStack> multiplyItems(List<RecipeIngredient> items, boolean ingredients,
Set<NBTTagCompound> approximate) {
final ItemStackAmount chanceAmounts = new ItemStackAmount();
final List<ItemStack> result = new ArrayList<>();
for (RecipeIngredient item : items) {
chanceAmounts.add(item.getItemStack(), (long) item.getAmount() * item.getChance());
}
for (Map.Entry<NBTTagCompound, Long> entry : chanceAmounts.entrySet()) {
final long amount = entry.getValue() * this.multiplier;
final long entryAmount = ingredients
? (amount + PositionedStack.CHANCE_FULL - 1) / PositionedStack.CHANCE_FULL
: amount / PositionedStack.CHANCE_FULL;
final ItemStack stack = StackInfo.loadFromNBT(entry.getKey(), entryAmount);
if (amount % PositionedStack.CHANCE_FULL != 0) {
approximate.add(entry.getKey());
}
result.add(stack);
}
return result;
}
protected static ItemStackAmount getInventoryItems() {
GuiScreen screen = NEIClientUtils.mc().currentScreen;
if (screen instanceof IGuiContainerOverlay overlay) {
screen = overlay.getFirstScreen();
}
if (screen instanceof GuiContainer container) {
return AutoCraftingManager.getInventoryItems(container);
}
return new ItemStackAmount();
}
protected static List<ItemStack> subtractItems(ItemStackAmount items, ItemStackAmount subtrahend) {
final List<ItemStack> result = new ArrayList<>();
for (Map.Entry<NBTTagCompound, Long> entry : items.entrySet()) {
final ItemStack stack = StackInfo.loadFromNBT(entry.getKey(), 0);
final long amount = entry.getValue() - subtrahend.getOrDefault(stack, 0L);
if (amount > 0) {
result.add(StackInfo.withAmount(stack, amount));
}
}
return result;
}
}
@@ -2,10 +2,15 @@ package codechicken.nei.recipe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.item.ItemStack;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.Widget;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.ScrollContainer;
public class RecipePageManager {
@@ -16,7 +21,6 @@ public class RecipePageManager {
private String cacheKey = "";
private int currentPageIndex = -1;
private List<Integer> recipeIndices = new ArrayList<>();
private List<List<Widget>> pageWidgets = new ArrayList<>();
public RecipePageManager(SearchRecipeHandler<?> handler, HandlerInfo handlerInfo, ScrollContainer container) {
@@ -40,6 +44,8 @@ public class RecipePageManager {
}
this.cacheKey = cacheKey;
final List<RecipeWidget> pausedWidgets = getPausedWidgets();
this.pageWidgets.clear();
final boolean allowOverflowY = this.handlerInfo.isAllowOverflowY();
@@ -52,12 +58,37 @@ public class RecipePageManager {
final int width = Math.max(166, this.handlerInfo.getWidth());
final List<Integer> searchRecipes = this.handler.getFilteredRecipes();
final int numRecipes = searchRecipes.size();
final List<List<Integer>> groupedRecipes = new ArrayList<>();
final Map<Object, Integer> groupEndIndices = new HashMap<>();
List<Widget> widgets = new ArrayList<>();
int shiftY = 0;
for (int i = 0; i < numRecipes; i++) {
final int recipeIndex = searchRecipes.get(i);
final NEIRecipeWidget widget = RecipeHandlerRef.of(this.handler.original, recipeIndex).getRecipeWidget();
final Object groupId = this.handler.original.getRecipeGroupId(recipeIndex);
final int groupIndex = groupId != null
? groupEndIndices.computeIfAbsent(groupId, k -> groupedRecipes.size())
: groupedRecipes.size();
if (groupedRecipes.size() <= groupIndex) {
groupedRecipes.add(new ArrayList<>());
}
groupedRecipes.get(groupIndex).add(recipeIndex);
}
for (int groupIndex = 0; groupIndex < groupedRecipes.size(); groupIndex++) {
final RecipeWidget widget = new RecipeWidget(this.handler.original, groupedRecipes.get(groupIndex));
// keep the pause of the rebuilt widgets
for (RecipeWidget paused : pausedWidgets) {
final int pausedIndex = paused.getRecipeHandlerRef().recipeIndex;
if (widget.containsRecipeIndex(pausedIndex)) {
widget.pinRecipe(pausedIndex, paused.getPinnedIngredients());
}
}
widget.w = width;
if (allowOverflowY) {
@@ -109,29 +140,48 @@ public class RecipePageManager {
}
this.currentPageIndex = page;
this.recipeIndices = new ArrayList<>();
for (Widget widget : getCurrentPageWidgets()) {
if (widget instanceof NEIRecipeWidget recipeWidget) {
this.recipeIndices.add(recipeWidget.getRecipeHandlerRef().recipeIndex);
}
}
this.container.setVerticalScrollOffset(0);
}
public void gotoRefIndex(int refIndex) {
int page = 0;
public boolean gotoRecipeIndex(int recipeIndex) {
for (int i = 0; i < this.pageWidgets.size(); i++) {
refIndex -= this.pageWidgets.get(i).size();
if (refIndex < 0) {
page = i;
break;
for (int page = 0; page < this.pageWidgets.size(); page++) {
for (Widget widget : this.pageWidgets.get(page)) {
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.containsRecipeIndex(recipeIndex)) {
changePage(page - this.currentPageIndex);
return true;
}
}
}
changePage(page);
return false;
}
private List<RecipeWidget> getPausedWidgets() {
final List<RecipeWidget> paused = new ArrayList<>();
for (List<Widget> widgets : this.pageWidgets) {
for (Widget widget : widgets) {
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.isPaused()) {
paused.add(recipeWidget);
}
}
}
return paused;
}
public boolean pinRecipe(int recipeIndex, List<ItemStack> ingredients) {
for (List<Widget> widgets : this.pageWidgets) {
for (Widget widget : widgets) {
if (widget instanceof RecipeWidget recipeWidget && recipeWidget.containsRecipeIndex(recipeIndex)) {
return recipeWidget.pinRecipe(recipeIndex, ingredients);
}
}
}
return false;
}
public List<Widget> getCurrentPageWidgets() {
@@ -143,7 +193,7 @@ public class RecipePageManager {
int shiftY = 0;
for (Widget widget : currentWidgets) {
if (widget instanceof NEIRecipeWidget recipeWidget) {
if (widget instanceof RecipeWidget recipeWidget) {
recipeWidget.setLocation(2, shiftY);
}
shiftY += widget.h;
@@ -153,7 +203,20 @@ public class RecipePageManager {
}
public List<Integer> getRecipeIndices() {
return this.recipeIndices;
if (this.pageWidgets.isEmpty()) {
return Collections.emptyList();
}
final List<Integer> recipeIndices = new ArrayList<>();
for (Widget widget : this.pageWidgets.get(this.currentPageIndex)) {
if (widget instanceof RecipeWidget recipeWidget) {
recipeIndices.add(recipeWidget.getRecipeHandlerRef().recipeIndex);
}
}
return recipeIndices;
}
}
@@ -12,7 +12,9 @@ import codechicken.nei.drawable.DrawableBuilder;
import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.widget.RecipeWidget;
import codechicken.nei.scroll.GuiHelper;
import codechicken.nei.util.ReadableNumberConverter;
public class RecipeTooltipLineHandler implements ITooltipLineHandler {
@@ -23,18 +25,30 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
0,
0,
176 + TRANSPARENCY_BORDER * 2,
166 + TRANSPARENCY_BORDER * 2).build();
166 + TRANSPARENCY_BORDER * 2)
.setSlices(
BG_PADDING + TRANSPARENCY_BORDER,
BG_PADDING + TRANSPARENCY_BORDER,
BG_PADDING + TRANSPARENCY_BORDER,
BG_PADDING + TRANSPARENCY_BORDER)
.build();
protected long lastUpdate = System.currentTimeMillis();
protected String recipeName = "";
protected GuiRecipe<?> gui = null;
protected RecipeHandlerRef handlerRef = null;
protected NEIRecipeWidget widget = null;
protected RecipeWidget 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 +59,10 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
return this.recipeId;
}
public long getMultiplier() {
return this.multiplier;
}
@Override
public Dimension getSize() {
@@ -52,13 +70,13 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
this.handlerRef = RecipeHandlerRef.of(this.recipeId);
this.createdGui = true;
if (handlerRef != null) {
this.widget = new NEIRecipeWidget(this.handlerRef);
if (this.handlerRef != null) {
this.widget = new RecipeWidget(this.handlerRef);
this.widget.showAsWidget(true);
this.widget.x = BG_PADDING;
this.widget.y = BG_PADDING + 12;
this.recipeName = this.widget.getRecipeHandlerRef().handler.getRecipeName().trim();
this.recipeName = this.handlerRef.handler.getRecipeName().trim();
}
}
@@ -93,17 +111,21 @@ public class RecipeTooltipLineHandler implements ITooltipLineHandler {
-TRANSPARENCY_BORDER,
-TRANSPARENCY_BORDER,
size.width + TRANSPARENCY_BORDER * 2,
size.height + TRANSPARENCY_BORDER * 2,
BG_PADDING + TRANSPARENCY_BORDER,
BG_PADDING + TRANSPARENCY_BORDER,
BG_PADDING + TRANSPARENCY_BORDER,
BG_PADDING + TRANSPARENCY_BORDER);
size.height + TRANSPARENCY_BORDER * 2);
GL11.glEnable(GL11.GL_ALPHA_TEST);
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,
@@ -2,13 +2,17 @@ package codechicken.nei.recipe;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import net.minecraft.client.gui.GuiRepair;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import codechicken.nei.ItemList;
@@ -35,14 +39,17 @@ public class RepairRecipeHandler extends TemplateRecipeHandler {
private static class ItemStackKey {
private final ItemStack stack;
private final int hashCode;
public ItemStackKey(ItemStack stack) {
this.stack = stack;
this.hashCode = stack.isItemStackDamageable() ? stack.getItem().hashCode()
: 31 * stack.getItem().hashCode() + stack.getItemDamage();
}
@Override
public int hashCode() {
return stack.getItem().hashCode();
return this.hashCode;
}
@Override
@@ -104,22 +111,48 @@ public class RepairRecipeHandler extends TemplateRecipeHandler {
reloadRepairRecipesTask.restart();
}
private void buildCache() {
final Map<ItemStackKey, RepairPair> recipes = new HashMap<>();
private static final Set<Integer> knownBrokenItemTypes = ConcurrentHashMap.newKeySet();
private static volatile List<ItemStack> uniqueMaterials;
for (ItemStack itemstack1 : ItemList.items) {
recipes.computeIfAbsent(new ItemStackKey(itemstack1), key -> this.getRepairRecipe(key.stack));
private void buildCache() {
if (cachedRecipes != null) return;
uniqueMaterials = collectUniqueMaterials();
final Map<ItemStackKey, RepairPair> recipes = new ConcurrentHashMap<>();
try {
ItemList.forkJoinPool.submit(
() -> ItemList.items.parallelStream().forEach(
itemstack1 -> recipes
.computeIfAbsent(new ItemStackKey(itemstack1), key -> getRepairRecipe(key.stack))))
.get();
} catch (InterruptedException | ExecutionException e) {
NEIClientConfig.logger.error("Failed to build repair recipe cache", e);
}
cachedRecipes = recipes.values().stream().filter(recipe -> recipe != null).collect(Collectors.toList());
}
private static List<ItemStack> collectUniqueMaterials() {
final Map<Long, ItemStack> unique = new LinkedHashMap<>();
for (ItemStack stack : ItemList.items) {
final long key = ((long) Item.getIdFromItem(stack.getItem()) << 32) | (stack.getItemDamage() & 0xFFFFFFFFL);
unique.putIfAbsent(key, stack);
}
return new ArrayList<>(unique.values());
}
private RepairPair getRepairRecipe(ItemStack itemstack1) {
if (!itemstack1.isItemStackDamageable()) return null;
final List<ItemStack> permutations = new ArrayList<>();
final int itemId = Item.getIdFromItem(itemstack1.getItem());
if (knownBrokenItemTypes.contains(itemId)) return null;
for (ItemStack itemstack2 : ItemList.items) {
final List<ItemStack> permutations = new ArrayList<>();
final List<ItemStack> candidates = uniqueMaterials != null ? uniqueMaterials : collectUniqueMaterials();
for (ItemStack itemstack2 : candidates) {
try {
if (itemstack1.getItem().getIsRepairable(itemstack1, itemstack2)) {
final ItemStack right = itemstack2.copy();
@@ -127,7 +160,8 @@ public class RepairRecipeHandler extends TemplateRecipeHandler {
permutations.add(right);
}
} catch (Exception e) {
// Catch exceptions from mods that don't properly implement getIsRepairable
knownBrokenItemTypes.add(itemId);
break;
}
}
@@ -52,15 +52,10 @@ class SearchRecipeHandler<H extends IRecipeHandler> {
public static int findFirst(IRecipeHandler handler, IntPredicate predicate) {
final IRecipeFilter filter = searchingAvailable(handler) ? GuiRecipe.getRecipeListFilter() : null;
int refIndex = -1;
for (int recipeIndex = 0; recipeIndex < handler.numRecipes(); recipeIndex++) {
if (filter == null || mathRecipe(handler, recipeIndex, filter)) {
refIndex++;
if (predicate.test(recipeIndex)) {
return refIndex;
}
if ((filter == null || mathRecipe(handler, recipeIndex, filter)) && predicate.test(recipeIndex)) {
return recipeIndex;
}
}
@@ -74,20 +69,18 @@ class SearchRecipeHandler<H extends IRecipeHandler> {
}
List<Integer> filtered = null;
final List<Integer> recipes = IntStream.range(0, filteredRecipes.size()).boxed()
.collect(Collectors.toCollection(ArrayList::new));
final List<Integer> recipes = IntStream.range(0, filteredRecipes.size()).boxed().collect(Collectors.toList());
try {
filtered = ItemList.forkJoinPool.submit(
() -> recipes.parallelStream()
.filter(recipe -> mathRecipe(this.original, filteredRecipes.get(recipe), filter))
.collect(Collectors.toCollection(ArrayList::new)))
.collect(Collectors.toList()))
.get();
filtered.sort((a, b) -> a - b);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return filtered;
@@ -28,6 +28,10 @@ import codechicken.nei.util.NBTHelper;
public class StackInfo {
/// Prefix of the MaterialLib reference form `ml:<MaterialName>:<shapeToken>`, which config entries carry in
/// place of an id and metadata.
public static final String MATERIALLIB_PREFIX = "ml:";
private static final FluidStack NULL_FLUID = new FluidStack(FluidRegistry.WATER, 0);
public static final List<IStackStringifyHandler> stackStringifyHandlers = new ArrayList<>();
private static final Map<String, HashMap<String, String[]>> guidfilters = new HashMap<>();
@@ -59,7 +63,7 @@ public class StackInfo {
if (nbtTag != null) {
nbtTag = (NBTTagCompound) nbtTag.copy();
nbtTag.setInteger("Count", (int) Math.max(Math.min(customCount, Integer.MAX_VALUE), 0));
nbtTag.setLong("Count", Math.max(customCount, 0));
return loadFromNBT(nbtTag);
}
@@ -126,7 +130,22 @@ public class StackInfo {
}
public static boolean equalItemAndNBT(ItemStack stackA, ItemStack stackB, boolean useNBT) {
if (stackA == null || stackB == null || stackA.getItem() != stackB.getItem()) {
if (stackA == null || stackB == null) {
return false;
}
if (isFluidDisplayItem(stackA)) {
if (isFluidDisplayItem(stackB)) {
final FluidStack fluidA = getFluid(stackA);
final FluidStack fluidB = getFluid(stackB);
return fluidA != null && fluidA.isFluidEqual(fluidB);
}
return false;
}
if (stackA.getItem() != stackB.getItem()) {
return false;
}
@@ -142,6 +161,10 @@ public class StackInfo {
}
public static synchronized FluidStack getFluid(ItemStack stack) {
if (stack == null) {
return null;
}
ItemStackKey key = new ItemStackKey(stack);
FluidStack fluid = fluidcache.get(key);
@@ -158,7 +181,8 @@ public class StackInfo {
}
public static boolean isFluidContainer(ItemStack stack) {
return stack.getItem() instanceof IFluidContainerItem || FluidContainerRegistry.isContainer(stack);
return stack.getItem() instanceof IFluidContainerItem && !isFluidDisplayItem(stack)
|| FluidContainerRegistry.isContainer(stack);
}
public static String getItemStackGUID(ItemStack stack) {
@@ -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;
}
}
@@ -1,30 +1,124 @@
package codechicken.nei.recipe.chain;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumChatFormatting;
import org.lwjgl.opengl.GL11;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.gui.GuiDraw.ITooltipLineHandler;
import codechicken.nei.ColorUtils;
import codechicken.nei.CompositeTooltipLineHandler;
import codechicken.nei.ItemSorter;
import codechicken.nei.ItemStackAmount;
import codechicken.nei.ItemsTooltipLineHandler;
import codechicken.nei.ItemsTooltipLineHandler.AmountRenderer;
import codechicken.nei.ItemsTooltipLineHandler.StacksAmountRenderer;
import codechicken.nei.ItemsTooltipLineHandler.TotalAmountRenderer;
import codechicken.nei.NEIClientConfig;
import codechicken.nei.NEIClientUtils;
import codechicken.nei.bookmark.BookmarkItem;
import codechicken.nei.drawable.DrawableResource;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.AutoCraftingManager;
import codechicken.nei.recipe.GuiRecipe;
import codechicken.nei.recipe.GuiRecipeTab;
import codechicken.nei.recipe.HandlerInfo;
import codechicken.nei.recipe.Recipe.RecipeId;
import codechicken.nei.recipe.StackInfo;
public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
public class RecipeChainTooltipLineHandler extends CompositeTooltipLineHandler {
protected static class MissingStacksAmountRenderer extends StacksAmountRenderer {
protected static final int MISSING_COLOR = 0xFF5555;
protected final Set<NBTTagCompound> missing;
private final Map<ItemStack, Boolean> cache = new IdentityHashMap<>();
public MissingStacksAmountRenderer(Set<NBTTagCompound> missing) {
this.missing = missing;
}
@Override
protected int getColor(ItemStack stack) {
return this.cache.computeIfAbsent(stack, key -> this.missing.contains(StackInfo.itemStackToNBT(key, false)))
? MISSING_COLOR
: super.getColor(stack);
}
}
protected static class HandlerIconRenderer extends TotalAmountRenderer {
protected static final int ICON_SIZE = 16;
protected static final float BADGE_SCALE = 0.6f;
protected static final float BADGE_OFFSET = 3f;
// slots are drawn edge to edge, so the badge has to clear the 3d model of the neighbour slot
protected static final float BADGE_Z = 10f;
protected final Map<NBTTagCompound, HandlerInfo> handlers;
private final Map<ItemStack, HandlerInfo> cache = new IdentityHashMap<>();
private final Map<String, String> titleCache = new HashMap<>();
public HandlerIconRenderer(Map<NBTTagCompound, HandlerInfo> handlers) {
this.handlers = handlers;
}
@Override
public void draw(int x, int y, ItemStack stack, long amount) {
super.draw(x, y, stack, amount);
drawHandlerBadge(
x,
y,
this.cache.computeIfAbsent(stack, key -> this.handlers.get(StackInfo.itemStackToNBT(key, false))));
}
protected void drawHandlerBadge(int x, int y, HandlerInfo handlerInfo) {
if (handlerInfo == null) return;
GL11.glPushMatrix();
GL11.glColor4f(1, 1, 1, 1);
GL11.glTranslatef(x - BADGE_OFFSET * BADGE_SCALE, y - BADGE_OFFSET * BADGE_SCALE, BADGE_Z);
GL11.glScalef(BADGE_SCALE, BADGE_SCALE, 1.0f);
if (handlerInfo.hasImageOrItem()) {
final DrawableResource image = handlerInfo.getImage();
if (image != null) {
NEIClientUtils.gl2DRenderContext(() -> image.draw(0, 0));
} else {
GuiContainerManager.drawItem(0, 0, handlerInfo.getItemStack(), true, "");
}
} else {
drawBadgeText(handlerInfo.getHandlerName());
}
GL11.glPopMatrix();
}
protected void drawBadgeText(String handlerName) {
final String title = this.titleCache.computeIfAbsent(handlerName, GuiRecipeTab::getHandlerTitle);
final String text = title.isEmpty() ? "??" : title.substring(0, Math.min(2, title.length()));
NEIClientUtils.gl2DRenderContext(
() -> GuiDraw.fontRenderer.drawStringWithShadow(
text,
(ICON_SIZE - GuiDraw.fontRenderer.getStringWidth(text)) / 2,
(ICON_SIZE - GuiDraw.fontRenderer.FONT_HEIGHT) / 2,
ColorUtils.buttonLabelNormal.getColor()));
}
}
public final int groupId;
public final boolean crafting;
@@ -32,16 +126,9 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
protected final List<BookmarkItem> initialItems;
protected final Map<RecipeId, Long> outputRecipes;
protected ItemsTooltipLineHandler available;
protected ItemsTooltipLineHandler inputs;
protected ItemsTooltipLineHandler outputs;
protected ItemsTooltipLineHandler remainder;
protected ItemsTooltipLineHandler craftingNeeded;
protected boolean lastShiftKey = false;
protected boolean lastControlKey = false;
protected Dimension size = new Dimension();
public RecipeChainTooltipLineHandler(int groupId, boolean crafting, RecipeChainMath math) {
this.groupId = groupId;
this.crafting = crafting;
@@ -50,20 +137,32 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
this.outputRecipes = new HashMap<>(this.math.outputRecipes);
}
private void onUpdate() {
@Override
protected boolean needsUpdate() {
final boolean shiftChanged = this.lastShiftKey != (this.lastShiftKey = NEIClientUtils.shiftKey());
return this.lastControlKey != (this.lastControlKey = NEIClientUtils.controlKey()) || shiftChanged;
}
@Override
protected void createLines() {
if (this.outputRecipes.isEmpty() && !this.lastShiftKey) {
return;
}
final List<ItemStack> available = new ArrayList<>();
final List<ItemStack> inputs = new ArrayList<>();
final List<ItemStack> outputs = new ArrayList<>();
final List<ItemStack> remainder = new ArrayList<>();
final List<ItemStack> craftingNeeded = new ArrayList<>();
final Map<NBTTagCompound, HandlerInfo> craftingHandlers = new HashMap<>();
final ItemStackAmount inventory = new ItemStackAmount();
final GuiContainer currentGui = NEIClientUtils.getGuiContainer();
if (this.lastShiftKey && !(currentGui instanceof GuiRecipe<?>)) {
if (this.lastShiftKey && currentGui != null && !(currentGui instanceof GuiRecipe<?>)) {
inventory.putAll(AutoCraftingManager.getInventoryItems(currentGui));
}
if (!this.math.outputRecipes.isEmpty()) {
if (!this.outputRecipes.isEmpty()) {
this.math.initialItems.clear();
this.math.outputRecipes.clear();
this.math.outputRecipes.putAll(this.outputRecipes);
@@ -105,7 +204,7 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
}
} else {
this.math.initialItems.addAll(initialItems);
this.math.initialItems.addAll(this.initialItems);
}
this.math.refresh();
@@ -138,21 +237,24 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
if (amount > 0) {
if (math.outputRecipes.containsKey(item.recipeId)) {
outputs.add(item.getItemStack(amount));
} else if (this.lastShiftKey) {
} else {
remainder.add(item.getItemStack(amount));
}
}
}
if (this.lastShiftKey) {
for (ItemStack stack : math.containerItems) {
if (stack != null) {
remainder.add(stack.copy());
}
for (ItemStack stack : math.containerItemsInventory) {
if (stack != null) {
remainder.add(stack.copy());
}
}
for (ItemStack stack : math.containerItemsCrafting) {
if (stack != null) {
remainder.add(stack.copy());
}
}
} else if (this.lastShiftKey) {
} else {
for (BookmarkItem item : this.math.initialItems) {
if (inventory.contains(item.itemStack)) {
@@ -170,161 +272,100 @@ public class RecipeChainTooltipLineHandler implements ITooltipLineHandler {
inputs.add(item.getItemStack());
}
}
}
if (this.lastShiftKey) {
for (Map.Entry<BookmarkItem, Long> item : this.math.requiredAmount.entrySet()) {
if (item.getKey().type == BookmarkItem.BookmarkItemType.RESULT && item.getValue() != 0)
craftingNeeded.add(item.getKey().getItemStack(item.getValue()));
if (item.getKey().type == BookmarkItem.BookmarkItemType.RESULT && item.getValue() != 0) {
final ItemStack stack = item.getKey().getItemStack(item.getValue());
craftingNeeded.add(stack);
putHandlerInfo(craftingHandlers, stack, item.getKey().recipeId);
}
}
}
inputs.sort(
Comparator.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null)
.thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance));
outputs.sort(
Comparator.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null)
.thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance));
remainder.sort(
Comparator.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null)
.thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance));
this.inputs = new ItemsTooltipLineHandler(
this.lastShiftKey ? NEIClientUtils.translate("bookmark.crafting_chain.missing")
: NEIClientUtils.translate("bookmark.crafting_chain.input"),
inputs,
true,
Integer.MAX_VALUE);
this.available = new ItemsTooltipLineHandler(
NEIClientUtils.translate("bookmark.crafting_chain.available"),
available,
true,
Integer.MAX_VALUE);
this.outputs = new ItemsTooltipLineHandler(
NEIClientUtils.translate("bookmark.crafting_chain.output"),
outputs,
true,
Integer.MAX_VALUE);
this.remainder = new ItemsTooltipLineHandler(
NEIClientUtils.translate("bookmark.crafting_chain.remainder"),
remainder,
true,
Integer.MAX_VALUE);
this.craftingNeeded = new ItemsTooltipLineHandler(
NEIClientUtils.translate("bookmark.crafting_chain.needed"),
craftingNeeded,
true,
Integer.MAX_VALUE);
final Set<NBTTagCompound> missing = new HashSet<>();
if (this.lastShiftKey) {
this.inputs.setLabelColor(EnumChatFormatting.RED);
this.available.setLabelColor(EnumChatFormatting.GREEN);
this.craftingNeeded.setLabelColor(EnumChatFormatting.BLUE);
}
this.size.height = this.size.width = 0;
if (!this.inputs.isEmpty() || !this.outputs.isEmpty()
|| !this.remainder.isEmpty()
|| !this.available.isEmpty()) {
if (!this.math.outputRecipes.isEmpty()) {
this.size.height = 2 + GuiDraw.fontRenderer.FONT_HEIGHT;
for (ItemStack stack : inputs) {
missing.add(StackInfo.itemStackToNBT(stack, false));
}
this.size.width = Math.max(
this.inputs.getSize().width,
Math.max(
this.outputs.getSize().width,
Math.max(
this.remainder.getSize().width,
Math.max(this.available.getSize().width, this.craftingNeeded.getSize().width))));
this.size.height += this.inputs.getSize().height + this.outputs.getSize().height
+ this.remainder.getSize().height
+ this.available.getSize().height
+ this.craftingNeeded.getSize().height;
}
// items with a shortage are already in the inputs list, holding the missing amount instead of the required one
final List<ItemStack> covered = new ArrayList<>();
for (ItemStack stack : available) {
if (!missing.contains(StackInfo.itemStackToNBT(stack, false))) {
covered.add(stack);
}
}
final Comparator<ItemStack> comparator = Comparator
.comparing((ItemStack stack) -> StackInfo.getFluid(stack) != null)
.thenComparingInt(stack -> -1 * stack.stackSize).thenComparing(ItemSorter.instance);
inputs.sort(comparator);
covered.sort(comparator);
outputs.sort(comparator);
remainder.sort(comparator);
// missing items stay on top of the list
inputs.addAll(covered);
if (this.lastShiftKey) {
outputs.addAll(remainder);
}
addLine(
NEIClientUtils.translate("bookmark.crafting_chain"),
outputs,
outputs.size() == 1,
EnumChatFormatting.AQUA,
new TotalAmountRenderer());
addLine(
NEIClientUtils.translate("bookmark.crafting_chain.needed"),
craftingNeeded,
false,
EnumChatFormatting.BLUE,
new HandlerIconRenderer(craftingHandlers));
addLine(
NEIClientUtils.translate("bookmark.crafting_chain.input"),
inputs,
this.lastShiftKey,
EnumChatFormatting.YELLOW,
this.lastShiftKey ? new MissingStacksAmountRenderer(missing) : new TotalAmountRenderer());
}
@Override
public Dimension getSize() {
boolean update = this.outputs == null;
update = this.lastShiftKey != (this.lastShiftKey = NEIClientUtils.shiftKey()) || update;
update = this.lastControlKey != (this.lastControlKey = NEIClientUtils.controlKey()) || update;
if (update) {
onUpdate();
protected static void putHandlerInfo(Map<NBTTagCompound, HandlerInfo> handlers, ItemStack stack,
RecipeId recipeId) {
if (recipeId == null) {
return;
}
return this.size;
final HandlerInfo handlerInfo = GuiRecipeTab.getHandlerInfo(recipeId.getHandlerName(), null);
handlers.put(
StackInfo.itemStackToNBT(stack, false),
handlerInfo != null ? handlerInfo : GuiRecipeTab.DEFAULT_HANDLER_INFO);
}
@Override
public void draw(int x, int y) {
if (this.size.height == 0) return;
protected void addLine(String label, List<ItemStack> items, boolean vertical, EnumChatFormatting labelColor,
AmountRenderer amountRenderer) {
final int maxNamedRows = vertical ? NEIClientConfig.getMaxVerticalTooltipRows() : 0;
final ItemsTooltipLineHandler line = ItemsTooltipLineHandler.of(label, items, maxLineRows(), maxNamedRows);
if (!this.math.outputRecipes.isEmpty()) {
GuiDraw.fontRenderer.drawStringWithShadow(
EnumChatFormatting.AQUA + NEIClientUtils.translate("bookmark.crafting_chain"),
x,
y + 2,
0xee555555);
y += 2 + GuiDraw.fontRenderer.FONT_HEIGHT;
if (line.isEmpty()) {
return;
}
if (NEIClientConfig.recipeChainDir() == 0) {
if (!this.inputs.isEmpty()) {
this.inputs.draw(x, y);
y += this.inputs.getSize().height;
}
if (!this.available.isEmpty()) {
this.available.draw(x, y);
y += this.available.getSize().height;
}
if (!this.craftingNeeded.isEmpty()) {
this.craftingNeeded.draw(x, y);
y += this.craftingNeeded.getSize().height;
}
if (!this.outputs.isEmpty()) {
this.outputs.draw(x, y);
y += this.outputs.getSize().height;
}
} else {
if (!this.outputs.isEmpty()) {
this.outputs.draw(x, y);
y += this.outputs.getSize().height;
}
if (!this.inputs.isEmpty()) {
this.inputs.draw(x, y);
y += this.inputs.getSize().height;
}
if (!this.craftingNeeded.isEmpty()) {
this.craftingNeeded.draw(x, y);
y += this.craftingNeeded.getSize().height;
}
if (!this.available.isEmpty()) {
this.available.draw(x, y);
y += this.available.getSize().height;
}
}
if (!this.remainder.isEmpty()) {
this.remainder.draw(x, y);
}
line.setLabelColor(labelColor);
line.setAmountRenderer(amountRenderer);
this.lines.add(line);
}
}
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more