From 32834910e881af8be7e08c5d3ecbb55bfc082278 Mon Sep 17 00:00:00 2001 From: P1chC Date: Fri, 23 Oct 2026 17:22:10 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E7=A7=BB=E6=A4=8D=E7=82=89=E5=AD=90?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=B9=B6=E4=BF=AE=E5=A4=8D=20Forge=201.20.1?= =?UTF-8?q?=20=E5=8F=AF=E9=80=89=E6=A8=A1=E7=BB=84=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 26 +- gradle.properties | 4 +- gradle/bundled-compat-libraries.gradle | 36 +++ gradle/bytecode-contract-tests.gradle | 4 +- gradle/forge1201-pack-profile.json | 8 +- gradle/merge-game-tests.gradle | 63 ++++ gradle/port-bridge-tests.gradle | 3 + .../tests/ApiCompatibilityGameTests.java | 200 +++++++++++++ .../useless/tests/ExMachinisClientProbe.java | 65 +++++ .../useless_mod/recipes/compat_reaction.json | 10 + .../useless/tests/MergedFurnaceGameTests.java | 272 ++++++++++++++++++ .../useless_mod/structures/furnace_empty.nbt | Bin 0 -> 111 bytes .../com/sorrowmist/useless/UselessMod.java | 2 + .../client/gui/FurnaceScreenScaler.java | 44 +++ .../client/network/ClientPacketHandlers.java | 21 ++ .../useless/compat/ae/AeDeviceLinker.java | 12 +- .../useless/compat/ae/AeNetworks.java | 10 +- .../BloodMagicRecipeCompatLoader.java | 19 ++ .../embers/EmbersRecipeCompatLoader.java | 27 ++ .../OmniversalPatternJeiTransferHandler.java | 4 +- .../mekanism/MekanismRecipeCompatLoader.java | 5 + .../thermal/ThermalItemStorageAccess.java | 29 ++ .../useless/compat/thermal/ThermalItems.java | 41 +++ .../compat/thermal/ThermalMixinPlugin.java | 33 +++ .../compat/thermal/ThermalParallelAccess.java | 32 +++ .../compat/thermal/ThermalParallelMath.java | 18 ++ .../thermal/ThermalRecipeCompatLoader.java | 23 ++ .../compat/thermal/UpgradeAugmentItem.java | 51 ++++ .../AdvancedAlloyFurnaceBlockEntity.java | 7 +- .../MePatternAssemblyBlockEntity.java | 7 +- .../ae/AdvancedAlloyFurnaceAeManager.java | 10 +- .../ae/CraftingTask.java | 9 + .../menus/AdvancedAlloyFurnaceMenu.java | 16 ++ .../content/menus/FurnaceMenuAccess.java | 24 ++ .../menus/FurnaceMenuSynchronizer.java | 61 ++++ .../menus/MultiblockAlloyFurnaceMenu.java | 16 ++ .../content/menus/PagedRecoverableMenu.java | 15 +- .../menus/PassiveCraftingHatchMenu.java | 2 +- .../recipe/AlloyFurnaceRecipeFingerprint.java | 2 +- .../content/recipe/CountedIngredient.java | 10 +- .../recipe/ItemIngredientAllocator.java | 49 ++-- .../content/recipe/RecipeSourceIds.java | 4 + .../content/recipe/RetainedRecipeInputs.java | 41 +++ .../recipe/adapters/BatchRecipeAdapter.java | 154 ++++++++++ .../adapters/RecipeAdapterCompatRegistry.java | 8 +- .../ReactionChamberRecipeAdapter.java | 2 +- ...eAvaritiaExtremeSmithingRecipeAdapter.java | 13 +- .../ReAvaritiaTableRecipeAdapter.java | 30 +- .../bloodmagic/BloodMagicRecipeAdapter.java | 46 +++ .../adapters/embers/EmbersRecipeAdapter.java | 77 +++++ .../exdeorum/HammerRecipeAdapter.java | 22 ++ .../MetallurgicItemRecipeAdapter.java | 62 ++++ .../mekanism/RecyclerRecipeAdapter.java | 10 +- .../AmbientGasCollectorRecipeAdapter.java | 10 +- .../thermal/NativeThermalIngredients.java | 49 ++++ .../thermal/ThermalRecipeAdapter.java | 65 +++++ .../useless/core/config/ConfigManager.java | 17 ++ .../useless/core/config/ThermalConfig.java | 39 +++ .../sorrowmist/useless/init/ClientSetup.java | 1 + .../sorrowmist/useless/init/ModNetwork.java | 5 +- .../exmachinis/CompactingCategoryMixin.java | 21 ++ .../thermal/AugmentableBlockEntityMixin.java | 122 ++++++++ .../mixin/thermal/ItemStorageCoFHMixin.java | 54 ++++ .../thermal/MachineBlockEntityMixin.java | 92 ++++++ .../Reconfigurable4WayBlockEntityMixin.java | 85 ++++++ .../thermal/ThermalAugmentRulesMixin.java | 48 ++++ .../useless/network/AECancelPacket.java | 3 +- .../network/AEReturnOutputTogglePacket.java | 3 +- .../useless/network/AutoIOChangePacket.java | 3 +- .../useless/network/FaceModeChangePacket.java | 6 +- .../network/FurnaceMenuSyncPacket.java | 43 +++ .../network/RedstoneControlPacket.java | 3 +- .../useless/network/TankClearPacket.java | 3 +- .../META-INF/JEI-texture-LICENSE.txt | 21 ++ .../resources/META-INF/JEI-texture-NOTICE.txt | 4 + src/main/resources/META-INF/mods.toml | 51 +++- .../assets/useless_mod/lang/en_us.json | 15 +- .../assets/useless_mod/lang/zh_cn.json | 15 +- .../textures/gui/exmachinis_recipe.png | Bin 0 -> 3459 bytes .../recipes/augment_parallel_1.json | 29 ++ .../recipes/augment_parallel_2.json | 29 ++ .../recipes/augment_parallel_3.json | 29 ++ .../useless_integral_component_tier_1.json | 32 +++ .../useless_integral_component_tier_2.json | 32 +++ .../useless_integral_component_tier_3.json | 32 +++ .../useless_integral_component_tier_4.json | 32 +++ .../useless_integral_component_tier_5.json | 32 +++ .../useless_integral_component_tier_6.json | 32 +++ .../useless_integral_component_tier_7.json | 32 +++ .../useless_integral_component_tier_8.json | 32 +++ .../useless_integral_component_tier_9.json | 32 +++ src/main/resources/useless_mod.mixins.json | 1 + .../resources/useless_mod.thermal.mixins.json | 18 ++ .../forge1201/DependencyMetadataTest.java | 2 +- .../compat/forge1201/RecipeTestBootstrap.java | 4 +- .../capability/NativeTestCapabilities.java | 25 ++ .../compat/jei/AEKeyBookmarkPortTest.java | 4 +- .../ae/extendedae/NativeAeRecipeTest.java | 6 +- .../useless/InventoryTestBootstrap.java | 15 +- .../client/gui/FurnaceScreenScalerTest.java | 17 ++ .../useless/compat/ae/AeHostLookupTest.java | 39 +++ .../jei/OmniversalPatternJeiTransferTest.java | 111 +++++++ .../thermal/ThermalParallelMathTest.java | 21 ++ .../content/menus/FurnaceMenuAccessTest.java | 47 +++ .../menus/FurnaceMenuSynchronizerTest.java | 28 ++ .../recipe/RetainedIngredientTest.java | 78 +++++ .../adapters/BatchRecipeAdapterTest.java | 174 +++++++++++ .../exdeorum/HammerRecipeLookupTest.java | 75 +++++ .../network/FurnaceMenuSyncPacketTest.java | 49 ++++ src/test/python/test_ae_crafting_contract.py | 5 + 110 files changed, 3518 insertions(+), 113 deletions(-) create mode 100644 gradle/bundled-compat-libraries.gradle create mode 100644 gradle/merge-game-tests.gradle create mode 100644 src/compatGameTest/java/com/sorrowmist/useless/tests/ApiCompatibilityGameTests.java create mode 100644 src/compatGameTest/java/com/sorrowmist/useless/tests/ExMachinisClientProbe.java create mode 100644 src/compatGameTest/resources/data/useless_mod/recipes/compat_reaction.json create mode 100644 src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java create mode 100644 src/gameTest/resources/data/useless_mod/structures/furnace_empty.nbt create mode 100644 src/main/java/com/sorrowmist/useless/client/gui/FurnaceScreenScaler.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/bloodmagic/BloodMagicRecipeCompatLoader.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/embers/EmbersRecipeCompatLoader.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItemStorageAccess.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItems.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/ThermalMixinPlugin.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelAccess.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMath.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/ThermalRecipeCompatLoader.java create mode 100644 src/main/java/com/sorrowmist/useless/compat/thermal/UpgradeAugmentItem.java create mode 100644 src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccess.java create mode 100644 src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizer.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/RetainedRecipeInputs.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapter.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/adapters/bloodmagic/BloodMagicRecipeAdapter.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/adapters/embers/EmbersRecipeAdapter.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/MetallurgicItemRecipeAdapter.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/NativeThermalIngredients.java create mode 100644 src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/ThermalRecipeAdapter.java create mode 100644 src/main/java/com/sorrowmist/useless/core/config/ThermalConfig.java create mode 100644 src/main/java/com/sorrowmist/useless/mixin/exmachinis/CompactingCategoryMixin.java create mode 100644 src/main/java/com/sorrowmist/useless/mixin/thermal/AugmentableBlockEntityMixin.java create mode 100644 src/main/java/com/sorrowmist/useless/mixin/thermal/ItemStorageCoFHMixin.java create mode 100644 src/main/java/com/sorrowmist/useless/mixin/thermal/MachineBlockEntityMixin.java create mode 100644 src/main/java/com/sorrowmist/useless/mixin/thermal/Reconfigurable4WayBlockEntityMixin.java create mode 100644 src/main/java/com/sorrowmist/useless/mixin/thermal/ThermalAugmentRulesMixin.java create mode 100644 src/main/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacket.java create mode 100644 src/main/resources/META-INF/JEI-texture-LICENSE.txt create mode 100644 src/main/resources/META-INF/JEI-texture-NOTICE.txt create mode 100644 src/main/resources/assets/useless_mod/textures/gui/exmachinis_recipe.png create mode 100644 src/main/resources/data/useless_mod/recipes/augment_parallel_1.json create mode 100644 src/main/resources/data/useless_mod/recipes/augment_parallel_2.json create mode 100644 src/main/resources/data/useless_mod/recipes/augment_parallel_3.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_1.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_2.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_3.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_4.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_5.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_6.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_7.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_8.json create mode 100644 src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_9.json create mode 100644 src/main/resources/useless_mod.thermal.mixins.json create mode 100644 src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeTestCapabilities.java create mode 100644 src/test/java/com/sorrowmist/useless/client/gui/FurnaceScreenScalerTest.java create mode 100644 src/test/java/com/sorrowmist/useless/compat/ae/AeHostLookupTest.java create mode 100644 src/test/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferTest.java create mode 100644 src/test/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMathTest.java create mode 100644 src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccessTest.java create mode 100644 src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizerTest.java create mode 100644 src/test/java/com/sorrowmist/useless/content/recipe/RetainedIngredientTest.java create mode 100644 src/test/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapterTest.java create mode 100644 src/test/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeLookupTest.java create mode 100644 src/test/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacketTest.java diff --git a/build.gradle b/build.gradle index 6ff6b6a0..31feb88a 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,10 @@ base { archivesName = mod_id } java.toolchain.languageVersion = JavaLanguageVersion.of(17) repositories { mavenCentral() + exclusiveContent { + forRepository { maven { url = 'https://maven.covers1624.net/' } } + filter { includeGroup 'com.teamcofh' } + } exclusiveContent { forRepository { maven { url = 'https://api.modrinth.com/maven' } } filter { includeGroup 'maven.modrinth' } @@ -39,7 +43,16 @@ sourceSets.test.runtimeClasspath += sourceSets.main.compileClasspath configurations.modCompileOnly.transitive = false dependencies { - modCompileOnly 'maven.modrinth:rxYaglEe:orFPvySe' // advancedae-1.2.7-1.20.1 + modCompileOnly 'maven.modrinth:QeB3NRC5:5muSI5qX' // Re-Avaritia 1.4.0 (Forge 1.20.1) + modCompileOnly 'curse.maven:cofh_core-69162:5374122' + modCompileOnly 'com.teamcofh:thermal_core:1.20.1-11.0.7.25' + modCompileOnly 'curse.maven:blood-magic-224791:7253333' + modCompileOnly 'curse.maven:embers-rekindled-300777:7446478' + jarJar('io.github.llamalad7:mixinextras-forge:0.5.0') { + version { strictly '[0.5.0,0.6)'; prefer '0.5.0' } + } + modCompileOnly 'maven.modrinth:rxYaglEe:d83Wdhdn' // AdvancedAE 1.3.6-1.20.1 + modCompileOnly 'maven.modrinth:qDJXZJTz:PQ3IlR98' // Mekanism More Machine 1.2.1 modCompileOnly 'maven.modrinth:rqFWfVlz:tT3CLYvK' // apotheosis-1.20.1-7.4.8 modCompileOnly 'maven.modrinth:IiATswDj:d9GM663Q' // applied-mekanistics-1.4.2 modCompileOnly 'maven.modrinth:XxWD5pD3:jYgSnxye' // appliedenergistics2-forge-15.4.11 @@ -66,7 +79,7 @@ dependencies { modCompileOnly 'maven.modrinth:lWxpUd04:O8YO7ORj' // industrial-foregoing-1.20.1-3.5.19 modCompileOnly 'maven.modrinth:nvQzSEkH:LecuGude' // jade-1.20.1-forge-11.13.2 modCompileOnly 'maven.modrinth:fRiHVvU7:WtJS5tVw' // emi-1.1.22-1.20.1-forge - modCompileOnly 'maven.modrinth:u6dRKJwZ:zzpNSbJZ' // jei-1.20.1-forge-15.20.0.116 + modCompileOnly 'maven.modrinth:u6dRKJwZ:TrKZlZ0b' // JEI 15.74.0.227 modCompileOnly 'maven.modrinth:umyGl7zF:g5igndAv' // kubejs-forge-2001.6.5-build.16 modCompileOnly 'maven.modrinth:Ce6I4WUE:9iboPfea' // mekanism-1.20.1-10.4.15.75 modCompileOnly 'maven.modrinth:OFVYKsAk:x9re7wt4' // mekanismgenerators-1.20.1-10.4.15.75 @@ -92,10 +105,10 @@ dependencies { } mixin { add sourceSets.main, "${mod_id}.refmap.json" - ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json", "${mod_id}.compact.mixins.json"].each { config it } + ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json", "${mod_id}.thermal.mixins.json", "${mod_id}.compact.mixins.json"].each { config it } } tasks.named('jar') { - manifest.attributes('MixinConfigs': ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json", "${mod_id}.compact.mixins.json"].join(',')) + manifest.attributes('MixinConfigs': ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json", "${mod_id}.thermal.mixins.json", "${mod_id}.compact.mixins.json"].join(',')) } tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' @@ -104,6 +117,7 @@ tasks.withType(JavaCompile).configureEach { tasks.named('test', Test) { useJUnitPlatform() classpath = classpath.filter { !it.name.startsWith('slf4j-api-1.') } + classpath += files(layout.buildDirectory.file("moddev/artifacts/client-extra-${minecraft_version}-${forge_version}.jar")) } tasks.named('processResources', ProcessResources) { inputs.property 'mod_version', mod_version @@ -117,3 +131,7 @@ apply from: 'gradle/port-bridge-tests.gradle' apply from: 'gradle/forge1201-pack-profile.gradle' apply from: 'gradle/bytecode-contract-tests.gradle' + +apply from: 'gradle/merge-game-tests.gradle' + +apply from: 'gradle/bundled-compat-libraries.gradle' diff --git a/gradle.properties b/gradle.properties index f21babdd..b29caf99 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,10 +5,10 @@ org.gradle.caching=true org.gradle.configuration-cache=false minecraft_version=1.20.1 minecraft_version_range=[1.20.1,1.20.2) -forge_version=47.3.10 +forge_version=47.4.10 loader_version_range=[47,) mod_id=useless_mod mod_name=Useless Mod -mod_version=20.2.4.6 +mod_version=20.2.4.6-merged.5 mod_group_id=com.sorrowmist.useless mod_license=MIT diff --git a/gradle/bundled-compat-libraries.gradle b/gradle/bundled-compat-libraries.gradle new file mode 100644 index 00000000..ae66f4b6 --- /dev/null +++ b/gradle/bundled-compat-libraries.gradle @@ -0,0 +1,36 @@ +// AdvancedAE only publishes this library inside its JarJar. Materialize a local module +// before resolution so ModDev can remap its Minecraft members instead of using raw SRG bytecode. +def bundledRepo = layout.buildDirectory.dir('compat-repository').get().asFile +repositories { + exclusiveContent { + forRepository { flatDir { dirs bundledRepo } } + filter { includeGroup 'bundled' } + } +} +configurations { advancedAeBundle { transitive = false } } +dependencies { advancedAeBundle 'maven.modrinth:rxYaglEe:d83Wdhdn' } +def addonFile = new File(bundledRepo, 'ae2addonlib-1.0.4-1.20.1.jar') +new java.util.zip.ZipFile(configurations.advancedAeBundle.singleFile).withCloseable { archive -> + def bytes = archive.getInputStream(archive.getEntry( + 'META-INF/jarjar/ae2addonlib-1.0.4-1.20.1.jar')).withCloseable { it.readAllBytes() } + if (!addonFile.isFile() || !Arrays.equals(addonFile.bytes, bytes)) { + bundledRepo.mkdirs() + addonFile.bytes = bytes + } +} +dependencies { modCompileOnly 'bundled:ae2addonlib:1.0.4-1.20.1' } + +// JEI's disk bookmark regression test also exercises the bundled config serializer. +configurations { jeiBundle { transitive = false } } +dependencies { jeiBundle 'maven.modrinth:u6dRKJwZ:TrKZlZ0b' } +def jeiConfig = tasks.register('extractJeiConfigLibrary', Copy) { + from({ zipTree(configurations.jeiBundle.singleFile) }) { + include 'META-INF/jarjar/mezz_config-1.20.1-forge-0.6.6.jar' + eachFile { path = name } + includeEmptyDirs = false + } + into layout.buildDirectory.dir('compat-libs/jei') +} +dependencies { + portBridgeTestImplementation files(layout.buildDirectory.file('compat-libs/jei/mezz_config-1.20.1-forge-0.6.6.jar')).builtBy(jeiConfig) +} diff --git a/gradle/bytecode-contract-tests.gradle b/gradle/bytecode-contract-tests.gradle index 0c51c544..c6b1a2b7 100644 --- a/gradle/bytecode-contract-tests.gradle +++ b/gradle/bytecode-contract-tests.gradle @@ -1,8 +1,8 @@ // Compare release mixin signatures with the pinned native AE2 and AdvancedAE bytecode. configurations { contractNative { transitive = false } } dependencies { - contractNative 'maven.modrinth:XxWD5pD3:Zfyso1sP' - contractNative 'maven.modrinth:rxYaglEe:orFPvySe' + contractNative 'maven.modrinth:XxWD5pD3:jYgSnxye' + contractNative 'maven.modrinth:rxYaglEe:d83Wdhdn' } def contractInputs = layout.buildDirectory.file('contract-inputs.json') tasks.register('writeContractInputs') { diff --git a/gradle/forge1201-pack-profile.json b/gradle/forge1201-pack-profile.json index c748df8c..06b5d1b0 100644 --- a/gradle/forge1201-pack-profile.json +++ b/gradle/forge1201-pack-profile.json @@ -4,7 +4,7 @@ "com/sorrowmist/useless/content/recipe/adapters/apothicflux/**", "com/sorrowmist/useless/content/recipe/adapters/arsnouveau/**", "com/sorrowmist/useless/content/recipe/adapters/astralsorcery/**", - "com/sorrowmist/useless/content/recipe/adapters/avaritia/**", + "com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaSculkCraftingRecipeAdapter.java", "com/sorrowmist/useless/content/recipe/adapters/delight/**", "com/sorrowmist/useless/content/recipe/adapters/eco/**", "com/sorrowmist/useless/content/recipe/adapters/extremereactors/**", @@ -27,7 +27,6 @@ "com/sorrowmist/useless/content/recipe/adapters/ae/ae2cs/**", "com/sorrowmist/useless/content/recipe/adapters/ae/ae2lt/**", "com/sorrowmist/useless/content/recipe/adapters/ae/dataenergistics/**", - "com/sorrowmist/useless/content/recipe/adapters/mekanism/mekanismmoremachine/**", "com/sorrowmist/useless/compat/apothicflux/**", "com/sorrowmist/useless/compat/ars/**", "com/sorrowmist/useless/compat/barbequesdelight/**", @@ -44,7 +43,6 @@ "com/sorrowmist/useless/compat/itemobliterator/**", "com/sorrowmist/useless/compat/kaleidoscope/**", "com/sorrowmist/useless/compat/lychee/**", - "com/sorrowmist/useless/compat/mekanismmoremachine/**", "com/sorrowmist/useless/compat/modernindustrialization/**", "com/sorrowmist/useless/compat/neoecoae/NeoEcoCompat.java", "com/sorrowmist/useless/compat/neoecoae/NeoEcoDynamicJobAttachment.java", @@ -70,7 +68,6 @@ "com/sorrowmist/useless/content/recipe/adapters/industrialforegoing/FluidShapedRecipeAdapter.java", "com/sorrowmist/useless/content/recipe/adapters/industrialforegoing/FluidShapelessRecipeAdapter.java", "com/sorrowmist/useless/content/recipe/adapters/ae/extendedae/CrystalAssemblerRecipeAdapter.java", - "com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java", "com/sorrowmist/useless/content/recipe/adapters/enderio/VatFermentingRecipeAdapter.java", "com/sorrowmist/useless/compat/ae/AeSourceCompat.java", "com/sorrowmist/useless/compat/ae/AePressureCompat.java", @@ -89,9 +86,6 @@ "com/sorrowmist/useless/content/recipe/adapters/industrialforegoing/FluidShapelessRecipeAdapter.java", "com/sorrowmist/useless/content/recipe/adapters/industrialforegoing/ExtraUpgradesCraftingRecipeAdapterSupport.java" ], - "mekmm": [ - "com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java" - ], "arseng": [ "com/sorrowmist/useless/compat/ae/AeSourceCompat.java" ], diff --git a/gradle/merge-game-tests.gradle b/gradle/merge-game-tests.gradle new file mode 100644 index 00000000..3c245387 --- /dev/null +++ b/gradle/merge-game-tests.gradle @@ -0,0 +1,63 @@ +// Real Forge lifecycle checks; test classes and runtime fixtures never enter the release jar. +if (!providers.gradleProperty('mergeGameTest').isPresent()) return + +sourceSets { + gameTest { + compileClasspath += sourceSets.main.output + sourceSets.main.compileClasspath + runtimeClasspath += sourceSets.main.output + sourceSets.main.runtimeClasspath + } +} + +dependencies { + modRuntimeOnly 'maven.modrinth:QeB3NRC5:5muSI5qX' + modRuntimeOnly 'curse.maven:ex-deorum-901420:6614784' + modRuntimeOnly 'maven.modrinth:XxWD5pD3:jYgSnxye' + modRuntimeOnly 'maven.modrinth:Ck4E7v7R:i7Tp1AHw' + modRuntimeOnly 'curse.maven:cofh_core-69162:5374122' + modRuntimeOnly 'com.teamcofh:thermal_core:1.20.1-11.0.7.25' + modRuntimeOnly 'curse.maven:thermal_foundation-222880:5443583' + modRuntimeOnly 'curse.maven:thermal_expansion-69163:5372749' + modRuntimeOnly 'curse.maven:blood-magic-224791:7253333' + modRuntimeOnly 'curse.maven:embers-rekindled-300777:7446478' + modRuntimeOnly 'curse.maven:Patchouli-306770:6164575' + modRuntimeOnly 'io.github.llamalad7:mixinextras-forge:0.5.0' +} + +if (providers.gradleProperty('compatGameTest').isPresent()) { + sourceSets.gameTest.java.srcDir 'src/compatGameTest/java' + sourceSets.gameTest.resources.srcDir 'src/compatGameTest/resources' + def stretcherRepo = providers.gradleProperty('stretcherRepo').get() + repositories { + flatDir { dirs "${stretcherRepo}/build/libs" } + } + dependencies { + modRuntimeOnly 'maven.modrinth:rxYaglEe:d83Wdhdn' + modRuntimeOnly 'curse.maven:applied-flux-965012:6755986' + modRuntimeOnly 'maven.modrinth:UhW5uCKw:eoUaDkZf' + modRuntimeOnly 'maven.modrinth:Ce6I4WUE:uxe1WQp4' + modRuntimeOnly 'maven.modrinth:IiATswDj:9n9p68Qq' + modRuntimeOnly 'maven.modrinth:qDJXZJTz:PQ3IlR98' + modRuntimeOnly 'maven.modrinth:8BmcQJ2H:aVW7Z5da' + modRuntimeOnly 'local.compat:useless_stretcher:20.1.6.0-compat.2' + gameTestCompileOnly files("${stretcherRepo}/build/devlibs/useless_stretcher-20.1.6.0-compat.2.jar") + } + if (providers.gradleProperty('compatClientProbe').isPresent()) { + repositories { flatDir { dirs providers.gradleProperty('packMods').get() } } + dependencies { + modRuntimeOnly 'maven.modrinth:u6dRKJwZ:TrKZlZ0b' + modRuntimeOnly 'pack.fixture:exmachinis-deorum:1.20.1-3.0.3' + } + legacyForge.runs.client.systemProperty 'useless.compatClientProbe', 'true' + } +} + +legacyForge { + mods { "${mod_id}" { sourceSet sourceSets.gameTest } } + runs { + gameTestServer { + type = 'gameTestServer' + gameDirectory = file('run/merge-gametest') + systemProperty 'forge.enabledGameTestNamespaces', mod_id + } + } +} diff --git a/gradle/port-bridge-tests.gradle b/gradle/port-bridge-tests.gradle index 91270a1f..9358a5da 100644 --- a/gradle/port-bridge-tests.gradle +++ b/gradle/port-bridge-tests.gradle @@ -9,6 +9,8 @@ sourceSets { java.include 'com/sorrowmist/useless/compat/kubejs/GenericStackRecipeComponent.java' java.include 'com/sorrowmist/useless/compat/kubejs/NativeRecipeComponent.java' java.include 'com/sorrowmist/useless/content/recipe/adapters/exdeorum/**' + java.include 'com/sorrowmist/useless/content/recipe/adapters/avaritia/**' + java.exclude 'com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaSculkCraftingRecipeAdapter.java' java.include 'com/sorrowmist/useless/content/recipe/adapters/botanypots/**' java.include 'com/sorrowmist/useless/compat/jei/GenericStackJeiIngredientProvider*.java' java.include 'com/sorrowmist/useless/compat/jei/AEKeyJeiIngredients.java' @@ -30,6 +32,7 @@ sourceSets { java.include 'com/sorrowmist/useless/compat/mekanism/MekanismChemicalStackView.java' java.include 'com/sorrowmist/useless/compat/mekanism/MekanismChemicalStorage.java' java.include 'com/sorrowmist/useless/content/recipe/adapters/mekanism/**' + java.exclude 'com/sorrowmist/useless/content/recipe/adapters/mekanism/MetallurgicItemRecipeAdapter.java' java.exclude 'com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java' java.exclude 'com/sorrowmist/useless/content/recipe/adapters/mekanism/mekanismmoremachine/**' java.include 'com/sorrowmist/useless/content/machines/advanced_alloy_furnace/chemical/ChemicalKeyProvider*.java' diff --git a/src/compatGameTest/java/com/sorrowmist/useless/tests/ApiCompatibilityGameTests.java b/src/compatGameTest/java/com/sorrowmist/useless/tests/ApiCompatibilityGameTests.java new file mode 100644 index 00000000..101d796d --- /dev/null +++ b/src/compatGameTest/java/com/sorrowmist/useless/tests/ApiCompatibilityGameTests.java @@ -0,0 +1,200 @@ +package com.sorrowmist.useless.tests; + +import appeng.core.definitions.AEBlocks; +import appeng.blockentity.networking.WirelessAccessPointBlockEntity; +import appeng.blockentity.storage.DriveBlockEntity; +import com.mojang.authlib.GameProfile; +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.compat.ae.AeDeviceLinker; +import com.sorrowmist.useless.compat.forge1201.component.ComponentAccess; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeManagerAccess; +import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; +import com.sorrowmist.useless.content.machines.advanced_alloy_furnace.layout.AdvancedAlloyFurnaceLayout; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeManager; +import com.sorrowmist.useless.content.recipe.adapters.ae.advancedae.ReactionChamberRecipeAdapter; +import com.sorrowmist.useless.content.recipe.adapters.mekanism.RecyclerRecipeAdapter; +import com.sorrowmist.useless.content.recipe.adapters.mekanism.mekanismmoremachine.AmbientGasCollectorRecipeAdapter; +import com.sorrowmist.useless.core.component.UComponents; +import com.sorrowmist.useless.event.EventHandler; +import com.sorrowmist.useless.init.ModBlocks; +import com.sorrowmist.useless.init.ModItems; +import com.sorrowmist.useless.stretcher.compat.StaffRadiationProtection; +import com.sorrowmist.useless.world.ae.AeConnectLinkSavedData; +import mekanism.common.capabilities.Capabilities; +import mekanism.common.lib.radiation.RadiationManager; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.GlobalPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.util.FakePlayerFactory; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.gametest.GameTestHolder; +import net.minecraftforge.gametest.PrefixGameTestTemplate; +import net.pedroksl.advanced_ae.recipes.ReactionChamberRecipe; + +import java.util.UUID; +import java.util.Map; + +@GameTestHolder(UselessMod.MODID) +@PrefixGameTestTemplate(false) +public final class ApiCompatibilityGameTests { + @GameTest(template = "furnace_empty", timeoutTicks = 400) + public static void reactionChamberConsumesExactItemsAndFluid(GameTestHelper helper) { + var level = helper.getLevel(); + var adapter = new ReactionChamberRecipeAdapter(); + helper.assertTrue(AlloyFurnaceRecipeManager.getInstance().getRegisteredAdapters().stream() + .anyMatch(ReactionChamberRecipeAdapter.class::isInstance), "Reaction adapter must register at startup"); + var holder = RecipeManagerAccess.getAllRecipesFor(level.getRecipeManager(), ReactionChamberRecipe.TYPE).stream() + .filter(r -> r.id().equals(new ResourceLocation("useless_mod", "compat_reaction"))) + .findFirst().orElseThrow(); + var recipe = adapter.convert(holder, level); + helper.assertTrue(recipe.energy() == 12000 && recipe.inputFluids().get(0).amount() == 500, + "Energy and fluid amount must survive the new native API"); + var pos = new BlockPos(2, 1, 2); + helper.setBlock(pos, ModBlocks.ADVANCED_ALLOY_FURNACE_BLOCK.get()); + var furnace = (AdvancedAlloyFurnaceBlockEntity) level.getBlockEntity(helper.absolutePos(pos)); + furnace.getItemHandler().setStackInSlot(0, new ItemStack(Items.IRON_INGOT, 4)); + furnace.getItemHandler().setStackInSlot(AdvancedAlloyFurnaceLayout.MOLD_SLOT, adapter.getMoldItem()); + furnace.getInputFluidTank(0).setFluid(new FluidStack(Fluids.WATER, 499)); + furnace.setEnergy(1_000_000L); + helper.runAtTickTime(25, () -> { + helper.assertTrue(furnace.getItemHandler().getStackInSlot(0).getCount() == 4 + && furnace.getInputFluidTank(0).getFluidAmount() == 499, + "Insufficient fluid must not consume either duplicate item requirement"); + furnace.getInputFluidTank(0).setFluid(new FluidStack(Fluids.WATER, 500)); + furnace.setChanged(); + }); + helper.succeedWhen(() -> { + int diamonds = 0; + for (int slot = 9; slot < 18; slot++) { + var stack = furnace.getItemHandler().getStackInSlot(slot); + if (stack.is(Items.DIAMOND)) diamonds += stack.getCount(); + } + helper.assertTrue(diamonds == 3 && furnace.getItemHandler().getStackInSlot(0).isEmpty() + && furnace.getInputFluidTank(0).isEmpty(), "Must consume four ingots and 500 mB exactly once"); + helper.assertTrue(ItemStack.isSameItem(furnace.getItemHandler().getStackInSlot( + AdvancedAlloyFurnaceLayout.MOLD_SLOT), adapter.getMoldItem()), "Mold must remain"); + }); + } + + @GameTest(template = "furnace_empty") + public static void moreMachineAdaptersRegisterAndGenerateRecipes(GameTestHelper helper) { + var adapters = AlloyFurnaceRecipeManager.getInstance().getRegisteredAdapters(); + var recycler = adapters.stream().filter(RecyclerRecipeAdapter.class::isInstance) + .map(RecyclerRecipeAdapter.class::cast).findFirst().orElseThrow(); + var collector = adapters.stream().filter(AmbientGasCollectorRecipeAdapter.class::isInstance) + .map(AmbientGasCollectorRecipeAdapter.class::cast).findFirst().orElseThrow(); + var recipes = recycler.getGeneratedRecipes(helper.getLevel()); + helper.assertTrue(!recipes.isEmpty(), "Recycler must expose its native outputs"); + helper.assertTrue(recipes.stream().allMatch(r -> r.value().convertedRecipe().energy() > 0 + && r.value().convertedRecipe().inputFluids().get(0).amount() == 1000), + "Recycler must retain the water cost and FloatingLong energy cost"); + var gas = collector.getGeneratedRecipes(helper.getLevel()); + helper.assertTrue(gas.size() == 1 && gas.get(0).value().convertedRecipe().keyOutputs().size() == 1, + "Ambient collector must produce the native gas key through Applied Mekanistics"); + long water = gas.get(0).value().convertedRecipe().inputFluids().get(0).amount(); + helper.assertTrue(!collector.findMatchingRecipes(helper.getLevel(), Map.of(), + Map.of(new FluidStack(Fluids.WATER, 1), water), Map.of(), collector.getMoldItem()).isEmpty(), + "Generated gas recipes must also be available to actual machine lookup"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty", timeoutTicks = 400) + public static void recyclerRunsInSingleBlockFurnaceWithOnlyWater(GameTestHelper helper) { + var adapter = new RecyclerRecipeAdapter(); + var outputs = adapter.getGeneratedRecipes(helper.getLevel()).stream() + .flatMap(r -> r.value().convertedRecipe().outputs().stream()).toList(); + var pos = new BlockPos(2, 1, 2); + helper.setBlock(pos, ModBlocks.ADVANCED_ALLOY_FURNACE_BLOCK.get()); + var furnace = (AdvancedAlloyFurnaceBlockEntity) helper.getLevel().getBlockEntity(helper.absolutePos(pos)); + furnace.getItemHandler().setStackInSlot(AdvancedAlloyFurnaceLayout.MOLD_SLOT, adapter.getMoldItem()); + furnace.getInputFluidTank(0).setFluid(new FluidStack(Fluids.WATER, 1000)); + furnace.setEnergy(1_000_000L); + helper.succeedWhen(() -> { + boolean produced = false; + for (int slot = 9; slot < 18; slot++) { + var output = furnace.getItemHandler().getStackInSlot(slot); + if (outputs.stream().anyMatch(expected -> ItemStack.matches(expected, output))) produced = true; + } + helper.assertTrue(produced && furnace.getInputFluidTank(0).isEmpty(), + "Recycler must consume exactly one bucket and produce a native output without item inputs"); + }); + } + + @GameTest(template = "furnace_empty") + public static void staffBlocksNewRadiationAndClearsDoseBeforeEffects(GameTestHelper helper) { + var player = FakePlayerFactory.get(helper.getLevel(), new GameProfile(UUID.randomUUID(), "RadiationTest")); + player.setGameMode(GameType.SURVIVAL); + player.setItemInHand(InteractionHand.MAIN_HAND, + new ItemStack(com.sorrowmist.useless.stretcher.init.ModItems.WONDROUS_STAFF.get())); + var dose = player.getCapability(Capabilities.RADIATION_ENTITY).orElseThrow(IllegalStateException::new); + StaffRadiationProtection.configure(player, false, false); + dose.set(RadiationManager.BASELINE); + RadiationManager.get().radiate(player, 100); + helper.assertTrue(dose.getRadiation() > RadiationManager.BASELINE, "Positive control must receive radiation"); + StaffRadiationProtection.configure(player, false, true); + RadiationManager.get().radiate(player, 100); + helper.assertTrue(dose.getRadiation() == RadiationManager.BASELINE, "Enabled staff must block new radiation"); + dose.set(100); + dose.update(player); + helper.assertTrue(dose.getRadiation() == RadiationManager.BASELINE, + "Native radiation update must clear an existing dose immediately, without waiting 20 ticks"); + player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack.EMPTY); + helper.succeed(); + } + + @GameTest(template = "furnace_empty", timeoutTicks = 200) + public static void staffConnectsRestoresAndDisconnectsNativeAeMachine(GameTestHelper helper) { + var level = helper.getLevel(); + var power = new BlockPos(1, 1, 1); + var access = new BlockPos(2, 1, 1); + var machine = new BlockPos(6, 1, 1); + helper.setBlock(power, AEBlocks.CREATIVE_ENERGY_CELL.block()); + helper.setBlock(access, AEBlocks.WIRELESS_ACCESS_POINT.block()); + helper.setBlock(access.south(), AEBlocks.CREATIVE_ENERGY_CELL.block()); + helper.setBlock(machine, AEBlocks.DRIVE.block()); + helper.runAtTickTime(40, () -> { + var accessPos = helper.absolutePos(access); + var machinePos = helper.absolutePos(machine); + var player = FakePlayerFactory.get(level, new GameProfile(UUID.randomUUID(), "AeLinkTest")); + var tool = new ItemStack(com.sorrowmist.useless.stretcher.init.ModItems.WONDROUS_STAFF.get()); + player.setItemInHand(InteractionHand.MAIN_HAND, tool); + ComponentAccess.set(tool, UComponents.AeNetworkConnectComponent.get(), true); + ComponentAccess.set(tool, UComponents.WIRELESS_LINK_TARGET.get(), GlobalPos.of(level.dimension(), accessPos)); + var ap = (WirelessAccessPointBlockEntity) level.getBlockEntity(accessPos); + helper.assertTrue(ap.getMainNode().isOnline(), "Fixture access point must be online before connecting"); + var machineNode = ((DriveBlockEntity) level.getBlockEntity(machinePos)).getMainNode().getNode(); + helper.assertTrue(machineNode != null && !machineNode.isPowered(), + "Fixture machine must start outside a powered network"); + var event = new PlayerInteractEvent.RightClickBlock(player, InteractionHand.MAIN_HAND, machinePos, + new BlockHitResult(Vec3.atCenterOf(machinePos), Direction.UP, machinePos, false)); + EventHandler.onAeConnectInteract(event); + helper.assertTrue(event.isCanceled(), "Staff AE mode must consume the click instead of opening the GUI"); + var link = new AeConnectLinkSavedData.Link(level.dimension(), level.dimension(), accessPos, machinePos); + var data = AeConnectLinkSavedData.get(level.getServer()); + helper.assertTrue(data.contains(link), "Native AE machine must be linked and saved"); + var accessNode = ap.getMainNode().getNode(); + var connection = accessNode.getConnections().stream() + .filter(c -> !c.isInWorld() && c.getOtherSide(accessNode) == machineNode).findFirst().orElseThrow(); + connection.destroy(); + AeDeviceLinker.ensureLinks(level.getServer()); + helper.assertTrue(accessNode.getConnections().stream().anyMatch(c -> c.getOtherSide(accessNode) == machineNode), + "Saved links must restore using the same native host lookup"); + AeDeviceLinker.toggle(level, player, tool, machinePos); + helper.assertTrue(!data.contains(link) && accessNode.getConnections().stream() + .noneMatch(c -> c.getOtherSide(accessNode) == machineNode), "Disconnect must remove only the saved link"); + helper.assertTrue(AeDeviceLinker.isLinkTarget(level, machinePos), "Disconnected device must remain a valid host"); + helper.succeed(); + }); + } +} diff --git a/src/compatGameTest/java/com/sorrowmist/useless/tests/ExMachinisClientProbe.java b/src/compatGameTest/java/com/sorrowmist/useless/tests/ExMachinisClientProbe.java new file mode 100644 index 00000000..74c9de4c --- /dev/null +++ b/src/compatGameTest/java/com/sorrowmist/useless/tests/ExMachinisClientProbe.java @@ -0,0 +1,65 @@ +package com.sorrowmist.useless.tests; + +import com.sorrowmist.useless.UselessMod; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.gui.drawable.IDrawableStatic; +import mezz.jei.api.helpers.IGuiHelper; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.event.TickEvent; + +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicReference; + +/** Runs the real third-party constructor under the client Mixin transformer, then exits. */ +@Mod.EventBusSubscriber(modid = UselessMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) +public final class ExMachinisClientProbe { + private static volatile boolean finished; + @SubscribeEvent + public static void setup(FMLClientSetupEvent event) { + if (!Boolean.getBoolean("useless.compatClientProbe")) return; + event.enqueueWork(() -> { + try { + var background = new AtomicReference(); + var drawable = Proxy.newProxyInstance(IDrawableStatic.class.getClassLoader(), + new Class[]{IDrawableStatic.class}, (proxy, method, args) -> switch (method.getName()) { + case "getWidth" -> 82; + case "getHeight" -> 34; + default -> null; + }); + var helper = Proxy.newProxyInstance(IGuiHelper.class.getClassLoader(), + new Class[]{IGuiHelper.class}, (proxy, method, args) -> { + if (method.getName().equals("createDrawable")) background.set((ResourceLocation) args[0]); + if (method.getName().startsWith("createDrawable")) return drawable; + throw new AssertionError("Unexpected JEI call: " + method.getName()); + }); + var category = Class.forName("net.permutated.exmachinis.compat.jei.category.CompactingCategory") + .getConstructor(IGuiHelper.class).newInstance(helper); + var expected = new ResourceLocation("useless_mod", "textures/gui/exmachinis_recipe.png"); + if (!expected.equals(background.get())) throw new AssertionError("Mixin did not redirect the removed field"); + var rendered = (IDrawable) category.getClass().getMethod("getBackground").invoke(category); + if (rendered.getWidth() != 82 || rendered.getHeight() != 34) throw new AssertionError("Incorrect background size"); + try (var texture = ExMachinisClientProbe.class.getResourceAsStream( + "/assets/useless_mod/textures/gui/exmachinis_recipe.png")) { + if (texture == null || texture.readAllBytes().length < 100) throw new AssertionError("Missing background texture"); + } + System.out.println("COMPAT_CLIENT_PROBE_OK ExMachinis constructor + JEI 15.74 + redirected texture"); + finished = true; + } catch (ReflectiveOperationException | java.io.IOException failure) { + throw new IllegalStateException("Ex Machinis compatibility probe failed", failure); + } + }); + } + + @Mod.EventBusSubscriber(modid = UselessMod.MODID, value = Dist.CLIENT) + public static final class ExitAfterStartup { + @SubscribeEvent + public static void tick(TickEvent.ClientTickEvent event) { + if (finished && event.phase == TickEvent.Phase.END) Minecraft.getInstance().stop(); + } + } +} diff --git a/src/compatGameTest/resources/data/useless_mod/recipes/compat_reaction.json b/src/compatGameTest/resources/data/useless_mod/recipes/compat_reaction.json new file mode 100644 index 00000000..69a05b8a --- /dev/null +++ b/src/compatGameTest/resources/data/useless_mod/recipes/compat_reaction.json @@ -0,0 +1,10 @@ +{ + "type": "advanced_ae:reaction", + "energy": 12000, + "fluid": {"fluidStack": {"Amount": 500, "FluidName": "minecraft:water"}}, + "input_items": [ + {"amount": 2, "ingredient": {"item": "minecraft:iron_ingot"}}, + {"amount": 2, "ingredient": {"item": "minecraft:iron_ingot"}} + ], + "output": {"#": 3, "#c": "ae2:i", "id": "minecraft:diamond"} +} diff --git a/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java b/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java new file mode 100644 index 00000000..14436526 --- /dev/null +++ b/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java @@ -0,0 +1,272 @@ +package com.sorrowmist.useless.tests; + +import appeng.api.crafting.PatternDetailsHelper; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.GenericStack; +import appeng.helpers.IPatternTerminalLogicHost; +import appeng.parts.encoding.PatternEncodingLogic; +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.api.enums.FurnaceFace; +import com.sorrowmist.useless.api.enums.FurnaceFaceMode; +import com.sorrowmist.useless.compat.thermal.ThermalParallelAccess; +import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; +import com.sorrowmist.useless.content.blockentities.multiblock.MePatternAssemblyBlockEntity; +import com.sorrowmist.useless.content.machines.advanced_alloy_furnace.io.FurnaceInputPort; +import com.sorrowmist.useless.content.machines.advanced_alloy_furnace.layout.AdvancedAlloyFurnaceLayout; +import com.sorrowmist.useless.content.machines.advanced_alloy_furnace.ae.OmniversalPatternEncoding; +import com.sorrowmist.useless.content.machines.advanced_alloy_furnace.ae.PendingOmniversalPatternHolder; +import com.sorrowmist.useless.compat.forge1201.component.ComponentAccess; +import com.sorrowmist.useless.core.component.UComponents; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeCatalog; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import com.sorrowmist.useless.init.ModBlocks; +import com.sorrowmist.useless.init.ModItems; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.network.chat.Component; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.Fluids; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.templates.FluidTank; +import net.minecraftforge.gametest.GameTestHolder; +import net.minecraftforge.gametest.PrefixGameTestTemplate; +import net.minecraftforge.items.ItemStackHandler; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.List; + +@GameTestHolder(UselessMod.MODID) +@PrefixGameTestTemplate(false) +public final class MergedFurnaceGameTests { + @GameTest(template = "furnace_empty") + public static void selectedJeiRecipeEncodesItsMoldInAe2TerminalLogic(GameTestHelper helper) { + checkTerminalPatternSelection(helper, false); + } + + @GameTest(template = "furnace_empty") + public static void lateJeiSelectionConvertsAlreadyEncodedAe2Pattern(GameTestHelper helper) { + checkTerminalPatternSelection(helper, true); + } + + private static void checkTerminalPatternSelection(GameTestHelper helper, boolean lateSelection) { + var entry = AlloyFurnaceRecipeCatalog.entries(helper.getLevel()).stream() + .filter(value -> value.recipe().inputs().size() == 1 + && value.recipe().inputs().get(0).count() == 1L + && value.recipe().inputs().get(0).ingredient().test(new ItemStack(Items.RAW_IRON)) + && value.recipe().molds().size() == 1 + && value.recipe().mold().test(new ItemStack(Items.FURNACE)) + && value.recipe().outputs().size() == 1 + && value.recipe().outputs().get(0).is(Items.IRON_INGOT)) + .findFirst().orElseThrow(); + var host = new IPatternTerminalLogicHost() { + private final PatternEncodingLogic logic = new PatternEncodingLogic(this); + @Override public PatternEncodingLogic getLogic() { return logic; } + @Override public Level getLevel() { return helper.getLevel(); } + @Override public void markForSave() { } + }; + var logic = host.getLogic(); + helper.assertTrue(logic instanceof PendingOmniversalPatternHolder, + "Native AE2 terminal logic must receive the selected-recipe mixin"); + var pending = (PendingOmniversalPatternHolder) logic; + var pattern = OmniversalPatternEncoding.createProcessingPattern(entry.recipe()); + helper.assertTrue(!pattern.isEmpty(), "Native AE2 processing pattern must be encodable"); + if (lateSelection) { + logic.getEncodedPatternInv().setItemDirect(0, pattern); + helper.assertTrue(!logic.getEncodedPatternInv().getStackInSlot(0).is(ModItems.OMNIVERSAL_PATTERN.get()), + "An ordinary pattern must not be converted before a JEI recipe is selected"); + } + pending.uselessMod$setPendingOmniversalRecipe(entry.identity()); + pending.uselessMod$setPendingOmniversalSourceId(entry.sourceId()); + if (lateSelection) pending.uselessMod$tryConvertPendingOmniversalPattern(); + else logic.getEncodedPatternInv().setItemDirect(0, pattern); + var encoded = logic.getEncodedPatternInv().getStackInSlot(0); + helper.assertTrue(encoded.is(ModItems.OMNIVERSAL_PATTERN.get()), + "A selected alloy-furnace recipe must encode an omniversal pattern"); + var data = ComponentAccess.get(encoded, UComponents.OMNIVERSAL_PATTERN_DATA.get()); + helper.assertTrue(data != null && data.recipeId().equals(entry.recipe().id()) + && data.sourceId().equals(entry.sourceId()) && data.requiresMold() + && data.displayMold().isPresent() + && data.displayMold().get().getItem() == Items.FURNACE, + "Encoded pattern must retain the selected source, recipe and furnace mold"); + helper.assertTrue(pending.uselessMod$getPendingOmniversalRecipe() == null, + "A consumed JEI selection must not affect the next ordinary AE2 pattern"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty", timeoutTicks = 300) + public static void diamondHammerRunsInSingleBlockFurnace(GameTestHelper helper) { + runHammerRecipe(helper, false); + } + + @GameTest(template = "furnace_empty", timeoutTicks = 300) + public static void damagedNamedHammerRunsWithoutLosingItsNbt(GameTestHelper helper) { + runHammerRecipe(helper, true); + } + + private static void runHammerRecipe(GameTestHelper helper, boolean tagged) { + var hammerItem = ForgeRegistries.ITEMS.getValue(new ResourceLocation("exdeorum", "diamond_hammer")); + helper.assertTrue(hammerItem != null && hammerItem != Items.AIR, "Native Ex Deorum diamond hammer must exist"); + var hammer = new ItemStack(hammerItem); + if (tagged) { + hammer.setDamageValue(37); + hammer.setHoverName(Component.literal("Regression hammer")); + hammer.getOrCreateTag().putString("custom_marker", "preserved"); + } + var expectedHammer = hammer.copy(); + BlockPos local = new BlockPos(2, 1, 2); + helper.setBlock(local, ModBlocks.ADVANCED_ALLOY_FURNACE_BLOCK.get()); + var furnace = (AdvancedAlloyFurnaceBlockEntity) helper.getLevel().getBlockEntity(helper.absolutePos(local)); + furnace.getItemHandler().setStackInSlot(0, new ItemStack(Items.COBBLESTONE)); + furnace.getItemHandler().setStackInSlot(AdvancedAlloyFurnaceLayout.MOLD_SLOT, hammer); + furnace.setEnergy(100000L); + helper.succeedWhen(() -> { + int gravel = 0; + for (int slot = 9; slot < 18; slot++) { + var output = furnace.getItemHandler().getStackInSlot(slot); + if (output.is(Items.GRAVEL)) gravel += output.getCount(); + } + helper.assertTrue(gravel == 1 && furnace.getItemHandler().getStackInSlot(0).isEmpty(), + "Single-block furnace must convert one cobblestone into one gravel using the native hammer recipe"); + helper.assertTrue(ItemStack.matches(expectedHammer, + furnace.getItemHandler().getStackInSlot(AdvancedAlloyFurnaceLayout.MOLD_SLOT)), + "Hammer damage and all custom NBT must remain unchanged"); + }); + } + + @GameTest(template = "furnace_empty") + public static void thermalMachineLoadsParallelMixinAndRestoredAugments(GameTestHelper helper) { + var machine = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("thermal", "machine_smelter")); + helper.assertTrue(machine != null && machine != Blocks.AIR, "Native Thermal smelter must be registered"); + var pos = new BlockPos(2, 1, 2); + helper.setBlock(pos, machine); + var blockEntity = helper.getLevel().getBlockEntity(helper.absolutePos(pos)); + helper.assertTrue(blockEntity instanceof ThermalParallelAccess, "Thermal machine must receive the parallel mixin"); + helper.assertTrue(((ThermalParallelAccess) blockEntity).useless_mod$getParallel() == 0, + "Unaugmented machines must not receive an extra parallel operation"); + for (int index = 1; index <= 9; index++) { + helper.assertTrue(ForgeRegistries.ITEMS.containsKey(UselessMod.id("useless_integral_component_tier_" + index)), + "Restored integral component must be registered"); + } + for (int index = 1; index <= 3; index++) { + helper.assertTrue(ForgeRegistries.ITEMS.containsKey(UselessMod.id("augment_parallel_" + index)), + "Restored parallel augment must be registered"); + } + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void portableFurnaceRebindsAfterForgeCreatesTemporaryInventory(GameTestHelper helper) { + BlockPos local = new BlockPos(2, 1, 2); + BlockPos pos = helper.absolutePos(local); + var state = ModBlocks.ADVANCED_ALLOY_FURNACE_BLOCK.get().defaultBlockState(); + helper.setBlock(local, state); + var original = (AdvancedAlloyFurnaceBlockEntity) helper.getLevel().getBlockEntity(pos); + helper.assertTrue(original.tryUpgrade(10), "Useful tier must remain available"); + original.getItemHandler().setStackInSlot(0, new ItemStack(Items.DIAMOND, Integer.MAX_VALUE)); + original.setEnergy(1234567890123L); + original.getInputFluidTank(0).setFluid(new FluidStack(Fluids.WATER, 12345)); + original.setFaceMode(FurnaceFace.TOP, FurnaceFaceMode.DISABLED); + var drops = Block.getDrops(state, helper.getLevel(), pos, original, null, ItemStack.EMPTY); + helper.assertTrue(drops.size() == 1, "Exactly one portable furnace must drop"); + helper.setBlock(local, Blocks.AIR); + helper.setBlock(local, state); + var restored = (AdvancedAlloyFurnaceBlockEntity) helper.getLevel().getBlockEntity(pos); + restored.getExternalInventoryReference(); + state.getBlock().setPlacedBy(helper.getLevel(), pos, state, null, drops.get(0)); + helper.assertTrue(restored.getItemHandler().getStackInSlot(0).getCount() == Integer.MAX_VALUE + && restored.getEnergy() == 1234567890123L + && restored.getInputFluidTank(0).getFluidAmount() == 12345 + && restored.getFurnaceTier() == 10 + && restored.getFaceMode(FurnaceFace.TOP) == FurnaceFaceMode.DISABLED, + "Portable furnace must recover its original inventory, energy, fluid, tier and side configuration"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void portableAssemblyRecoversOriginalPatternReference(GameTestHelper helper) { + BlockPos local = new BlockPos(2, 1, 2); + BlockPos pos = helper.absolutePos(local); + var state = ModBlocks.ME_PATTERN_ASSEMBLY.get().defaultBlockState(); + helper.setBlock(local, state); + var original = (MePatternAssemblyBlockEntity) helper.getLevel().getBlockEntity(pos); + var pattern = PatternDetailsHelper.encodeProcessingPattern( + new GenericStack[]{new GenericStack(AEItemKey.of(Items.RAW_IRON), 1)}, + new GenericStack[]{new GenericStack(AEItemKey.of(Items.IRON_INGOT), 1)}); + original.getPatterns().setStackInSlot(0, pattern); + var drops = Block.getDrops(state, helper.getLevel(), pos, original, null, ItemStack.EMPTY); + helper.assertTrue(drops.size() == 1, "Exactly one portable assembly must drop"); + helper.setBlock(local, Blocks.AIR); + helper.setBlock(local, state); + var restored = (MePatternAssemblyBlockEntity) helper.getLevel().getBlockEntity(pos); + restored.getExternalInventoryReference(); + state.getBlock().setPlacedBy(helper.getLevel(), pos, state, null, drops.get(0)); + helper.assertTrue(ItemStack.matches(pattern, restored.getPatterns().getStackInSlot(0)), + "Rebinding must replace the temporary inventory with the portable pattern inventory"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty", timeoutTicks = 300) + public static void furnaceRunsNativeSmeltingRecipe(GameTestHelper helper) { + BlockPos local = new BlockPos(2, 1, 2); + helper.setBlock(local, ModBlocks.ADVANCED_ALLOY_FURNACE_BLOCK.get()); + var furnace = (AdvancedAlloyFurnaceBlockEntity) helper.getLevel().getBlockEntity(helper.absolutePos(local)); + furnace.getItemHandler().setStackInSlot(0, new ItemStack(Items.RAW_IRON)); + furnace.getItemHandler().setStackInSlot(AdvancedAlloyFurnaceLayout.MOLD_SLOT, new ItemStack(Items.FURNACE)); + furnace.setEnergy(100000L); + helper.succeedWhen(() -> { + boolean output = false; + for (int slot = 9; slot < 18; slot++) output |= furnace.getItemHandler().getStackInSlot(slot).is(Items.IRON_INGOT); + helper.assertTrue(output && furnace.getItemHandler().getStackInSlot(0).isEmpty(), + "Registered furnace must consume raw iron and produce an iron ingot"); + }); + } + + @GameTest(template = "furnace_empty") + public static void extraSourcesContainRealRecipesAfterNativeDataLoad(GameTestHelper helper) { + AlloyFurnaceRecipeCatalog.prewarm(helper.getLevel()); + int adapters = 0; + for (var adapter : com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeManager.getInstance().getRegisteredAdapters()) { + if (adapter instanceof com.sorrowmist.useless.content.recipe.adapters.BatchRecipeAdapter) { + helper.assertTrue(!adapter.getMoldItem().isEmpty(), "Every restored adapter needs its native mold: " + adapter.getClass()); + adapters++; + } + } + helper.assertTrue(adapters == 11, "All eleven extra machine adapters must be registered"); + for (String source : List.of("thermal", "bloodmagic", "embers")) { + helper.assertTrue(!AlloyFurnaceRecipeCatalog.entries(helper.getLevel(), source).isEmpty(), + "Native data load must populate the " + source + " recipe source"); + } + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void retainedSeedAndWaterUseTheSameAtomicInputPort(GameTestHelper helper) { + var recipe = new AdvancedAlloyFurnaceRecipe(UselessMod.id("test_retained_seed"), + List.of(new CountedIngredient(Ingredient.of(Items.WHEAT_SEEDS), 1L, false)), + List.of(new FluidStack(Fluids.WATER, 500)), List.of(new ItemStack(Items.WHEAT)), + List.of(), 100L, 20, Ingredient.EMPTY, 0, Ingredient.EMPTY, AlloyFurnaceMode.INSOLATOR); + var inventory = new ItemStackHandler(1); + inventory.setStackInSlot(0, new ItemStack(Items.WHEAT_SEEDS)); + var water = new FluidTank(1000); + water.setFluid(new FluidStack(Fluids.WATER, 1000)); + FluidTank[] tanks = {water}; + helper.assertTrue(FurnaceInputPort.calculateMaterialParallel(recipe, inventory, 0, 1, tanks, 1) == 2, + "Retained seeds must not cap parallelism, but water must"); + helper.assertTrue(FurnaceInputPort.consumeRecipeInputs(recipe, 2, inventory, 0, 1, tanks, 1) + && inventory.getStackInSlot(0).getCount() == 1 && water.getFluidAmount() == 0, + "Two operations must drain exactly 1000 mB while retaining the seed"); + helper.assertTrue(!FurnaceInputPort.consumeRecipeInputs(recipe, 1, inventory, 0, 1, tanks, 1) + && inventory.getStackInSlot(0).getCount() == 1, + "An unavailable fluid must fail atomically without consuming the retained input"); + helper.succeed(); + } +} diff --git a/src/gameTest/resources/data/useless_mod/structures/furnace_empty.nbt b/src/gameTest/resources/data/useless_mod/structures/furnace_empty.nbt new file mode 100644 index 0000000000000000000000000000000000000000..7853e37e714c5bf71089b8d46cc875bfc4898917 GIT binary patch literal 111 zcmb2|=3oE;rvIv4hFlE>0xaQEGL-60+`4t?-xOiKL0B4-5}A{SlHk?`N=~m zhl6Q88(pg>r-znVuI4-Iqn>P``avx7`b3pych~&8)fciXMwVSv^xtkb`-zth8J-kv PczBfkkbFc&5zra{({C>> literal 0 HcmV?d00001 diff --git a/src/main/java/com/sorrowmist/useless/UselessMod.java b/src/main/java/com/sorrowmist/useless/UselessMod.java index 1dcbcb2d..b8de687b 100644 --- a/src/main/java/com/sorrowmist/useless/UselessMod.java +++ b/src/main/java/com/sorrowmist/useless/UselessMod.java @@ -81,6 +81,8 @@ public UselessMod() { ModPOIs.POI_TYPES.register(modEventBus); ModCreativeTabs.CREATIVE_TAB.register(modEventBus); + OptionalIntegration.call("thermal", "com.sorrowmist.useless.compat.thermal.ThermalItems", + "register", Void.class, null, new Class[]{net.minecraftforge.eventbus.api.IEventBus.class}, modEventBus); modEventBus.addListener(ModNetwork::registerPayloadHandlers); diff --git a/src/main/java/com/sorrowmist/useless/client/gui/FurnaceScreenScaler.java b/src/main/java/com/sorrowmist/useless/client/gui/FurnaceScreenScaler.java new file mode 100644 index 00000000..c960b5ba --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/client/gui/FurnaceScreenScaler.java @@ -0,0 +1,44 @@ +package com.sorrowmist.useless.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; + +/** Fits the furnace panels without changing the player's saved GUI scale option. */ +public final class FurnaceScreenScaler { + private static Double preferredScale; + private static double appliedScale; + + private FurnaceScreenScaler() { + } + + public static void update() { + Minecraft client = Minecraft.getInstance(); + var screen = client.screen; + var window = client.getWindow(); + double current = window.getGuiScale(); + boolean furnace = screen instanceof AdvancedAlloyFurnaceScreen + || screen instanceof MultiblockAlloyFurnaceScreen + || screen instanceof PagedRecoverableScreen + || screen instanceof PassiveCraftingHatchScreen + || screen instanceof PatternConverterScreen; + if (furnace) { + if (preferredScale == null || current != appliedScale) preferredScale = current; + var container = (AbstractContainerScreen) screen; + appliedScale = scaleForWindow(preferredScale, window.getHeight(), container.getYSize()); + if (current == appliedScale) return; + window.setGuiScale(appliedScale); + } else { + if (preferredScale == null) return; + double restore = preferredScale; + preferredScale = null; + if (current != appliedScale) return; + window.setGuiScale(restore); + } + // Resize both drawing and mouse hit boxes into the new logical coordinate space. + if (screen != null) screen.resize(client, window.getGuiScaledWidth(), window.getGuiScaledHeight()); + } + + static double scaleForWindow(double preferred, int height, int panelHeight) { + return Math.max(1.0D, Math.min(preferred, Math.floor((double) height / (panelHeight + 16)))); + } +} diff --git a/src/main/java/com/sorrowmist/useless/client/network/ClientPacketHandlers.java b/src/main/java/com/sorrowmist/useless/client/network/ClientPacketHandlers.java index e7a42b34..9ef2b65c 100644 --- a/src/main/java/com/sorrowmist/useless/client/network/ClientPacketHandlers.java +++ b/src/main/java/com/sorrowmist/useless/client/network/ClientPacketHandlers.java @@ -44,6 +44,27 @@ * 仍需加载它们。参数一律使用公共的载荷类型。 */ public final class ClientPacketHandlers { + public static void handleFurnaceMenuSync(com.sorrowmist.useless.network.FurnaceMenuSyncPacket packet) { + var player = Minecraft.getInstance().player; + if (player == null || player.containerMenu.containerId != packet.containerId()) return; + var menu = player.containerMenu; + com.sorrowmist.useless.content.menus.FurnaceMenuSynchronizer synchronizer; + if (menu instanceof com.sorrowmist.useless.content.menus.AdvancedAlloyFurnaceMenu furnace) { + synchronizer = furnace.getFurnaceSynchronizer(); + } else if (menu instanceof MultiblockAlloyFurnaceMenu furnace) { + synchronizer = furnace.getFurnaceSynchronizer(); + } else if (menu instanceof com.sorrowmist.useless.content.menus.PagedRecoverableMenu furnace) { + synchronizer = furnace.getFurnaceSynchronizer(); + } else { + return; + } + if (!packet.slots().isEmpty() && packet.slots().size() != menu.slots.size()) return; + // Restore paging first so the slot snapshot is written into the selected page. + if (!synchronizer.applyData(packet.data())) return; + if (!packet.slots().isEmpty()) menu.initializeContents(packet.stateId(), packet.slots(), packet.carried()); + else menu.setCarried(packet.carried()); + } + private ClientPacketHandlers() {} /** 同步被保护玩家的血量与状态(抑制死亡与无敌帧,维持存活表现)。 */ diff --git a/src/main/java/com/sorrowmist/useless/compat/ae/AeDeviceLinker.java b/src/main/java/com/sorrowmist/useless/compat/ae/AeDeviceLinker.java index 585b1bf3..bfd77c97 100644 --- a/src/main/java/com/sorrowmist/useless/compat/ae/AeDeviceLinker.java +++ b/src/main/java/com/sorrowmist/useless/compat/ae/AeDeviceLinker.java @@ -1,6 +1,5 @@ package com.sorrowmist.useless.compat.ae; -import appeng.capabilities.Capabilities; import appeng.api.networking.GridHelper; import appeng.api.networking.IGrid; import appeng.api.networking.IGridConnection; @@ -60,7 +59,7 @@ public static boolean isLinkTarget(Level level, BlockPos pos) { if (!ModList.get().isLoaded("ae2")) { return false; } - return com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess.get(level, appeng.capabilities.Capabilities.IN_WORLD_GRID_NODE_HOST, pos, null) != null; + return AeNetworks.host(level, pos) != null; } /** @@ -89,8 +88,7 @@ public static void toggle(ServerLevel level, ServerPlayer player, ItemStack tool // 而且只会拆「我们自己建的那条非空间连接」:线缆连接和其它模组的无线连接都原样保留。 if (data.contains(link)) { IGridNode accessNode = resolveAccessNode(level, bound); - IInWorldGridNodeHost host = com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess.get(level, - appeng.capabilities.Capabilities.IN_WORLD_GRID_NODE_HOST, machinePos, null); + IInWorldGridNodeHost host = AeNetworks.host(level, machinePos); IGridNode machineNode = host == null ? null : resolveNode(host); if (accessNode != null && machineNode != null) { destroyLink(accessNode, machineNode); @@ -106,8 +104,7 @@ public static void toggle(ServerLevel level, ServerPlayer player, ItemStack tool return; } - IInWorldGridNodeHost host = com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess.get(level, - appeng.capabilities.Capabilities.IN_WORLD_GRID_NODE_HOST, machinePos, null); + IInWorldGridNodeHost host = AeNetworks.host(level, machinePos); IGridNode machineNode = host == null ? null : resolveNode(host); if (machineNode == null) { notify(player, "gui.useless_mod.ae_connect.not_ready", ChatFormatting.YELLOW); @@ -239,8 +236,7 @@ public static void ensureLinks(MinecraftServer server) { continue; } - IInWorldGridNodeHost host = com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess.get(machineLevel, - appeng.capabilities.Capabilities.IN_WORLD_GRID_NODE_HOST, link.machine(), null); + IInWorldGridNodeHost host = AeNetworks.host(machineLevel, link.machine()); if (host == null) { data.removeByMachine(link.machineDimension(), link.machine()); continue; diff --git a/src/main/java/com/sorrowmist/useless/compat/ae/AeNetworks.java b/src/main/java/com/sorrowmist/useless/compat/ae/AeNetworks.java index 8ceba5ac..dbbfbf8b 100644 --- a/src/main/java/com/sorrowmist/useless/compat/ae/AeNetworks.java +++ b/src/main/java/com/sorrowmist/useless/compat/ae/AeNetworks.java @@ -1,6 +1,6 @@ package com.sorrowmist.useless.compat.ae; -import appeng.capabilities.Capabilities; +import appeng.api.networking.GridHelper; import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; import appeng.api.networking.IInWorldGridNodeHost; @@ -24,7 +24,7 @@ *

早先这里只认 {@code WirelessAccessPointBlockEntity},于是玩家把 ME 接口、终端、 * 总线这类方块绑进无线物流、并把资源类型选成 {@code AE_*} 时,端点永远解析成 {@code null}: * 方块本身明明就在、也明明挂在网上,却像不存在一样。现在判定改为 - * 注册了 AE2 网格节点宿主能力({@code appeng.capabilities.Capabilities.IN_WORLD_GRID_NODE_HOST})即可, + * 由 AE2 的 {@code GridHelper.getNodeHost} 识别为网格节点宿主即可, * 与 {@link AeDeviceLinker#isLinkTarget} 同一把尺子,无线访问点只是其中一种。

* *

为什么判定不看「在线」

@@ -48,14 +48,16 @@ static boolean isEndpoint(Level level, BlockPos pos) { return host(level, pos) != null; } - /** 该坐标的网格节点宿主能力;方块不是 AE 设备时为 {@code null}。 */ + /** 同时识别直接实现宿主接口的 AE2 方块和通过 capability 暴露宿主的附属模组方块。 */ @Nullable static IInWorldGridNodeHost host(Level level, BlockPos pos) { if (level == null || pos == null || !level.isLoaded(pos)) { return null; } try { - return com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess.get(level, appeng.capabilities.Capabilities.IN_WORLD_GRID_NODE_HOST, pos, null); + var entity = level.getBlockEntity(pos); + if (entity == null || entity.isRemoved()) return null; + return GridHelper.getNodeHost(level, pos); } catch (Throwable notReady) { // 能力查询本身不该抛,但方块实体可能正处于替换/卸载的中间态。当作「暂时没有」。 return null; diff --git a/src/main/java/com/sorrowmist/useless/compat/bloodmagic/BloodMagicRecipeCompatLoader.java b/src/main/java/com/sorrowmist/useless/compat/bloodmagic/BloodMagicRecipeCompatLoader.java new file mode 100644 index 00000000..57c01bbd --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/bloodmagic/BloodMagicRecipeCompatLoader.java @@ -0,0 +1,19 @@ +package com.sorrowmist.useless.compat.bloodmagic; + +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeManager; +import com.sorrowmist.useless.content.recipe.adapters.bloodmagic.BloodMagicRecipeAdapter; +import wayoftime.bloodmagic.recipe.RecipeAlchemyTable; +import wayoftime.bloodmagic.recipe.RecipeBloodAltar; +import wayoftime.bloodmagic.recipe.RecipeTartaricForge; + +public final class BloodMagicRecipeCompatLoader { + private BloodMagicRecipeCompatLoader() { + } + + public static void register() { + var manager = AlloyFurnaceRecipeManager.getInstance(); + manager.registerAdapter(new BloodMagicRecipeAdapter<>(RecipeBloodAltar.class, "altar", "bm_altar_")); + manager.registerAdapter(new BloodMagicRecipeAdapter<>(RecipeTartaricForge.class, "soulforge", "bm_soulforge_")); + manager.registerAdapter(new BloodMagicRecipeAdapter<>(RecipeAlchemyTable.class, "alchemytable", "bm_alchemytable_")); + } +} diff --git a/src/main/java/com/sorrowmist/useless/compat/embers/EmbersRecipeCompatLoader.java b/src/main/java/com/sorrowmist/useless/compat/embers/EmbersRecipeCompatLoader.java new file mode 100644 index 00000000..1c543f6f --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/embers/EmbersRecipeCompatLoader.java @@ -0,0 +1,27 @@ +package com.sorrowmist.useless.compat.embers; + +import com.rekindled.embers.recipe.IAlchemyRecipe; +import com.rekindled.embers.recipe.IMeltingRecipe; +import com.rekindled.embers.recipe.IMixingRecipe; +import com.rekindled.embers.recipe.IStampingRecipe; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeManager; +import com.sorrowmist.useless.content.recipe.adapters.embers.EmbersRecipeAdapter; + +public final class EmbersRecipeCompatLoader { + private EmbersRecipeCompatLoader() { + } + + public static void register() { + var manager = AlloyFurnaceRecipeManager.getInstance(); + manager.registerAdapter(new EmbersRecipeAdapter<>(IAlchemyRecipe.class, + "alchemy_tablet", "embers_alchemy_", false)); + manager.registerAdapter(new EmbersRecipeAdapter<>(IMeltingRecipe.class, + "geologic_separator", "embers_geo_separator_", true)); + manager.registerAdapter(new EmbersRecipeAdapter<>(IMeltingRecipe.class, + "melter", "embers_melter_", false)); + manager.registerAdapter(new EmbersRecipeAdapter<>(IStampingRecipe.class, + "stamper", "embers_stamping_", false)); + manager.registerAdapter(new EmbersRecipeAdapter<>(IMixingRecipe.class, + "mixer_centrifuge", "embers_mixing_", false)); + } +} diff --git a/src/main/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferHandler.java b/src/main/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferHandler.java index 1872b5de..f40012fd 100644 --- a/src/main/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferHandler.java +++ b/src/main/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferHandler.java @@ -5,7 +5,6 @@ import appeng.api.stacks.GenericStack; import appeng.integration.modules.jeirei.EncodingHelper; import appeng.menu.me.items.PatternEncodingTermMenu; -import com.sorrowmist.useless.compat.forge1201.OptionalIntegration; import com.sorrowmist.useless.content.recipe.AdapterUtils; import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeCatalog; @@ -133,8 +132,7 @@ public mezz.jei.api.recipe.RecipeType getRecipe recipe.id(), AlloyFurnaceRecipeFingerprint.create(recipe, player.level().registryAccess()), entry.sourceId())); - OptionalIntegration.call("extendedae_plus", "com.sorrowmist.useless.compat.extendedae.ExtendedAEPlusJeiCompat", - "presetAlloyFurnaceSearchKey", Void.class, null, new Class[]{}); + // Forge ExtendedAE Plus has no search-preset API; it must not gate AE2 pattern transfer. EncodingHelper.encodeProcessingRecipe(menu, inputs, outputs); return null; } diff --git a/src/main/java/com/sorrowmist/useless/compat/mekanism/MekanismRecipeCompatLoader.java b/src/main/java/com/sorrowmist/useless/compat/mekanism/MekanismRecipeCompatLoader.java index 25048ece..ab82a75f 100644 --- a/src/main/java/com/sorrowmist/useless/compat/mekanism/MekanismRecipeCompatLoader.java +++ b/src/main/java/com/sorrowmist/useless/compat/mekanism/MekanismRecipeCompatLoader.java @@ -10,6 +10,7 @@ import com.sorrowmist.useless.content.recipe.adapters.mekanism.EnrichmentChamberRecipeAdapter; import com.sorrowmist.useless.content.recipe.adapters.mekanism.FluidToFluidRecipeAdapter; import com.sorrowmist.useless.content.recipe.adapters.mekanism.HeavyWaterRecipeAdapter; +import com.sorrowmist.useless.content.recipe.adapters.mekanism.MetallurgicItemRecipeAdapter; import com.sorrowmist.useless.content.recipe.adapters.mekanism.NutritionalLiquifierRecipeAdapter; import com.sorrowmist.useless.content.recipe.adapters.mekanism.PrecisionSawmillRecipeAdapter; import com.sorrowmist.useless.core.config.ConfigManager; @@ -29,6 +30,10 @@ public static void register() { manager.registerAdapter(new NutritionalLiquifierRecipeAdapter(), RecipeSourceIds.MEKANISM); manager.registerAdapter(new FluidToFluidRecipeAdapter(), RecipeSourceIds.MEKANISM); manager.registerAdapter(new HeavyWaterRecipeAdapter(), RecipeSourceIds.MEKANISM); + if (ConfigManager.isRecipeConversionEnabled(RecipeSourceIds.LEGACY_METALLURGIC)) { + manager.registerAdapter(new MetallurgicItemRecipeAdapter(), + RecipeSourceIds.LEGACY_METALLURGIC); + } if (ModList.get().isLoaded("appmek") && ConfigManager.isRecipeConversionEnabled(RecipeSourceIds.APP_MEK)) { diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItemStorageAccess.java b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItemStorageAccess.java new file mode 100644 index 00000000..10a032f7 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItemStorageAccess.java @@ -0,0 +1,29 @@ +package com.sorrowmist.useless.compat.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +public interface ThermalItemStorageAccess { + void useless_mod$setParallel(int count); + int useless_mod$getParallel(); +} diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItems.java b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItems.java new file mode 100644 index 00000000..7cf9fa55 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalItems.java @@ -0,0 +1,41 @@ +package com.sorrowmist.useless.compat.thermal; + +import cofh.core.util.helpers.AugmentDataHelper; +import cofh.thermal.lib.common.item.AugmentItem; +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.compat.forge1201.registry.DeferredItem; +import com.sorrowmist.useless.compat.forge1201.registry.DeferredRegister; +import com.sorrowmist.useless.init.ModCreativeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Rarity; +import net.minecraftforge.eventbus.api.IEventBus; + +public final class ThermalItems { + public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(UselessMod.MODID); + + static { + float[] modifiers = {10.0F, 25.0F, 50.0F, 100.0F, 250.0F, 500.0F, 1000.0F, 2500.0F, 5000.0F}; + for (int index = 0; index < modifiers.length; index++) { + float modifier = modifiers[index]; + DeferredItem item = ITEMS.register("useless_integral_component_tier_" + (index + 1), + () -> new UpgradeAugmentItem(new Item.Properties().rarity(Rarity.EPIC), + AugmentDataHelper.builder().type("Upgrade").mod("BaseMod", modifier).build())); + ModCreativeTabs.EXTRA_TAB_ITEMS.add(item); + } + float[] parallel = {1.0F, 16.0F, 256.0F}; + for (int index = 0; index < parallel.length; index++) { + float modifier = parallel[index]; + DeferredItem item = ITEMS.register("augment_parallel_" + (index + 1), + () -> new AugmentItem(new Item.Properties(), + AugmentDataHelper.builder().type("Machine").mod("MachineParallel", modifier).build())); + ModCreativeTabs.EXTRA_TAB_ITEMS.add(item); + } + } + + private ThermalItems() { + } + + public static void register(IEventBus bus) { + ITEMS.register(bus); + } +} diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalMixinPlugin.java b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalMixinPlugin.java new file mode 100644 index 00000000..ef7e2200 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalMixinPlugin.java @@ -0,0 +1,33 @@ +package com.sorrowmist.useless.compat.thermal; + +import net.minecraftforge.fml.loading.LoadingModList; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +/** Keeps CoFH targets optional before normal mod construction begins. */ +public final class ThermalMixinPlugin implements IMixinConfigPlugin { + @Override public void onLoad(String mixinPackage) { + } + + @Override public String getRefMapperConfig() { return null; } + + @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + LoadingModList mods = LoadingModList.get(); + return mods != null && mods.getMods().stream().anyMatch(mod -> mod.getModId().equals("thermal")); + } + + @Override public void acceptTargets(Set myTargets, Set otherTargets) { + } + + @Override public List getMixins() { return null; } + + @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } + + @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } +} diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelAccess.java b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelAccess.java new file mode 100644 index 00000000..1bb0bdfc --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelAccess.java @@ -0,0 +1,32 @@ +package com.sorrowmist.useless.compat.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +public interface ThermalParallelAccess { + int useless_mod$getParallel(); + void useless_mod$setParallel(int parallel); + + + +} diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMath.java b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMath.java new file mode 100644 index 00000000..d940c30d --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMath.java @@ -0,0 +1,18 @@ +package com.sorrowmist.useless.compat.thermal; + +/** Parallel values represent extra operations beyond the machine's native operation. */ +public final class ThermalParallelMath { + private ThermalParallelMath() { + } + + public static int extraOperations(float augment, float baseModifier, boolean applyBaseModifier) { + double extra = applyBaseModifier ? (Math.max(0.0D, augment) + 1.0D) * baseModifier - 1.0D : augment; + return (int) Math.max(0.0D, Math.min(Integer.MAX_VALUE - 1.0D, extra)); + } + + public static int scale(int value, int extraOperations) { + if (value <= 0) return 0; + long scaled = (long) value * (1L + Math.max(0, extraOperations)); + return (int) Math.min(Integer.MAX_VALUE, scaled); + } +} diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalRecipeCompatLoader.java b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalRecipeCompatLoader.java new file mode 100644 index 00000000..03a5f647 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/ThermalRecipeCompatLoader.java @@ -0,0 +1,23 @@ +package com.sorrowmist.useless.compat.thermal; + +import cofh.thermal.core.util.recipes.machine.InsolatorRecipe; +import cofh.thermal.core.util.recipes.machine.PressRecipe; +import cofh.thermal.core.util.recipes.machine.SmelterRecipe; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeManager; +import com.sorrowmist.useless.content.recipe.adapters.thermal.ThermalRecipeAdapter; + +public final class ThermalRecipeCompatLoader { + private ThermalRecipeCompatLoader() { + } + + public static void register() { + var manager = AlloyFurnaceRecipeManager.getInstance(); + manager.registerAdapter(new ThermalRecipeAdapter<>(InsolatorRecipe.class, "machine_insolator", + "thermal_insolator_", AlloyFurnaceMode.INSOLATOR)); + manager.registerAdapter(new ThermalRecipeAdapter<>(SmelterRecipe.class, "machine_smelter", + "thermal_smelter_", AlloyFurnaceMode.NORMAL)); + manager.registerAdapter(new ThermalRecipeAdapter<>(PressRecipe.class, "machine_press", + "thermal_press_", AlloyFurnaceMode.PRESS)); + } +} diff --git a/src/main/java/com/sorrowmist/useless/compat/thermal/UpgradeAugmentItem.java b/src/main/java/com/sorrowmist/useless/compat/thermal/UpgradeAugmentItem.java new file mode 100644 index 00000000..269b6e6e --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/compat/thermal/UpgradeAugmentItem.java @@ -0,0 +1,51 @@ +package com.sorrowmist.useless.compat.thermal; + +/* + * This file is part of Thermal More Integral Components. + * + * Copyright (c) 2024 Elephant_dev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import cofh.lib.util.helpers.StringHelper; +import cofh.thermal.lib.common.item.AugmentItem; +import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class UpgradeAugmentItem extends AugmentItem { + public UpgradeAugmentItem(Item.Properties builder, CompoundTag augmentData) { + super(builder, augmentData); + } + + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable Level worldIn, List tooltip, TooltipFlag flagIn) { + tooltip.add(1, StringHelper.getTextComponent("useless_mod.augment.upgrade").withStyle(ChatFormatting.GOLD)); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/blockentities/AdvancedAlloyFurnaceBlockEntity.java b/src/main/java/com/sorrowmist/useless/content/blockentities/AdvancedAlloyFurnaceBlockEntity.java index 375664a3..3a261d6f 100644 --- a/src/main/java/com/sorrowmist/useless/content/blockentities/AdvancedAlloyFurnaceBlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/content/blockentities/AdvancedAlloyFurnaceBlockEntity.java @@ -887,7 +887,12 @@ public ExternalInventoryReference getExternalInventoryReference() { public void bindExternalInventory(@Nullable ExternalInventoryReference requested, @Nullable CompoundTag legacyInventory, HolderLookup.Provider registries) { - if (externalInventoryLoaded || level == null || level.isClientSide) return; + if (level == null || level.isClientSide) return; + if (externalInventoryLoaded) { + if (requested == null || requested.equals(inventoryReference)) return; + // Forge may call onLoad before setPlacedBy supplies the portable inventory reference. + ExternalInventoryStore.release(level, worldPosition, inventoryReference); + } inventoryReference = ExternalInventoryStore.bindAt( level, ExternalInventoryKind.FURNACE, requested, worldPosition, itemHandler, legacyInventory, registries); diff --git a/src/main/java/com/sorrowmist/useless/content/blockentities/multiblock/MePatternAssemblyBlockEntity.java b/src/main/java/com/sorrowmist/useless/content/blockentities/multiblock/MePatternAssemblyBlockEntity.java index e9a9d1d6..fe63ff8c 100644 --- a/src/main/java/com/sorrowmist/useless/content/blockentities/multiblock/MePatternAssemblyBlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/content/blockentities/multiblock/MePatternAssemblyBlockEntity.java @@ -152,7 +152,12 @@ public void restoreItemData(MultiblockPartData data, HolderLookup.Provider regis public void bindExternalInventory(@Nullable ExternalInventoryReference requested, @Nullable CompoundTag legacyInventory, HolderLookup.Provider registries) { - if (externalInventoryLoaded || level == null || level.isClientSide) return; + if (level == null || level.isClientSide) return; + if (externalInventoryLoaded) { + if (requested == null || requested.equals(inventoryReference)) return; + // Match the portable-reference recovery used by the mold hub and passive hatch. + ExternalInventoryStore.release(level, worldPosition, inventoryReference); + } inventoryReference = ExternalInventoryStore.bindAt( level, ExternalInventoryKind.PATTERN_ASSEMBLY, requested, worldPosition, patterns, legacyInventory, registries); diff --git a/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/AdvancedAlloyFurnaceAeManager.java b/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/AdvancedAlloyFurnaceAeManager.java index ea5b23da..79e69a42 100644 --- a/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/AdvancedAlloyFurnaceAeManager.java +++ b/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/AdvancedAlloyFurnaceAeManager.java @@ -1204,7 +1204,15 @@ private boolean commitOmniversalBatch(OmniversalPatternDetails pattern, if (manualOperations <= 0L) { return false; } - List unitOutputs = AlloyFurnaceBigIntegerCrafting.unitOutputs(recipe, manualOperations); + List prototypeKeys = new ArrayList<>(); + for (KeyCounter counter : unitPrototype) { + for (var entry : counter) prototypeKeys.add(new GenericStack(entry.getKey(), entry.getLongValue())); + } + var retained = com.sorrowmist.useless.content.recipe.RetainedRecipeInputs.remainders( + recipe, List.of(), prototypeKeys, manualOperations); + if (retained == null) return false; + List unitOutputs = new ArrayList<>(AlloyFurnaceBigIntegerCrafting.unitOutputs(recipe, manualOperations)); + unitOutputs.addAll(retained); if (unitOutputs.isEmpty()) { return false; } diff --git a/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/CraftingTask.java b/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/CraftingTask.java index e1484c39..358509d2 100644 --- a/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/CraftingTask.java +++ b/src/main/java/com/sorrowmist/useless/content/machines/advanced_alloy_furnace/ae/CraftingTask.java @@ -738,7 +738,16 @@ public boolean tick() { } } + var retained = com.sorrowmist.useless.content.recipe.RetainedRecipeInputs.remainders( + recipe, taskInputItems, toGenericStacks(taskInputKeys), craftCount); + if (retained == null) { + updateWaitingProgress(); + return false; + } generatePendingOutputs(craftCount); + for (GenericStack remainder : retained) { + pendingOutputKeys.add(new KeyAmount(remainder.what(), remainder.amount())); + } taskInputItems.clear(); taskInputFluids.clear(); taskInputKeys.clear(); diff --git a/src/main/java/com/sorrowmist/useless/content/menus/AdvancedAlloyFurnaceMenu.java b/src/main/java/com/sorrowmist/useless/content/menus/AdvancedAlloyFurnaceMenu.java index acdb54b1..e7346976 100644 --- a/src/main/java/com/sorrowmist/useless/content/menus/AdvancedAlloyFurnaceMenu.java +++ b/src/main/java/com/sorrowmist/useless/content/menus/AdvancedAlloyFurnaceMenu.java @@ -96,6 +96,20 @@ public class AdvancedAlloyFurnaceMenu extends AEBaseMenu { private final AdvancedAlloyFurnaceBlockEntity blockEntity; private final ContainerData data; + private final Player menuPlayer; + private final FurnaceMenuSynchronizer furnaceSync = new FurnaceMenuSynchronizer(); + + public FurnaceMenuSynchronizer getFurnaceSynchronizer() { + return furnaceSync; + } + + @Override + public void broadcastChanges() { + super.broadcastChanges(); + if (menuPlayer instanceof net.minecraft.server.level.ServerPlayer player) { + furnaceSync.broadcast(this, player); + } + } public AdvancedAlloyFurnaceMenu(int containerId, Inventory inv, FriendlyByteBuf buf) { this(containerId, inv, buf != null ? buf.readBlockPos() : BlockPos.ZERO); @@ -117,8 +131,10 @@ public AdvancedAlloyFurnaceMenu(int containerId, Inventory inv, AdvancedAlloyFur ContainerData data) { super(ModMenuType.ADVANCED_ALLOY_FURNACE_MENU.get(), containerId, inv, entity); this.data = data; + this.menuPlayer = inv.player; this.blockEntity = entity; this.addDataSlots(data); + furnaceSync.addData(data); IItemHandler itemHandler = entity != null ? entity.getItemHandler() : new ItemStackHandler(AdvancedAlloyFurnaceLayout.TOTAL_SLOTS); this.addMachineSlots(itemHandler); diff --git a/src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccess.java b/src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccess.java new file mode 100644 index 00000000..5ae566ca --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccess.java @@ -0,0 +1,24 @@ +package com.sorrowmist.useless.content.menus; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; + +/** Checks the open machine menu before accepting a furnace control packet. */ +public final class FurnaceMenuAccess { + private FurnaceMenuAccess() { + } + + public static boolean canUse(ServerPlayer player, BlockPos pos) { + if (player == null || pos == null || player.isSpectator() + || !player.level().hasChunkAt(pos) || !player.containerMenu.stillValid(player) + || player.distanceToSqr(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D) > 64.0D) { + return false; + } + if (player.containerMenu instanceof AdvancedAlloyFurnaceMenu menu) { + return menu.getBlockEntity() != null && pos.equals(menu.getBlockEntity().getBlockPos()); + } + if (player.containerMenu instanceof MultiblockAlloyFurnaceMenu menu) return pos.equals(menu.getBlockPos()); + if (player.containerMenu instanceof PagedRecoverableMenu menu) return pos.equals(menu.getBlockPos()); + return false; + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizer.java b/src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizer.java new file mode 100644 index 00000000..fc487277 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizer.java @@ -0,0 +1,61 @@ +package com.sorrowmist.useless.content.menus; + +import com.sorrowmist.useless.compat.forge1201.network.PacketDistributor; +import com.sorrowmist.useless.network.FurnaceMenuSyncPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +/** Supplements vanilla's byte-sized stack counts and short-sized menu values. */ +public final class FurnaceMenuSynchronizer { + private final List dataGroups = new ArrayList<>(); + private List lastData = List.of(); + private List lastSlots = List.of(); + private ItemStack lastCarried = ItemStack.EMPTY; + + public void addData(ContainerData data) { + dataGroups.add(data); + } + + public void broadcast(AbstractContainerMenu menu, ServerPlayer player) { + List data = new ArrayList<>(); + for (ContainerData group : dataGroups) { + for (int index = 0; index < group.getCount(); index++) data.add(group.get(index)); + } + boolean itemsChanged = lastSlots.size() != menu.slots.size() + || !ItemStack.matches(lastCarried, menu.getCarried()); + boolean wide = menu.getCarried().getCount() > Byte.MAX_VALUE; + for (int index = 0; index < menu.slots.size(); index++) { + ItemStack stack = menu.slots.get(index).getItem(); + wide |= stack.getCount() > Byte.MAX_VALUE; + if (!itemsChanged && !ItemStack.matches(lastSlots.get(index), stack)) itemsChanged = true; + } + boolean dataChanged = !data.equals(lastData); + if (!dataChanged && !itemsChanged && !wide) return; + + List slots = itemsChanged || wide + ? menu.slots.stream().map(slot -> slot.getItem().copy()).toList() : List.of(); + ItemStack carried = menu.getCarried().copy(); + // A vanilla click acknowledgement can overwrite the wide counts after our last packet. + PacketDistributor.sendToPlayer(player, new FurnaceMenuSyncPacket(menu.containerId, + menu.getStateId(), slots, carried, dataChanged ? List.copyOf(data) : List.of())); + if (!slots.isEmpty()) lastSlots = slots; + lastCarried = carried; + lastData = List.copyOf(data); + } + + public boolean applyData(List values) { + if (values.isEmpty()) return true; + int count = dataGroups.stream().mapToInt(ContainerData::getCount).sum(); + if (values.size() != count) return false; + int offset = 0; + for (ContainerData group : dataGroups) { + for (int index = 0; index < group.getCount(); index++) group.set(index, values.get(offset++)); + } + return true; + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/menus/MultiblockAlloyFurnaceMenu.java b/src/main/java/com/sorrowmist/useless/content/menus/MultiblockAlloyFurnaceMenu.java index 18861ccb..6f5354a7 100644 --- a/src/main/java/com/sorrowmist/useless/content/menus/MultiblockAlloyFurnaceMenu.java +++ b/src/main/java/com/sorrowmist/useless/content/menus/MultiblockAlloyFurnaceMenu.java @@ -19,6 +19,20 @@ public final class MultiblockAlloyFurnaceMenu extends AbstractContainerMenu { private final BlockPos blockPos; private final ContainerData data; + private final Player player; + private final FurnaceMenuSynchronizer furnaceSync = new FurnaceMenuSynchronizer(); + + public FurnaceMenuSynchronizer getFurnaceSynchronizer() { + return furnaceSync; + } + + @Override + public void broadcastChanges() { + super.broadcastChanges(); + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { + furnaceSync.broadcast(this, serverPlayer); + } + } @Nullable private final MultiblockAlloyFurnaceCoreBlockEntity core; private final TaskProgressSnapshot taskProgress = new TaskProgressSnapshot(); @@ -30,6 +44,7 @@ public MultiblockAlloyFurnaceMenu(int containerId, Inventory inventory, Friendly public MultiblockAlloyFurnaceMenu(int containerId, Inventory inventory, BlockPos pos) { super(ModMenuType.MULTIBLOCK_ALLOY_FURNACE_MENU.get(), containerId); blockPos = pos.immutable(); + player = inventory.player; core = inventory.player.level().getBlockEntity(pos) instanceof MultiblockAlloyFurnaceCoreBlockEntity value ? value : null; // The server's live ContainerData is read-only (its set method is a @@ -40,6 +55,7 @@ public MultiblockAlloyFurnaceMenu(int containerId, Inventory inventory, BlockPos data = createMenuData(clientSide, !clientSide && core != null ? core.getMenuData() : null); addDataSlots(data); + furnaceSync.addData(data); for (int row = 0; row < 3; row++) { for (int column = 0; column < 9; column++) { addSlot(new Slot(inventory, column + row * 9 + 9, 8 + column * 18, 140 + row * 18)); diff --git a/src/main/java/com/sorrowmist/useless/content/menus/PagedRecoverableMenu.java b/src/main/java/com/sorrowmist/useless/content/menus/PagedRecoverableMenu.java index 6f978669..cd8a28e8 100644 --- a/src/main/java/com/sorrowmist/useless/content/menus/PagedRecoverableMenu.java +++ b/src/main/java/com/sorrowmist/useless/content/menus/PagedRecoverableMenu.java @@ -29,6 +29,16 @@ public class PagedRecoverableMenu extends AbstractContainerMenu { private final RecoverableItemStackHandler inventory; private final BlockPos blockPos; private final Player player; + private final FurnaceMenuSynchronizer furnaceSync = new FurnaceMenuSynchronizer(); + + public FurnaceMenuSynchronizer getFurnaceSynchronizer() { + return furnaceSync; + } + + protected void addSyncedDataSlots(ContainerData data) { + addDataSlots(data); + furnaceSync.addData(data); + } private final boolean clientSide; @Nullable private final PagedMenuPageMemory pageMemory; @@ -119,7 +129,7 @@ protected PagedRecoverableMenu(MenuType type, int containerId, Inventory play for (int column = 0; column < 9; column++) { addSlot(new Slot(playerInventory, column, hotbarX + column * 18, hotbarY)); } - addDataSlots(pageData); + addSyncedDataSlots(pageData); } public BlockPos getBlockPos() { @@ -220,6 +230,9 @@ public void broadcastChanges() { } } super.broadcastChanges(); + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { + furnaceSync.broadcast(this, serverPlayer); + } } private void rememberPage() { diff --git a/src/main/java/com/sorrowmist/useless/content/menus/PassiveCraftingHatchMenu.java b/src/main/java/com/sorrowmist/useless/content/menus/PassiveCraftingHatchMenu.java index 67c7fd8c..3e76413d 100644 --- a/src/main/java/com/sorrowmist/useless/content/menus/PassiveCraftingHatchMenu.java +++ b/src/main/java/com/sorrowmist/useless/content/menus/PassiveCraftingHatchMenu.java @@ -34,7 +34,7 @@ public PassiveCraftingHatchMenu(int containerId, Inventory inventory, BlockPos p ? new SimpleContainerData(PassiveCraftingHatchBlockEntity.MENU_DATA_COUNT) : hatch.getMenuData(); data = createMenuData(inventory.player.level().isClientSide, liveData); - addDataSlots(data); + addSyncedDataSlots(data); for (int slot = 0; slot < slotStatuses.length; slot++) { slotStatuses[slot] = emptyStatus(slot); } diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/AlloyFurnaceRecipeFingerprint.java b/src/main/java/com/sorrowmist/useless/content/recipe/AlloyFurnaceRecipeFingerprint.java index 45848066..075a2319 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/AlloyFurnaceRecipeFingerprint.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/AlloyFurnaceRecipeFingerprint.java @@ -140,7 +140,7 @@ private static String safeCountedIngredient( boolean preserveTags) { if (input == null) return "null"; return safeIngredient(input.ingredient(), registries, normalizeIngredientSemantics, preserveTags) - + "#" + input.count(); + + "#" + input.count() + (input.consumed() ? "" : "#retained"); } private static String safeFluidIngredient( diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/CountedIngredient.java b/src/main/java/com/sorrowmist/useless/content/recipe/CountedIngredient.java index cc4f5243..92851861 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/CountedIngredient.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/CountedIngredient.java @@ -10,8 +10,12 @@ public record CountedIngredient( Ingredient ingredient, - long count + long count, + boolean consumed ) { + public CountedIngredient(Ingredient ingredient, long count) { + this(ingredient, count, true); + } static final StreamCodec INGREDIENT_STREAM_CODEC = com.sorrowmist.useless.compat.forge1201.recipe.RecipeCodecs.INGREDIENT_STREAM; @@ -19,10 +23,12 @@ public record CountedIngredient( StreamCodec.composite( INGREDIENT_STREAM_CODEC, CountedIngredient::ingredient, ByteBufCodecs.VAR_LONG, CountedIngredient::count, + ByteBufCodecs.BOOL, CountedIngredient::consumed, CountedIngredient::new ); static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( com.sorrowmist.useless.compat.forge1201.recipe.RecipeCodecs.INGREDIENT.fieldOf("ingredient").forGetter(CountedIngredient::ingredient), - Codec.LONG.optionalFieldOf("count", 1L).forGetter(CountedIngredient::count) + Codec.LONG.optionalFieldOf("count", 1L).forGetter(CountedIngredient::count), + Codec.BOOL.optionalFieldOf("consumed", true).forGetter(CountedIngredient::consumed) ).apply(instance, CountedIngredient::new)); } diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/ItemIngredientAllocator.java b/src/main/java/com/sorrowmist/useless/content/recipe/ItemIngredientAllocator.java index 6de52c2e..bc6bdcfc 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/ItemIngredientAllocator.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/ItemIngredientAllocator.java @@ -31,11 +31,17 @@ public static boolean matches(List requirements, List requirements, List inputs, List keyInputs, long operations) { + return allocate(requirements, inputs, keyInputs, operations) != null; + } + + @Nullable + public static Allocation allocate(List requirements, List inputs, + List keyInputs, long operations) { List safeInputs = inputs == null ? List.of() : inputs; List safeKeyInputs = keyInputs == null ? List.of() : keyInputs; List demands = createDemands(requirements, operations); - if (demands == null) return false; - if (demands.isEmpty()) return true; + if (demands == null) return null; + if (demands.isEmpty()) return new Allocation(new long[safeInputs.size() + safeKeyInputs.size()]); List supplies = new ArrayList<>(); for (int i = 0; i < safeInputs.size(); i++) { @@ -53,7 +59,7 @@ public static boolean matches(List requirements, List requirements, List 0L ? Integer.MAX_VALUE : 0; return operations >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) operations; } long demandPerOperation = 0L; if (requirements != null) { for (CountedIngredient requirement : requirements) { - if (requirement != null && requirement.count() > 0) { + if (requirement != null && requirement.count() > 0 && requirement.consumed()) { demandPerOperation = saturatingAdd(demandPerOperation, requirement.count()); } } } - if (demandPerOperation == 0) return Integer.MAX_VALUE; + if (demandPerOperation == 0) return matches(requirements, inputs, 1L) ? Integer.MAX_VALUE : 0; long totalAvailable = 0L; if (inputs != null) { @@ -181,10 +188,11 @@ private static List createDemands(List requirements, for (CountedIngredient requirement : requirements) { if (requirement == null || requirement.count() <= 0) continue; long amount; - try { amount = Math.multiplyExact(requirement.count(),multiplier); } + try { amount = Math.multiplyExact(requirement.count(), + requirement.consumed() ? multiplier : multiplier > 0L ? 1L : 0L); } catch(ArithmeticException overflow) { return null; } if (amount > 0) { - demands.add(new Demand(requirement.ingredient(), amount)); + demands.add(new Demand(requirement.ingredient(), amount, requirement.consumed())); } } return demands; @@ -225,16 +233,19 @@ private static Allocation solve(List supplies, List demands, int int demandStart = supplyStart + supplies.size(); int sink = demandStart + demands.size(); FlowNetwork network = new FlowNetwork(sink + 1); - List supplyEdges = new ArrayList<>(supplies.size()); + List consumptionEdges = new ArrayList<>(); for (int i = 0; i < supplies.size(); i++) { - supplyEdges.add(network.addEdge(source, supplyStart + i, supplies.get(i).amount())); + network.addEdge(source, supplyStart + i, supplies.get(i).amount()); } for (int i = 0; i < supplies.size(); i++) { Supply supply = supplies.get(i); for (int j = 0; j < demands.size(); j++) { if (supply.matches().test(demands.get(j).ingredient())) { - network.addEdge(supplyStart + i, demandStart + j, totalDemand); + FlowEdge edge = network.addEdge(supplyStart + i, demandStart + j, totalDemand); + if (demands.get(j).consumed()) { + consumptionEdges.add(new ConsumptionEdge(supplies.get(i).originalIndex(), edge)); + } } } } @@ -245,9 +256,9 @@ private static Allocation solve(List supplies, List demands, int if (network.maxFlow(source, sink, totalDemand) != totalDemand) return null; long[] consumed = new long[resultSize]; - for (int i = 0; i < supplies.size(); i++) { - Supply supply = supplies.get(i); - consumed[supply.originalIndex()] = supply.amount() - supplyEdges.get(i).capacity; + for (ConsumptionEdge edge : consumptionEdges) { + consumed[edge.inputIndex()] = saturatingAdd(consumed[edge.inputIndex()], + totalDemand - edge.edge().capacity); } return new Allocation(consumed); } @@ -260,7 +271,7 @@ private static Allocation solveSingleDemand(List supplies, Demand demand if (remaining <= 0L || !supply.matches().test(demand.ingredient())) continue; long amount = Math.min(remaining, supply.amount()); int originalIndex = supply.originalIndex(); - consumed[originalIndex] = saturatingAdd(consumed[originalIndex], amount); + if (demand.consumed()) consumed[originalIndex] = saturatingAdd(consumed[originalIndex], amount); remaining -= amount; } return remaining > 0L ? null : new Allocation(consumed); @@ -296,7 +307,7 @@ private static Allocation solveExact(List supplies, List demands remaining[i] -= amount; needed -= amount; int originalIndex = supply.originalIndex(); - consumed[originalIndex] = saturatingAdd(consumed[originalIndex], amount); + if (demand.consumed()) consumed[originalIndex] = saturatingAdd(consumed[originalIndex], amount); } if (needed > 0L) return null; } @@ -338,7 +349,13 @@ public long consumedFromInput(int inputIndex) { private record Supply(int originalIndex, long amount, Predicate matches, boolean exact) { } - private record Demand(Ingredient ingredient, long amount) { + private record ConsumptionEdge(int inputIndex, FlowEdge edge) { + } + + private record Demand(Ingredient ingredient, long amount, boolean consumed) { + private Demand(Ingredient ingredient, long amount) { + this(ingredient, amount, true); + } } private static final class FlowNetwork { diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/RecipeSourceIds.java b/src/main/java/com/sorrowmist/useless/content/recipe/RecipeSourceIds.java index 54de209e..eef2d85e 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/RecipeSourceIds.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/RecipeSourceIds.java @@ -2,6 +2,10 @@ /** Stable identifiers used to partition the shared alloy-furnace recipe directory. */ public final class RecipeSourceIds { + public static final String THERMAL = "thermal"; + public static final String BLOOD_MAGIC = "bloodmagic"; + public static final String EMBERS = "embers"; + public static final String LEGACY_METALLURGIC = "mekanism_legacy_items"; public static final String CORE = "core"; public static final String UNKNOWN = "compatibility"; public static final String MINECRAFT = "minecraft"; diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/RetainedRecipeInputs.java b/src/main/java/com/sorrowmist/useless/content/recipe/RetainedRecipeInputs.java new file mode 100644 index 00000000..ff340bce --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/RetainedRecipeInputs.java @@ -0,0 +1,41 @@ +package com.sorrowmist.useless.content.recipe; + +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.GenericStack; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** Returns the actual retained item keys admitted by AE, including their original NBT. */ +public final class RetainedRecipeInputs { + private RetainedRecipeInputs() { + } + + @Nullable + public static List remainders(AdvancedAlloyFurnaceRecipe recipe, List items, + List keys, long operations) { + if (recipe == null || recipe.inputs().stream().allMatch(CountedIngredient::consumed)) return List.of(); + var allocation = ItemIngredientAllocator.allocate(recipe.inputs(), items, keys, operations); + if (allocation == null) return null; + List result = new ArrayList<>(); + for (int index = 0; index < items.size(); index++) { + ItemStack stack = items.get(index); + long left = stack.getCount() - allocation.consumedFromInput(index); + if (left > 0L && isRetained(recipe, stack)) result.add(new GenericStack(AEItemKey.of(stack), left)); + } + for (int index = 0; index < keys.size(); index++) { + GenericStack stack = keys.get(index); + if (stack == null || !(stack.what() instanceof AEItemKey key)) continue; + long left = stack.amount() - allocation.consumedFromInput(items.size() + index); + if (left > 0L && isRetained(recipe, key.toStack(1))) result.add(new GenericStack(key, left)); + } + return List.copyOf(result); + } + + private static boolean isRetained(AdvancedAlloyFurnaceRecipe recipe, ItemStack stack) { + return !stack.isEmpty() && recipe.inputs().stream() + .anyMatch(input -> !input.consumed() && input.ingredient().test(stack)); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapter.java new file mode 100644 index 00000000..1a68d4d1 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapter.java @@ -0,0 +1,154 @@ +package com.sorrowmist.useless.content.recipe.adapters; + +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.fluid.FluidIngredient; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import com.sorrowmist.useless.content.recipe.FluidIngredientAllocator; +import com.sorrowmist.useless.api.recipe.IRecipeAdapter; +import com.sorrowmist.useless.content.recipe.AdapterUtils; +import com.sorrowmist.useless.content.recipe.ItemIngredientAllocator; +import com.sorrowmist.useless.content.recipe.LongSizedFluidIngredient; +import com.sorrowmist.useless.content.recipe.RecipeConversionUtils; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.level.Level; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** Shared conversion and matching for the retained Forge batch-processing integrations. */ +public abstract class BatchRecipeAdapter> implements IRecipeAdapter { + private final Class recipeClass; + private final String sourceId; + private final ResourceLocation moldId; + private final String prefix; + protected final AlloyFurnaceMode mode; + + protected BatchRecipeAdapter(Class recipeClass, String sourceId, String mold, + String prefix, AlloyFurnaceMode mode) { + this.recipeClass = recipeClass; + this.sourceId = sourceId; + this.moldId = new ResourceLocation(sourceId, mold); + this.prefix = prefix; + this.mode = mode; + } + + @Override public Class getRecipeClass() { return recipeClass; } + @Override public String sourceId() { return sourceId; } + @Override public ItemStack getMoldItem() { + var item = ForgeRegistries.ITEMS.getValue(moldId); + return item == null ? ItemStack.EMPTY : new ItemStack(item); + } + + @Override + public boolean matchesMold(ItemStack mold) { + ItemStack required = getMoldItem(); + return !required.isEmpty() && mold != null && !mold.isEmpty() && ItemStack.isSameItem(required, mold); + } + + protected AdvancedAlloyFurnaceRecipe recipe(ResourceLocation id, List inputs, + List fluids, List outputs, + List outputFluids, long energy) { + Ingredient catalyst = Ingredient.of(TagKey.create(Registries.ITEM, UselessMod.id("useless_ingots"))); + ItemStack mold = getMoldItem(); + if (mold.isEmpty()) throw new IllegalStateException("Missing batch recipe mold: " + moldId); + return new AdvancedAlloyFurnaceRecipe(UselessMod.id(prefix + id.getNamespace() + "_" + id.getPath()), + inputs, fluids, outputs, outputFluids, Math.max(0L, energy), 40, catalyst, 1, + Ingredient.of(mold), mode); + } + + @Override + public List> findMatchingRecipes(Level level, Map items, + Map fluids, Map keys, + ItemStack mold, List actualInputs) { + if (level == null || !matchesMold(mold)) return List.of(); + List> result = new ArrayList<>(); + var keyInputs = keys.entrySet().stream().map(entry -> new GenericStack(entry.getKey(), entry.getValue())).toList(); + for (Recipe source : level.getRecipeManager().getRecipes()) { + if (!recipeClass.isInstance(source)) continue; + var holder = RecipeHolder.of(recipeClass.cast(source)); + for (AdvancedAlloyFurnaceRecipe converted : RecipeConversionUtils.convertAll(this, holder, level, actualInputs)) { + if (ItemIngredientAllocator.matches(converted.inputs(), actualInputs, keyInputs, 1L) + && FluidIngredientAllocator.matchesLong(converted.inputFluids(), + AdapterUtils.fluidSupplies(fluids), keyInputs, 1L)) { + result.add(holder); + break; + } + } + } + return List.copyOf(result); + } + + public static List items(List inputs, long batch) { + List result = new ArrayList<>(); + for (Ingredient ingredient : inputs) { + if (ingredient == Ingredient.EMPTY) continue; + if (ingredient.isEmpty()) throw new IllegalArgumentException("Unresolved required batch ingredient"); + if (batch <= 0L) throw new IllegalArgumentException("Nonpositive batch ingredient count"); + result.add(new CountedIngredient(ingredient, batch)); + } + return List.copyOf(result); + } + + /** Expands only differing amounts; equivalent fluid/tag predicates remain live unions. */ + public static List> fluids(List inputs, long batch) { + List> combinations = new ArrayList<>(); + combinations.add(List.of()); + for (JsonElement input : inputs) { + Map> groups = new LinkedHashMap<>(); + collectFluids(input, batch, groups, 0); + if (groups.isEmpty()) throw new IllegalArgumentException("Missing required batch fluid"); + if ((long) combinations.size() * groups.size() > 4096L) { + throw new IllegalArgumentException("Too many batch fluid alternatives"); + } + List> next = new ArrayList<>(); + for (var previous : combinations) { + for (var group : groups.entrySet()) { + var choice = new ArrayList<>(previous); + choice.add(new LongSizedFluidIngredient(FluidIngredient.any(group.getValue()), group.getKey())); + next.add(List.copyOf(choice)); + } + } + combinations = next; + } + return List.copyOf(combinations); + } + + private static void collectFluids(JsonElement input, long batch, + Map> groups, int depth) { + if (depth > 64) throw new IllegalArgumentException("Batch fluid nesting exceeds 64"); + if (input.isJsonArray()) { + for (JsonElement child : input.getAsJsonArray()) collectFluids(child, batch, groups, depth + 1); + return; + } + JsonObject json = input.getAsJsonObject().deepCopy(); + long amount = json.has("amount") ? json.remove("amount").getAsLong() : 1L; + if (amount <= 0L || batch <= 0L) throw new IllegalArgumentException("Nonpositive batch fluid amount"); + if (json.has("fluid_tag")) json.add("tag", json.remove("fluid_tag")); + groups.computeIfAbsent(Math.multiplyExact(amount, batch), ignored -> new ArrayList<>()) + .add(FluidIngredient.fromJson(json)); + } + + public static ItemStack scale(ItemStack stack, long batch) { + return stack.copyWithCount(Math.toIntExact(Math.multiplyExact(stack.getCount(), batch))); + } + + public static FluidStack scale(FluidStack stack, long batch) { + return new FluidStack(stack, Math.toIntExact(Math.multiplyExact(stack.getAmount(), batch))); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java index 5fc41e9d..040e6e51 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java @@ -96,6 +96,12 @@ public final class RecipeAdapterCompatRegistry { private static final List ENTRIES = List.of( new CompatEntry(null, RecipeAdapterCompatRegistry::registerMinecraft), + new CompatEntry(RecipeSourceIds.THERMAL, () -> invokeOptionalLoader( + "com.sorrowmist.useless.compat.thermal.ThermalRecipeCompatLoader")), + new CompatEntry(RecipeSourceIds.BLOOD_MAGIC, () -> invokeOptionalLoader( + "com.sorrowmist.useless.compat.bloodmagic.BloodMagicRecipeCompatLoader")), + new CompatEntry(RecipeSourceIds.EMBERS, () -> invokeOptionalLoader( + "com.sorrowmist.useless.compat.embers.EmbersRecipeCompatLoader")), new CompatEntry(FARMERS_DELIGHT, RecipeAdapterCompatRegistry::registerFarmersDelight), new CompatEntry(EXTRA_DELIGHT, RecipeAdapterCompatRegistry::registerExtraDelight), new CompatEntry(CRABBERS_DELIGHT, RecipeAdapterCompatRegistry::registerCrabbersDelight), @@ -224,6 +230,7 @@ private static void registerMinecraft() { register(new SmeltingRecipeAdapter(RecipeType.SMELTING, Items.FURNACE)); register(new SmeltingRecipeAdapter(RecipeType.BLASTING, Items.BLAST_FURNACE)); register(new SmeltingRecipeAdapter(RecipeType.SMOKING, Items.SMOKER)); + register(new SmeltingRecipeAdapter(RecipeType.CAMPFIRE_COOKING, Items.CAMPFIRE)); } if (ConfigManager.isBrewingRecipeConversionEnabled()) { register(new BrewingRecipeAdapter()); @@ -425,7 +432,6 @@ private static void registerAvaritia() { AVARITIA, RE_AVARITIA_REGISTRY_CLASS); return; } - registerByName("com.sorrowmist.useless.content.recipe.adapters.avaritia.ReAvaritiaSculkCraftingRecipeAdapter"); registerByName("com.sorrowmist.useless.content.recipe.adapters.avaritia.ReAvaritiaTableRecipeAdapter"); registerByName("com.sorrowmist.useless.content.recipe.adapters.avaritia.ReAvaritiaExtremeSmithingRecipeAdapter"); registerByName("com.sorrowmist.useless.content.recipe.adapters.avaritia.ReAvaritiaCompressorRecipeAdapter"); diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/ae/advancedae/ReactionChamberRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/ae/advancedae/ReactionChamberRecipeAdapter.java index 8f7e58c8..6eca206f 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/ae/advancedae/ReactionChamberRecipeAdapter.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/ae/advancedae/ReactionChamberRecipeAdapter.java @@ -17,7 +17,7 @@ import net.pedroksl.advanced_ae.recipes.ReactionChamberRecipe; import java.util.*; -/** AdvancedAE 1.2 uses its own ingredient classes; its fluid predicate ignores NBT. */ +/** AdvancedAE 1.3 uses AE2AddonLib ingredients; its fluid predicate ignores NBT. */ public class ReactionChamberRecipeAdapter implements IRecipeAdapter { @Override public Class getRecipeClass() { return ReactionChamberRecipe.class; } @Override public ItemStack getMoldItem() { return new ItemStack(AAEBlocks.REACTION_CHAMBER.block()); } diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaExtremeSmithingRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaExtremeSmithingRecipeAdapter.java index 96a5cac6..77a9f385 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaExtremeSmithingRecipeAdapter.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaExtremeSmithingRecipeAdapter.java @@ -10,7 +10,7 @@ import com.sorrowmist.useless.content.recipe.IRecipeAdapter; import com.sorrowmist.useless.content.recipe.RecipeSourceIds; import com.sorrowmist.useless.content.recipe.adapters.extendedcrafting.ExtendedCraftingAdapterUtils; -import committee.nova.mods.avaritia.common.crafting.input.ExtremeSmithingRecipeInput; +import net.minecraft.world.SimpleContainer; import committee.nova.mods.avaritia.common.crafting.recipe.ExtremeSmithingRecipe; import committee.nova.mods.avaritia.init.registry.ModBlocks; import committee.nova.mods.avaritia.init.registry.ModRecipeTypes; @@ -71,7 +71,7 @@ public List convertAll( } if (actualInputs != null && !actualInputs.isEmpty()) { - ExtremeSmithingRecipeInput input = resolveInput(holder.value(), actualInputs); + SimpleContainer input = resolveInput(holder.value(), actualInputs); if (input == null || level == null || !holder.value().matches(input, level)) { return List.of(); } @@ -134,7 +134,7 @@ private static Converted convertData(@Nullable ExtremeSmithingRecipe source) { List slots; try { - slots = source.getIngredients(); + slots = List.of(source.template, source.base, source.additions, source.additions, source.additions); } catch (RuntimeException exception) { return null; } @@ -164,7 +164,7 @@ private static Converted convertData(@Nullable ExtremeSmithingRecipe source) { } @Nullable - private static ExtremeSmithingRecipeInput resolveInput( + private static SimpleContainer resolveInput( ExtremeSmithingRecipe source, List actualInputs) { List available = new ArrayList<>(); for (ItemStack stack : actualInputs) { @@ -187,8 +187,7 @@ private static ExtremeSmithingRecipeInput resolveInput( if (!assignSlot(0, slots, available, selected)) { return null; } - return new ExtremeSmithingRecipeInput( - selected[0], selected[1], selected[2], selected[3], selected[4]); + return new SimpleContainer(selected); } private static boolean assignSlot( @@ -202,8 +201,8 @@ private static boolean assignSlot( if (!ingredient.test(candidate)) { continue; } - candidate.shrink(1); selected[slot] = candidate.copyWithCount(1); + candidate.shrink(1); if (assignSlot(slot + 1, ingredients, available, selected)) { return true; } diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaTableRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaTableRecipeAdapter.java index 8e59d725..770f5b1b 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaTableRecipeAdapter.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/avaritia/ReAvaritiaTableRecipeAdapter.java @@ -10,9 +10,9 @@ import com.sorrowmist.useless.content.recipe.IRecipeAdapter; import com.sorrowmist.useless.content.recipe.RecipeSourceIds; import com.sorrowmist.useless.content.recipe.adapters.extendedcrafting.ExtendedCraftingAdapterUtils; -import committee.nova.mods.avaritia.api.common.crafting.ITierCraftingRecipe; -import committee.nova.mods.avaritia.api.common.crafting.TierInput; -import committee.nova.mods.avaritia.common.ingredient.StackIngredient; +import committee.nova.mods.avaritia.common.crafting.recipe.ITierCraftingRecipe; +import net.minecraft.world.SimpleContainer; +import net.minecraftforge.common.crafting.StrictNBTIngredient; import committee.nova.mods.avaritia.init.registry.ModBlocks; import committee.nova.mods.avaritia.init.registry.ModRecipeTypes; import net.minecraft.world.item.ItemStack; @@ -20,7 +20,6 @@ import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.level.Level; -import com.sorrowmist.useless.compat.forge1201.recipe.DataComponentIngredient; import net.minecraftforge.fluids.FluidStack; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -143,10 +142,7 @@ private static Converted convertData(@Nullable ITierCraftingRecipe source) { return null; } - // Ingredient.equals() does not include item components. Re-Avaritia represents - // singularities as the same item with a different SINGULARITY_ID component, so a normal - // equality-based map would collapse all of them into one requirement. The identity map is - // only the storage layer; equal exact stacks are still merged explicitly below. + // Different singularity NBT must remain separate requirements. Map requirements = new IdentityHashMap<>(); if (!mergeTableIngredients(requirements, ingredients)) { return null; @@ -166,11 +162,8 @@ private static Converted convertData(@Nullable ITierCraftingRecipe source) { remainders = ExtendedCraftingAdapterUtils.deterministicRemainders( ingredients, Set.of(), - stacks -> { - int[] dimensions = ExtendedCraftingAdapterUtils.gridDimensions(source, stacks.size()); - return TierInput.of(dimensions[0], dimensions[1], stacks, tier); - }, - source::getRemainingItems + stacks -> new SimpleContainer(stacks.toArray(ItemStack[]::new)), + (net.minecraft.world.Container input) -> source.getRemainingItems(input) ); } catch (RuntimeException exception) { return null; @@ -260,19 +253,16 @@ private static Optional exactStack(@Nullable Ingredient ingredient) { return Optional.empty(); } - if (ingredient.getCustomIngredient() instanceof StackIngredient) { + if (ingredient instanceof StrictNBTIngredient) { return singleCandidate(ingredient); } - if (ingredient.getCustomIngredient() instanceof DataComponentIngredient dataComponent) { - return dataComponent.isStrict() ? singleCandidate(ingredient) : Optional.empty(); - } - if (ingredient.isCustom()) { + if (!ingredient.isVanilla()) { return Optional.empty(); } try { - Ingredient.Value[] values = ingredient.getValues(); - if (values.length != 1 || !(values[0] instanceof Ingredient.ItemValue)) { + var json = ingredient.toJson(); + if (!json.isJsonObject() || !json.getAsJsonObject().has("item")) { return Optional.empty(); } } catch (RuntimeException exception) { diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/bloodmagic/BloodMagicRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/bloodmagic/BloodMagicRecipeAdapter.java new file mode 100644 index 00000000..1e2a2ec2 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/bloodmagic/BloodMagicRecipeAdapter.java @@ -0,0 +1,46 @@ +package com.sorrowmist.useless.content.recipe.adapters.bloodmagic; + +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.RecipeSourceIds; +import com.sorrowmist.useless.content.recipe.adapters.BatchRecipeAdapter; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.Level; +import wayoftime.bloodmagic.recipe.BloodMagicRecipe; +import wayoftime.bloodmagic.recipe.RecipeAlchemyTable; +import wayoftime.bloodmagic.recipe.RecipeBloodAltar; +import wayoftime.bloodmagic.recipe.RecipeTartaricForge; + +import java.util.List; + +public final class BloodMagicRecipeAdapter extends BatchRecipeAdapter { + public BloodMagicRecipeAdapter(Class type, String mold, String prefix) { + super(type, RecipeSourceIds.BLOOD_MAGIC, mold, prefix, AlloyFurnaceMode.NORMAL); + } + + @Override + public List convertAll(RecipeHolder holder, Level level) { + if (holder == null) return List.of(); + List inputs; + ItemStack output; + long energy = 2000L; + if (holder.value() instanceof RecipeBloodAltar altar) { + inputs = List.of(altar.getInput()); + output = altar.getOutput(); + energy = Math.multiplyExact(altar.getSyphon(), 2L); + } else if (holder.value() instanceof RecipeTartaricForge forge) { + inputs = forge.getInput(); + output = forge.getOutput(); + } else if (holder.value() instanceof RecipeAlchemyTable table) { + inputs = table.getInput(); + output = table.getOutput(); + } else { + return List.of(); + } + if (output.isEmpty()) return List.of(); + return List.of(recipe(holder.id(), items(inputs, 64L), List.of(), + List.of(scale(output, 64L)), List.of(), energy)); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/embers/EmbersRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/embers/EmbersRecipeAdapter.java new file mode 100644 index 00000000..4e9bd9f5 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/embers/EmbersRecipeAdapter.java @@ -0,0 +1,77 @@ +package com.sorrowmist.useless.content.recipe.adapters.embers; + +import com.rekindled.embers.recipe.IAlchemyRecipe; +import com.rekindled.embers.recipe.IMeltingRecipe; +import com.rekindled.embers.recipe.IMixingRecipe; +import com.rekindled.embers.recipe.IStampingRecipe; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import com.sorrowmist.useless.content.recipe.LongSizedFluidIngredient; +import com.sorrowmist.useless.content.recipe.RecipeSourceIds; +import com.sorrowmist.useless.content.recipe.adapters.BatchRecipeAdapter; +import net.minecraft.world.item.ItemStack; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.level.Level; +import net.minecraftforge.fluids.FluidStack; + +import java.util.ArrayList; +import java.util.List; + +public final class EmbersRecipeAdapter> extends BatchRecipeAdapter { + private final boolean separator; + + public EmbersRecipeAdapter(Class type, String mold, String prefix, boolean separator) { + super(type, RecipeSourceIds.EMBERS, mold, prefix, AlloyFurnaceMode.NORMAL); + this.separator = separator; + } + + @Override + public List convertAll(RecipeHolder holder, Level level) { + if (holder == null) return List.of(); + List inputs = List.of(); + List> fluidInputs = List.of(List.of()); + List outputs = List.of(); + List fluidOutputs = new ArrayList<>(); + // shortcut: keep the local input/output conversion; revisit aspect puzzles and stamps for native machine parity. + if (holder.value() instanceof IAlchemyRecipe alchemy) { + var ingredients = new ArrayList(); + ingredients.add(alchemy.getCenterInput()); + ingredients.addAll(alchemy.getInputs()); + inputs = items(ingredients, 64L); + outputs = List.of(scale(alchemy.getResultItem(), 64L)); + } else if (holder.value() instanceof IMeltingRecipe melting) { + FluidStack output = melting.getDisplayOutput(); + FluidStack bonus = melting.getBonus(); + if (output.isEmpty() || !separator && !bonus.isEmpty()) return List.of(); + inputs = items(List.of(melting.getDisplayInput()), 64L); + fluidOutputs.add(scale(output, 64L)); + if (separator && !bonus.isEmpty()) fluidOutputs.add(scale(bonus, 64L)); + } else if (holder.value() instanceof IStampingRecipe stamping) { + inputs = items(List.of(stamping.getDisplayInput()), 64L); + if (stamping.getDisplayInputFluid() != com.rekindled.embers.recipe.FluidIngredient.EMPTY) { + fluidInputs = fluids(List.of(stamping.getDisplayInputFluid().serialize()), 64L); + } + outputs = List.of(scale(stamping.getResultItem(), 64L)); + } else if (holder.value() instanceof IMixingRecipe mixing) { + fluidInputs = fluids(mixing.getDisplayInputFluids().stream().map(value -> value.serialize()).toList(), 64L); + fluidOutputs.add(scale(mixing.getDisplayOutput(), 64L)); + } else { + return List.of(); + } + if (outputs.stream().allMatch(ItemStack::isEmpty) && fluidOutputs.stream().allMatch(FluidStack::isEmpty)) { + return List.of(); + } + List result = new ArrayList<>(); + for (int index = 0; index < fluidInputs.size(); index++) { + var id = fluidInputs.size() == 1 ? holder.id() + : new ResourceLocation(holder.id().getNamespace(), + holder.id().getPath() + "_fluid_variant_" + index); + result.add(recipe(id, inputs, fluidInputs.get(index), outputs, fluidOutputs, 2000L)); + } + return List.copyOf(result); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeAdapter.java index df8f0ddb..0b299d93 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeAdapter.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeAdapter.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.content.recipe.adapters.exdeorum; import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import appeng.api.stacks.AEKey; import com.sorrowmist.useless.content.recipe.AdapterUtils; import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; import com.sorrowmist.useless.content.recipe.CountedIngredient; @@ -11,12 +12,14 @@ import net.minecraft.world.item.crafting.Ingredient; import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; import net.minecraft.world.level.Level; +import net.minecraftforge.fluids.FluidStack; import org.jetbrains.annotations.Nullable; import thedarkcolour.exdeorum.recipe.hammer.CompressedHammerRecipe; import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; @@ -66,6 +69,25 @@ public boolean matchesMold(@Nullable ItemStack mold) { || ExDeorumRecipeAdapterUtils.isCompressedHammer(mold); } + @Override + public List> findMatchingRecipes( + Level level, Map mergedInputs, Map mergedFluids, + Map mergedKeys, @Nullable ItemStack mold) { + if (level == null || !matchesMold(mold)) return List.of(); + + List> matches = new ArrayList<>(); + // Generated viewer entries also need an explicit machine lookup, like the other synthetic adapters. + for (RecipeHolder holder : getGeneratedRecipes(level)) { + AdvancedAlloyFurnaceRecipe recipe = holder.value().convertedRecipe(); + if (AdapterUtils.matchesMold(recipe.mold(), mold) + && ExDeorumRecipeAdapterUtils.matchesItems(recipe.inputs(), mergedInputs, null) + && ExDeorumRecipeAdapterUtils.matchesFluids(recipe.inputFluids(), mergedFluids)) { + matches.add(holder); + } + } + return List.copyOf(matches); + } + @Override public List> getGeneratedRecipes(Level level) { if (level == null) { diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/MetallurgicItemRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/MetallurgicItemRecipeAdapter.java new file mode 100644 index 00000000..bafea5ce --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/MetallurgicItemRecipeAdapter.java @@ -0,0 +1,62 @@ +package com.sorrowmist.useless.content.recipe.adapters.mekanism; + +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import com.sorrowmist.useless.content.recipe.RecipeSourceIds; +import com.sorrowmist.useless.content.recipe.adapters.BatchRecipeAdapter; +import mekanism.api.recipes.ItemStackToInfuseTypeRecipe; +import mekanism.api.recipes.MetallurgicInfuserRecipe; +import mekanism.common.registries.MekanismBlocks; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; + +import java.util.ArrayList; +import java.util.List; + +/** Optional legacy shortcut; chemical-key processing remains the default. */ +public final class MetallurgicItemRecipeAdapter extends BatchRecipeAdapter { + public MetallurgicItemRecipeAdapter() { + super(MetallurgicInfuserRecipe.class, RecipeSourceIds.LEGACY_METALLURGIC, + "metallurgic_infuser", "mek_metallurgic_", AlloyFurnaceMode.NORMAL); + } + + @Override public ItemStack getMoldItem() { + return new ItemStack(MekanismBlocks.METALLURGIC_INFUSER.getBlock()); + } + + @Override + public List convertAll(RecipeHolder holder, Level level) { + if (holder == null || level == null) return List.of(); + var source = holder.value(); + List result = new ArrayList<>(); + for (var chemical : source.getChemicalInput().getRepresentations()) { + for (var candidate : level.getRecipeManager().getRecipes()) { + if (!(candidate instanceof ItemStackToInfuseTypeRecipe conversion)) continue; + for (var produced : conversion.getOutputDefinition()) { + if (produced.getType() != chemical.getType() || produced.getAmount() <= 0L) continue; + long needed = Math.multiplyExact(chemical.getAmount(), 64L); + long batches = needed / produced.getAmount() + (needed % produced.getAmount() == 0L ? 0L : 1L); + var materials = NativeMekanismIngredients.itemVariants(conversion.getInput().serialize(), batches); + var rawInputs = NativeMekanismIngredients.itemVariants(source.getItemInput().serialize(), 64L); + for (int itemIndex = 0; itemIndex < rawInputs.size(); itemIndex++) { + for (int materialIndex = 0; materialIndex < materials.size(); materialIndex++) { + var inputs = List.of(rawInputs.get(itemIndex), materials.get(materialIndex)); + var conversionId = conversion.getId(); + var chemicalId = chemical.getTypeRegistryName(); + ResourceLocation id = new ResourceLocation(holder.id().getNamespace(), holder.id().getPath() + + "/" + chemicalId.getNamespace() + "/" + chemicalId.getPath() + + "/" + conversionId.getNamespace() + "/" + conversionId.getPath() + + "/" + itemIndex + "_" + materialIndex); + result.add(recipe(id, inputs, List.of(), source.getOutputDefinition().stream() + .map(output -> scale(output, 64L)).toList(), List.of(), 10000L)); + } + } + } + } + } + return List.copyOf(result); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java index 74cc32c3..8c349c65 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/RecyclerRecipeAdapter.java @@ -1,6 +1,6 @@ package com.sorrowmist.useless.content.recipe.adapters.mekanism; -import com.jerry.mekmm.api.recipes.MoreMachineRecipeTypes; +import com.jerry.mekmm.common.recipe.MoreMachineRecipeType; import com.jerry.mekmm.api.recipes.RecyclerRecipe; import com.jerry.mekmm.common.config.MoreMachineConfig; import com.jerry.mekmm.common.registries.MoreMachineBlocks; @@ -29,7 +29,7 @@ public final class RecyclerRecipeAdapter extends MekanismSyntheticRecipeAdapter @Override public @Nullable ItemStack getMoldItem() { - return new ItemStack(MoreMachineBlocks.RECYCLER.get()); + return new ItemStack(MoreMachineBlocks.RECYCLER.getBlock()); } @Override @@ -40,7 +40,7 @@ protected List> createGeneratedRecipes(Lev List outputs = new ArrayList<>(); for (RecipeHolder holder : com.sorrowmist.useless.compat.forge1201.recipe.RecipeManagerAccess.getAllRecipesFor(level.getRecipeManager(), - MoreMachineRecipeTypes.TYPE_RECYCLER.value())) { + MoreMachineRecipeType.RECYCLING.get())) { RecyclerRecipe source = holder.value(); if (source == null || source.isIncomplete() || source.getOutputChance() <= 0) { continue; @@ -79,8 +79,8 @@ protected List> createGeneratedRecipes(Lev } private static long energyCost() { - return MekanismChemicalRecipeSupport.saturatingMultiply( - MoreMachineConfig.usage.recycler.get(), PROCESS_TICKS); + return MekanismChemicalRecipeSupport.energyCost( + PROCESS_TICKS, 1, MoreMachineConfig.usage.recycler.get()); } private static boolean containsOutput(List outputs, ItemStack candidate) { diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/mekanismmoremachine/AmbientGasCollectorRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/mekanismmoremachine/AmbientGasCollectorRecipeAdapter.java index 910ef227..45cd5fd6 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/mekanismmoremachine/AmbientGasCollectorRecipeAdapter.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/mekanism/mekanismmoremachine/AmbientGasCollectorRecipeAdapter.java @@ -3,7 +3,7 @@ import appeng.api.stacks.GenericStack; import com.jerry.mekmm.common.config.MoreMachineConfig; import com.jerry.mekmm.common.registries.MoreMachineBlocks; -import com.jerry.mekmm.common.registries.MoreMachineChemicals; +import com.jerry.mekmm.common.registries.MoreMachineGas; import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; import com.sorrowmist.useless.content.recipe.adapters.mekanism.MekanismChemicalRecipeSupport; import com.sorrowmist.useless.content.recipe.adapters.mekanism.MekanismSyntheticRecipe; @@ -26,7 +26,7 @@ public final class AmbientGasCollectorRecipeAdapter extends MekanismSyntheticRec @Override public @Nullable ItemStack getMoldItem() { - return new ItemStack(MoreMachineBlocks.AMBIENT_GAS_COLLECTOR.get()); + return new ItemStack(MoreMachineBlocks.AMBIENT_GAS_COLLECTOR.getBlock()); } @Override @@ -41,7 +41,7 @@ protected List> createGeneratedRecipes(Lev } GenericStack output = MekanismChemicalRecipeSupport.key( - MoreMachineChemicals.UNSTABLE_DIMENSIONAL_GAS.asStack(gasAmount)); + MoreMachineGas.UNSTABLE_DIMENSIONAL_GAS.getStack(gasAmount)); if (output == null) { return List.of(); } @@ -54,8 +54,8 @@ protected List> createGeneratedRecipes(Lev List.of(), List.of(), List.of(output), - MekanismChemicalRecipeSupport.saturatingMultiply( - MoreMachineConfig.usage.ambientGasCollector.get(), PROCESS_TICKS), + MekanismChemicalRecipeSupport.energyCost( + PROCESS_TICKS, 1, MoreMachineConfig.usage.ambientGasCollector.get()), PROCESS_TICKS, getMoldItem()); return List.of(MekanismChemicalRecipeSupport.syntheticHolder(RECIPE_ID, converted)); diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/NativeThermalIngredients.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/NativeThermalIngredients.java new file mode 100644 index 00000000..730651e5 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/NativeThermalIngredients.java @@ -0,0 +1,49 @@ +package com.sorrowmist.useless.content.recipe.adapters.thermal; + +import cofh.lib.util.crafting.IngredientWithCount; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import net.minecraft.world.item.crafting.Ingredient; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** CoFH's toJson omits the count; unwrapping must also preserve its actual tag/NBT predicate. */ +public final class NativeThermalIngredients { + private static final Field COUNT = field("count"); + private static final Field WRAPPED = field("wrappedIngredient"); + + private NativeThermalIngredients() { + } + + private static Field field(String name) { + try { + Field field = IngredientWithCount.class.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (ReflectiveOperationException exception) { + throw new IllegalStateException("Unsupported CoFH counted ingredient", exception); + } + } + + public static List items(List inputs, long batch) { + List result = new ArrayList<>(); + for (Ingredient ingredient : inputs) { + if (ingredient == Ingredient.EMPTY) continue; + long count = batch; + int depth = 0; + try { + while (ingredient instanceof IngredientWithCount) { + if (++depth > 64) throw new IllegalArgumentException("Nested CoFH ingredient exceeds 64"); + count = Math.multiplyExact(count, COUNT.getInt(ingredient)); + ingredient = (Ingredient) WRAPPED.get(ingredient); + } + } catch (IllegalAccessException exception) { + throw new IllegalStateException("Cannot read CoFH ingredient count", exception); + } + if (count <= 0L || ingredient.isEmpty()) throw new IllegalArgumentException("Invalid required CoFH ingredient"); + result.add(new CountedIngredient(ingredient, count)); + } + return List.copyOf(result); + } +} diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/ThermalRecipeAdapter.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/ThermalRecipeAdapter.java new file mode 100644 index 00000000..bd83f1fa --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/thermal/ThermalRecipeAdapter.java @@ -0,0 +1,65 @@ +package com.sorrowmist.useless.content.recipe.adapters.thermal; + +import cofh.thermal.lib.util.recipes.ThermalRecipe; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import com.sorrowmist.useless.content.recipe.RecipeSourceIds; +import com.sorrowmist.useless.content.recipe.adapters.BatchRecipeAdapter; +import net.minecraft.world.item.ItemStack; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; + +import java.util.ArrayList; +import java.util.List; + +public final class ThermalRecipeAdapter extends BatchRecipeAdapter { + public ThermalRecipeAdapter(Class type, String machine, String prefix, AlloyFurnaceMode mode) { + super(type, RecipeSourceIds.THERMAL, machine, prefix, mode); + } + + @Override + public List convertAll(RecipeHolder holder, Level level) { + if (holder == null) return List.of(); + T source = holder.value(); + long batch = mode == AlloyFurnaceMode.INSOLATOR ? 1L : 64L; + var inputs = NativeThermalIngredients.items(source.getInputItems(), batch); + if (mode == AlloyFurnaceMode.INSOLATOR) { + inputs = inputs.stream().map(input -> new CountedIngredient( + input.ingredient(), input.count(), false)).toList(); + } + if (mode == AlloyFurnaceMode.PRESS && inputs.size() > 1) { + var retained = new ArrayList<>(inputs); + var mold = NativeThermalIngredients.items(List.of(source.getInputItems().get(1)), 1L).get(0); + retained.set(1, new CountedIngredient( + mold.ingredient(), mold.count(), false)); + inputs = List.copyOf(retained); + } + List outputs = new ArrayList<>(); + for (int index = 0; index < source.getOutputItems().size(); index++) { + ItemStack output = source.getOutputItems().get(index); + if (mode == AlloyFurnaceMode.INSOLATOR) { + float chance = index < source.getOutputItemChances().size() + ? source.getOutputItemChances().get(index) : 1.0F; + if (!Float.isFinite(chance) || chance < 0.0F) throw new IllegalArgumentException("Invalid Thermal chance"); + // shortcut: retain the local deterministic yield; revisit if native chance semantics are requested. + output = output.copyWithCount((int) Math.ceil(chance)); + } else { + output = scale(output, batch); + } + if (!output.isEmpty()) outputs.add(output); + } + var fluids = fluids(source.getInputFluids().stream().map(value -> value.toJson()).toList(), batch); + var outputFluids = source.getOutputFluids().stream().map(value -> scale(value, batch)).toList(); + List result = new ArrayList<>(); + for (int index = 0; index < fluids.size(); index++) { + var id = fluids.size() == 1 ? holder.id() + : new ResourceLocation(holder.id().getNamespace(), + holder.id().getPath() + "_fluid_variant_" + index); + result.add(recipe(id, inputs, fluids.get(index), outputs, outputFluids, + Math.multiplyExact(source.getEnergy(), batch))); + } + return List.copyOf(result); + } +} diff --git a/src/main/java/com/sorrowmist/useless/core/config/ConfigManager.java b/src/main/java/com/sorrowmist/useless/core/config/ConfigManager.java index d4dd3d64..7ff29460 100644 --- a/src/main/java/com/sorrowmist/useless/core/config/ConfigManager.java +++ b/src/main/java/com/sorrowmist/useless/core/config/ConfigManager.java @@ -109,6 +109,10 @@ public class ConfigManager { private static final ForgeConfigSpec.IntValue STAFFLINK_IDLE_BACKOFF_CAP; // 万象炉配方转换配置 + private static final ForgeConfigSpec.BooleanValue ENABLE_THERMAL_RECIPE_CONVERSION; + private static final ForgeConfigSpec.BooleanValue ENABLE_BLOOD_MAGIC_RECIPE_CONVERSION; + private static final ForgeConfigSpec.BooleanValue ENABLE_EMBERS_RECIPE_CONVERSION; + private static final ForgeConfigSpec.BooleanValue ENABLE_LEGACY_METALLURGIC_RECIPE_CONVERSION; private static final ForgeConfigSpec.BooleanValue ENABLE_CRAFTING_RECIPE_CONVERSION; private static final ForgeConfigSpec.BooleanValue ENABLE_SMELTING_RECIPE_CONVERSION; private static final ForgeConfigSpec.BooleanValue ENABLE_BREWING_RECIPE_CONVERSION; @@ -705,6 +709,8 @@ public class ConfigManager { "enable_appmek_recipe_conversion", true); ENABLE_MEKANISM_GENERATORS_RECIPE_CONVERSION = defineRecipeConversionOption( "enable_mekanism_generators_recipe_conversion", true); + ENABLE_LEGACY_METALLURGIC_RECIPE_CONVERSION = defineRecipeConversionOption( + "enable_legacy_metallurgic_item_recipe_conversion", false); ENABLE_MEKANISM_RECIPE_CONVERSION = defineRecipeConversionOption( "enable_mekanism_recipe_conversion", true); COMMON_BUILDER.pop(); @@ -761,6 +767,12 @@ public class ConfigManager { "enable_hostilenetworks_recipe_conversion", true); ENABLE_IMMERSIVE_ENGINEERING_RECIPE_CONVERSION = defineRecipeConversionOption( "enable_immersiveengineering_recipe_conversion", true); + ENABLE_THERMAL_RECIPE_CONVERSION = defineRecipeConversionOption( + "enable_thermal_recipe_conversion", true); + ENABLE_BLOOD_MAGIC_RECIPE_CONVERSION = defineRecipeConversionOption( + "enable_blood_magic_recipe_conversion", true); + ENABLE_EMBERS_RECIPE_CONVERSION = defineRecipeConversionOption( + "enable_embers_recipe_conversion", true); ENABLE_INDUSTRIAL_FOREGOING_RECIPE_CONVERSION = defineRecipeConversionOption( "enable_industrial_foregoing_recipe_conversion", true); ENABLE_IRONS_SPELLBOOKS_RECIPE_CONVERSION = defineRecipeConversionOption( @@ -792,6 +804,10 @@ public class ConfigManager { COMMON_BUILDER.pop(); RECIPE_CONVERSION_OPTIONS = Map.ofEntries( + Map.entry("thermal", ENABLE_THERMAL_RECIPE_CONVERSION), + Map.entry("bloodmagic", ENABLE_BLOOD_MAGIC_RECIPE_CONVERSION), + Map.entry("embers", ENABLE_EMBERS_RECIPE_CONVERSION), + Map.entry("mekanism_legacy_items", ENABLE_LEGACY_METALLURGIC_RECIPE_CONVERSION), Map.entry("actuallyadditions", ENABLE_ACTUALLY_ADDITIONS_RECIPE_CONVERSION), Map.entry("advanced_ae", ENABLE_ADVANCED_AE_RECIPE_CONVERSION), Map.entry("ae2", ENABLE_AE2_RECIPE_CONVERSION), @@ -855,6 +871,7 @@ public class ConfigManager { COMMON_BUILDER.pop(); COMMON_BUILDER.pop(); + com.sorrowmist.useless.core.config.ThermalConfig.define(COMMON_BUILDER); COMMON_SPEC = COMMON_BUILDER.build(); CLIENT_SPEC = CLIENT_BUILDER.build(); SERVER_SPEC = SERVER_BUILDER.build(); diff --git a/src/main/java/com/sorrowmist/useless/core/config/ThermalConfig.java b/src/main/java/com/sorrowmist/useless/core/config/ThermalConfig.java new file mode 100644 index 00000000..1254cada --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/core/config/ThermalConfig.java @@ -0,0 +1,39 @@ +package com.sorrowmist.useless.core.config; + +import net.minecraftforge.common.ForgeConfigSpec; + +/** Thermal options share the community build's common configuration. */ +public final class ThermalConfig { + private ThermalConfig() { + } + + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_ITEM_CAPACITY; + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_FLUID_CAPACITY; + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_ENERGY_CAPACITY; + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_ITEM_TRANSFER; + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_FLUID_TRANSFER; + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_ENERGY_TRANSFER; + public static ForgeConfigSpec.BooleanValue PARALLEL_INCREASE_ENERGY_CONSUMPTION; + public static ForgeConfigSpec.BooleanValue BASE_MOD_AFFECT_PARALLEL; + + public static void define(ForgeConfigSpec.Builder builder) { + builder.push("thermal_parallel"); + PARALLEL_INCREASE_ITEM_CAPACITY = builder.define("parallel_increase_item_capacity", true); + PARALLEL_INCREASE_FLUID_CAPACITY = builder.define("parallel_increase_fluid_capacity", true); + PARALLEL_INCREASE_ENERGY_CAPACITY = builder.define("parallel_increase_energy_capacity", false); + PARALLEL_INCREASE_ITEM_TRANSFER = builder.define("parallel_increase_item_transfer", true); + PARALLEL_INCREASE_FLUID_TRANSFER = builder.define("parallel_increase_fluid_transfer", true); + PARALLEL_INCREASE_ENERGY_TRANSFER = builder.define("parallel_increase_energy_transfer", true); + PARALLEL_INCREASE_ENERGY_CONSUMPTION = builder.define("parallel_increase_energy_consumption", false); + BASE_MOD_AFFECT_PARALLEL = builder.define("base_mod_affect_parallel", false); + builder.pop(); + } + + public static boolean enabled(ForgeConfigSpec.BooleanValue option) { + try { + return option.get(); + } catch (IllegalStateException ignored) { + return option.getDefault(); + } + } +} diff --git a/src/main/java/com/sorrowmist/useless/init/ClientSetup.java b/src/main/java/com/sorrowmist/useless/init/ClientSetup.java index cdb2edbe..23642af5 100644 --- a/src/main/java/com/sorrowmist/useless/init/ClientSetup.java +++ b/src/main/java/com/sorrowmist/useless/init/ClientSetup.java @@ -143,6 +143,7 @@ public static void onRecipesUpdated(RecipesUpdatedEvent event) { @SubscribeEvent public static void onClientTick(ClientTickEvent event) { if (event.phase != net.minecraftforge.event.TickEvent.Phase.END) return; + com.sorrowmist.useless.client.gui.FurnaceScreenScaler.update(); Minecraft minecraft = Minecraft.getInstance(); Level level = minecraft.level; if (level != observedClientLevel) { diff --git a/src/main/java/com/sorrowmist/useless/init/ModNetwork.java b/src/main/java/com/sorrowmist/useless/init/ModNetwork.java index cb476b2e..7716a9bd 100644 --- a/src/main/java/com/sorrowmist/useless/init/ModNetwork.java +++ b/src/main/java/com/sorrowmist/useless/init/ModNetwork.java @@ -2,6 +2,7 @@ import com.sorrowmist.useless.UselessMod; import com.sorrowmist.useless.network.AECancelPacket; +import com.sorrowmist.useless.network.FurnaceMenuSyncPacket; import com.sorrowmist.useless.network.AEReturnOutputTogglePacket; import com.sorrowmist.useless.network.AETaskProgressPacket; import com.sorrowmist.useless.network.AETaskProgressRequestPacket; @@ -58,7 +59,9 @@ public class ModNetwork { public static void registerPayloadHandlers(final RegisterPayloadHandlersEvent event) { - var registrar = event.registrar(UselessMod.MODID).versioned("17"); + var registrar = event.registrar(UselessMod.MODID).versioned("17-merged-1"); + registrar.playToClient(FurnaceMenuSyncPacket.TYPE, FurnaceMenuSyncPacket.STREAM_CODEC, + FurnaceMenuSyncPacket::handle); registrar.playToServer(BeefToolLayoutRequestPacket.TYPE, BeefToolLayoutRequestPacket.STREAM_CODEC, BeefToolLayoutRequestPacket::handle); diff --git a/src/main/java/com/sorrowmist/useless/mixin/exmachinis/CompactingCategoryMixin.java b/src/main/java/com/sorrowmist/useless/mixin/exmachinis/CompactingCategoryMixin.java new file mode 100644 index 00000000..dede0be7 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/mixin/exmachinis/CompactingCategoryMixin.java @@ -0,0 +1,21 @@ +package com.sorrowmist.useless.mixin.exmachinis; + +import net.minecraft.resources.ResourceLocation; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +/** Ex Machinis 3.0.3 still reads a JEI internal field removed by current JEI. */ +@Pseudo +@Mixin(targets = "net.permutated.exmachinis.compat.jei.category.CompactingCategory", remap = false) +public abstract class CompactingCategoryMixin { + // A fixed Ex Machinis version may no longer read this field. + @Redirect(method = "", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, + target = "Lmezz/jei/common/Constants;RECIPE_GUI_VANILLA:Lnet/minecraft/resources/ResourceLocation;"), + require = 0) + private ResourceLocation uselessMod$recipeBackground() { + return new ResourceLocation("useless_mod", "textures/gui/exmachinis_recipe.png"); + } +} diff --git a/src/main/java/com/sorrowmist/useless/mixin/thermal/AugmentableBlockEntityMixin.java b/src/main/java/com/sorrowmist/useless/mixin/thermal/AugmentableBlockEntityMixin.java new file mode 100644 index 00000000..918d2859 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/mixin/thermal/AugmentableBlockEntityMixin.java @@ -0,0 +1,122 @@ +package com.sorrowmist.useless.mixin.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import cofh.core.util.helpers.AugmentableHelper; +import cofh.lib.common.energy.EnergyStorageCoFH; +import cofh.lib.common.fluid.FluidStorageCoFH; +import cofh.lib.common.fluid.ManagedTankInv; +import cofh.lib.common.inventory.ManagedItemInv; +import cofh.thermal.lib.common.block.entity.AugmentableBlockEntity; +import com.sorrowmist.useless.compat.thermal.ThermalParallelAccess; +import com.sorrowmist.useless.compat.thermal.ThermalItemStorageAccess; +import com.sorrowmist.useless.core.config.ThermalConfig; +import com.sorrowmist.useless.compat.thermal.ThermalParallelMath; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.enchantment.Enchantment; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.Map; + +@Mixin(value = AugmentableBlockEntity.class, remap = false) +public abstract class AugmentableBlockEntityMixin implements ThermalParallelAccess { + @Shadow protected CompoundTag augmentNBT; + @Shadow protected ManagedItemInv inventory; + @Shadow protected ManagedTankInv tankInv; + @Shadow protected EnergyStorageCoFH energyStorage; + + @Unique private int useless_mod$parallel; + + public int useless_mod$getParallel() { + return this.useless_mod$parallel; + } + + public void useless_mod$setParallel(int parallel) { + this.useless_mod$parallel = parallel; + } + + @Inject(method = "setAttributesFromAugment", at = @At(value = "INVOKE", target = "Lcofh/core/util/helpers/AugmentableHelper;setAttributeFromAugmentMax(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/nbt/CompoundTag;Ljava/lang/String;)V", ordinal = 0, shift = At.Shift.AFTER)) + public void setParallelAttribute(CompoundTag augmentData, CallbackInfo ci) { + AugmentableHelper.setAttributeFromAugmentAdd(this.augmentNBT, augmentData, "MachineParallel"); + } + + @Inject(method = "finalizeAttributes", at = @At("TAIL"), locals = LocalCapture.CAPTURE_FAILHARD) + public void finalizeAttributes(Map enchantmentMap, CallbackInfo ci, float holdingMod, float baseMod, float energyStorageMod, float fluidStorageMod, float itemStorageMod, float xpStorageMod, float energyXferMod, int storedXp, CompoundTag filterNBT) { + int parallel = ThermalParallelMath.extraOperations( + AugmentableHelper.getAttributeMod(this.augmentNBT, "MachineParallel"), baseMod, + ThermalConfig.enabled(ThermalConfig.BASE_MOD_AFFECT_PARALLEL)); + this.useless_mod$parallel = parallel; + final int finalParallel = parallel; + // Clear the slot multiplier when augments are removed or capacity scaling is disabled. + int slotParallel = ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_ITEM_CAPACITY) ? parallel : 0; + this.inventory.getInputSlots().forEach(slot -> ((ThermalItemStorageAccess) slot).useless_mod$setParallel(slotParallel)); + this.inventory.getOutputSlots().forEach(slot -> ((ThermalItemStorageAccess) slot).useless_mod$setParallel(slotParallel)); + + if (parallel > 0) { + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_FLUID_CAPACITY)) { + for (int i = 0; i < this.tankInv.getTanks(); ++i) { + FluidStorageCoFH tank = this.tankInv.getTank(i); + long newCapacity = (long) tank.getCapacity() * (1L + finalParallel); + // 防止整数溢出 + if (newCapacity > Integer.MAX_VALUE) { + newCapacity = Integer.MAX_VALUE; + } + this.tankInv.getTank(i).setCapacity((int) newCapacity); + } + } + + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_ENERGY_CAPACITY)) { + long newCapacity = (long) this.energyStorage.getCapacity() * (1L + finalParallel); + // 防止整数溢出 + if (newCapacity > Integer.MAX_VALUE) { + newCapacity = Integer.MAX_VALUE; + } + this.energyStorage.setCapacity((int) newCapacity); + } + + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_ENERGY_TRANSFER)) { + long newMaxReceive = (long) this.energyStorage.getMaxReceive() * (1L + finalParallel); + long newMaxExtract = (long) this.energyStorage.getMaxExtract() * (1L + finalParallel); + + // 防止整数溢出 + if (newMaxReceive > Integer.MAX_VALUE) { + newMaxReceive = Integer.MAX_VALUE; + } + if (newMaxExtract > Integer.MAX_VALUE) { + newMaxExtract = Integer.MAX_VALUE; + } + + this.energyStorage.setMaxReceive((int) newMaxReceive); + this.energyStorage.setMaxExtract((int) newMaxExtract); + } + } + } +} diff --git a/src/main/java/com/sorrowmist/useless/mixin/thermal/ItemStorageCoFHMixin.java b/src/main/java/com/sorrowmist/useless/mixin/thermal/ItemStorageCoFHMixin.java new file mode 100644 index 00000000..546c1543 --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/mixin/thermal/ItemStorageCoFHMixin.java @@ -0,0 +1,54 @@ +package com.sorrowmist.useless.mixin.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import cofh.lib.common.inventory.ItemStorageCoFH; +import com.sorrowmist.useless.compat.thermal.ThermalItemStorageAccess; +import org.spongepowered.asm.mixin.Mixin; +import com.sorrowmist.useless.compat.thermal.ThermalParallelMath; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ItemStorageCoFH.class, remap = false) +public class ItemStorageCoFHMixin implements ThermalItemStorageAccess { + @Unique private int useless_mod$parallel; + + public void useless_mod$setParallel(int count) { + this.useless_mod$parallel = count; + } + + public int useless_mod$getParallel() { + return this.useless_mod$parallel; + } + + @Inject(at = @At("RETURN"), method = "getSlotLimit", cancellable = true) + public void modifyItemStorage(int slot, CallbackInfoReturnable cir) { + if (this.useless_mod$parallel > 0) { + cir.setReturnValue(ThermalParallelMath.scale(cir.getReturnValueI(), this.useless_mod$parallel)); + } + } +} diff --git a/src/main/java/com/sorrowmist/useless/mixin/thermal/MachineBlockEntityMixin.java b/src/main/java/com/sorrowmist/useless/mixin/thermal/MachineBlockEntityMixin.java new file mode 100644 index 00000000..a17f2d9a --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/mixin/thermal/MachineBlockEntityMixin.java @@ -0,0 +1,92 @@ +package com.sorrowmist.useless.mixin.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import cofh.thermal.lib.common.block.entity.MachineBlockEntity; +import cofh.thermal.lib.common.block.entity.Reconfigurable4WayBlockEntity; +import cofh.thermal.lib.util.recipes.MachineProperties; +import com.sorrowmist.useless.compat.thermal.ThermalParallelAccess; +import com.sorrowmist.useless.core.config.ThermalConfig; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = MachineBlockEntity.class, remap = false) +public abstract class MachineBlockEntityMixin extends Reconfigurable4WayBlockEntity { + @Shadow protected int processMax; + @Shadow protected int process; + + public MachineBlockEntityMixin(BlockEntityType tileEntityTypeIn, BlockPos pos, BlockState state) { + super(tileEntityTypeIn, pos, state); + } + + @Shadow public abstract MachineProperties getMachineProperties(); + @Shadow protected abstract void resolveOutputs(); + @Shadow protected abstract void resolveInputs(); + @Shadow protected abstract boolean validateInputs(); + @Shadow protected abstract boolean validateOutputs(); + + @Inject(method = "tickServer", at = @At(value = "INVOKE", target = "Lcofh/thermal/lib/common/block/entity/MachineBlockEntity;transferOutput()V", ordinal = 0)) + public void addParallelLogic(CallbackInfo ci) { + int parallel = ((ThermalParallelAccess) this).useless_mod$getParallel(); + if (parallel > 0) { + for (int i = 0; i < parallel; ++i) { + if (!this.validateInputs() || !this.validateOutputs()) { + return; + } + + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_ENERGY_CONSUMPTION)) { + // 检查能量是否足够,防止负数溢出 + int energyRequired = this.processMax; + if (energyRequired < 0) { + // 如果 processMax 已经溢出为负数,使用安全值 + energyRequired = Integer.MAX_VALUE / (parallel + 1); + } + + if (this.energyStorage.getEnergyStored() < energyRequired) { + return; + } + + // 安全地减少能量 + int currentEnergy = this.energyStorage.getEnergyStored(); + int newEnergy = currentEnergy - energyRequired; + if (newEnergy < 0) { + newEnergy = 0; + } + this.energyStorage.setEnergyStored(newEnergy); + } + + this.resolveOutputs(); + this.resolveInputs(); + this.markDirtyFast(); + } + } + } +} diff --git a/src/main/java/com/sorrowmist/useless/mixin/thermal/Reconfigurable4WayBlockEntityMixin.java b/src/main/java/com/sorrowmist/useless/mixin/thermal/Reconfigurable4WayBlockEntityMixin.java new file mode 100644 index 00000000..1cbea44f --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/mixin/thermal/Reconfigurable4WayBlockEntityMixin.java @@ -0,0 +1,85 @@ +package com.sorrowmist.useless.mixin.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import cofh.thermal.lib.common.block.entity.AugmentableBlockEntity; +import cofh.thermal.lib.common.block.entity.Reconfigurable4WayBlockEntity; +import com.sorrowmist.useless.compat.thermal.ThermalParallelAccess; +import com.sorrowmist.useless.core.config.ThermalConfig; +import com.sorrowmist.useless.compat.thermal.ThermalParallelMath; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = Reconfigurable4WayBlockEntity.class, remap = false) +public abstract class Reconfigurable4WayBlockEntityMixin extends AugmentableBlockEntity { + public Reconfigurable4WayBlockEntityMixin(BlockEntityType tileEntityTypeIn, BlockPos pos, BlockState state) { + super(tileEntityTypeIn, pos, state); + } + + @Inject(at = @At("RETURN"), method = "getInputItemAmount", cancellable = true) + public void addParallelInput(CallbackInfoReturnable cir) { + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_ITEM_TRANSFER)) { + int parallel = ((ThermalParallelAccess) this).useless_mod$getParallel(); + if (parallel > 0) { + cir.setReturnValue(ThermalParallelMath.scale(cir.getReturnValueI(), parallel)); + } + } + } + + @Inject(at = @At("RETURN"), method = "getOutputItemAmount", cancellable = true) + public void addParallelOutput(CallbackInfoReturnable cir) { + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_ITEM_TRANSFER)) { + int parallel = ((ThermalParallelAccess) this).useless_mod$getParallel(); + if (parallel > 0) { + cir.setReturnValue(ThermalParallelMath.scale(cir.getReturnValueI(), parallel)); + } + } + } + + @Inject(at = @At("RETURN"), method = "getInputFluidAmount", cancellable = true) + public void addParallelInputFluid(CallbackInfoReturnable cir) { + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_FLUID_TRANSFER)) { + int parallel = ((ThermalParallelAccess) this).useless_mod$getParallel(); + if (parallel > 0) { + cir.setReturnValue(ThermalParallelMath.scale(cir.getReturnValueI(), parallel)); + } + } + } + + @Inject(at = @At("RETURN"), method = "getOutputFluidAmount", cancellable = true) + public void addParallelOutputFluid(CallbackInfoReturnable cir) { + if (ThermalConfig.enabled(ThermalConfig.PARALLEL_INCREASE_FLUID_TRANSFER)) { + int parallel = ((ThermalParallelAccess) this).useless_mod$getParallel(); + if (parallel > 0) { + cir.setReturnValue(ThermalParallelMath.scale(cir.getReturnValueI(), parallel)); + } + } + } +} diff --git a/src/main/java/com/sorrowmist/useless/mixin/thermal/ThermalAugmentRulesMixin.java b/src/main/java/com/sorrowmist/useless/mixin/thermal/ThermalAugmentRulesMixin.java new file mode 100644 index 00000000..6fac909d --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/mixin/thermal/ThermalAugmentRulesMixin.java @@ -0,0 +1,48 @@ +package com.sorrowmist.useless.mixin.thermal; + +/* + * This file is part of Thermal Parallel. + * + * Copyright (c) 2025 EtSH-C2H6S + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import cofh.thermal.lib.util.ThermalAugmentRules; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = ThermalAugmentRules.class, remap = false) +public class ThermalAugmentRulesMixin { + @Inject(at = @At("RETURN"), method = "isAdditive", cancellable = true) + private static void setCustomAdditive(String mod, CallbackInfoReturnable cir) { + if (!cir.getReturnValueZ()) { + cir.setReturnValue("MachineParallel".equals(mod)); + } + } + + @Inject(at = @At("RETURN"), method = "isInteger", cancellable = true) + private static void setCustomInt(String mod, CallbackInfoReturnable cir) { + if (!cir.getReturnValueZ()) { + cir.setReturnValue("MachineParallel".equals(mod)); + } + } +} diff --git a/src/main/java/com/sorrowmist/useless/network/AECancelPacket.java b/src/main/java/com/sorrowmist/useless/network/AECancelPacket.java index c1d72300..cc2de12d 100644 --- a/src/main/java/com/sorrowmist/useless/network/AECancelPacket.java +++ b/src/main/java/com/sorrowmist/useless/network/AECancelPacket.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.network; import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.content.menus.FurnaceMenuAccess; import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; import com.sorrowmist.useless.content.blockentities.multiblock.MultiblockAlloyFurnaceCoreBlockEntity; import net.minecraft.core.BlockPos; @@ -33,7 +34,7 @@ public AECancelPacket(BlockPos pos) { public static void handle(AECancelPacket msg, IPayloadContext ctx) { ctx.enqueueWork(() -> { ServerPlayer player = (ServerPlayer) ctx.player(); - if (player == null) return; + if (!FurnaceMenuAccess.canUse(player, msg.pos)) return; BlockEntity be = player.level().getBlockEntity(msg.pos); if (be instanceof AdvancedAlloyFurnaceBlockEntity furnace) { diff --git a/src/main/java/com/sorrowmist/useless/network/AEReturnOutputTogglePacket.java b/src/main/java/com/sorrowmist/useless/network/AEReturnOutputTogglePacket.java index bde7e06f..24c9a9b2 100644 --- a/src/main/java/com/sorrowmist/useless/network/AEReturnOutputTogglePacket.java +++ b/src/main/java/com/sorrowmist/useless/network/AEReturnOutputTogglePacket.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.network; import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.content.menus.FurnaceMenuAccess; import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; @@ -32,7 +33,7 @@ public AEReturnOutputTogglePacket(BlockPos pos) { public static void handle(AEReturnOutputTogglePacket msg, IPayloadContext ctx) { ctx.enqueueWork(() -> { ServerPlayer player = (ServerPlayer) ctx.player(); - if (player == null) return; + if (!FurnaceMenuAccess.canUse(player, msg.pos)) return; BlockEntity be = player.level().getBlockEntity(msg.pos); if (be instanceof AdvancedAlloyFurnaceBlockEntity furnace) { diff --git a/src/main/java/com/sorrowmist/useless/network/AutoIOChangePacket.java b/src/main/java/com/sorrowmist/useless/network/AutoIOChangePacket.java index 5f68bfd7..7637e100 100644 --- a/src/main/java/com/sorrowmist/useless/network/AutoIOChangePacket.java +++ b/src/main/java/com/sorrowmist/useless/network/AutoIOChangePacket.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.network; import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.content.menus.FurnaceMenuAccess; import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; @@ -41,7 +42,7 @@ public AutoIOChangePacket(BlockPos pos, boolean isOutput) { public static void handle(AutoIOChangePacket msg, IPayloadContext ctx) { ctx.enqueueWork(() -> { ServerPlayer player = (ServerPlayer) ctx.player(); - if (player == null) return; + if (!FurnaceMenuAccess.canUse(player, msg.pos)) return; BlockEntity be = player.level().getBlockEntity(msg.pos); if (be instanceof AdvancedAlloyFurnaceBlockEntity furnace) { diff --git a/src/main/java/com/sorrowmist/useless/network/FaceModeChangePacket.java b/src/main/java/com/sorrowmist/useless/network/FaceModeChangePacket.java index 6c42b2e7..a0b70b24 100644 --- a/src/main/java/com/sorrowmist/useless/network/FaceModeChangePacket.java +++ b/src/main/java/com/sorrowmist/useless/network/FaceModeChangePacket.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.network; import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.content.menus.FurnaceMenuAccess; import com.sorrowmist.useless.api.enums.FurnaceFace; import com.sorrowmist.useless.api.enums.FurnaceFaceMode; import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; @@ -50,11 +51,12 @@ public FaceModeChangePacket(BlockPos pos, int faceIndex, boolean reverse) { public static void handle(FaceModeChangePacket msg, IPayloadContext ctx) { ctx.enqueueWork(() -> { ServerPlayer player = (ServerPlayer) ctx.player(); - if (player == null) return; + if (!FurnaceMenuAccess.canUse(player, msg.pos)) return; BlockEntity be = player.level().getBlockEntity(msg.pos); if (be instanceof AdvancedAlloyFurnaceBlockEntity furnace) { - FurnaceFace face = FurnaceFace.values()[msg.faceIndex % FurnaceFace.COUNT]; + if (msg.faceIndex < 0 || msg.faceIndex >= FurnaceFace.COUNT) return; + FurnaceFace face = FurnaceFace.values()[msg.faceIndex]; FurnaceFaceMode newMode = furnace.cycleFaceMode(face, msg.reverse); } }); diff --git a/src/main/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacket.java b/src/main/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacket.java new file mode 100644 index 00000000..cda51d7d --- /dev/null +++ b/src/main/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacket.java @@ -0,0 +1,43 @@ +package com.sorrowmist.useless.network; + +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.client.network.ClientPacketHandlers; +import com.sorrowmist.useless.compat.forge1201.network.ByteBufCodecs; +import com.sorrowmist.useless.compat.forge1201.network.CustomPacketPayload; +import com.sorrowmist.useless.compat.forge1201.network.IPayloadContext; +import com.sorrowmist.useless.compat.forge1201.network.PortCodecs; +import com.sorrowmist.useless.compat.forge1201.network.StreamCodec; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.loading.FMLEnvironment; + +import java.util.List; + +public record FurnaceMenuSyncPacket(int containerId, int stateId, List slots, + ItemStack carried, List data) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(UselessMod.id("furnace_menu_sync")); + private static final StreamCodec> ITEMS = + PortCodecs.ITEM_STACK.apply(ByteBufCodecs.list(512)); + private static final StreamCodec> DATA = + ByteBufCodecs.INT.apply(ByteBufCodecs.list(256)); + public static final StreamCodec STREAM_CODEC = + StreamCodec.of((buffer, packet) -> { + buffer.writeVarInt(packet.containerId); + buffer.writeVarInt(packet.stateId); + ITEMS.encode(buffer, packet.slots); + PortCodecs.ITEM_STACK.encode(buffer, packet.carried); + DATA.encode(buffer, packet.data); + }, buffer -> new FurnaceMenuSyncPacket(buffer.readVarInt(), buffer.readVarInt(), + ITEMS.decode(buffer), PortCodecs.ITEM_STACK.decode(buffer), DATA.decode(buffer))); + + public static void handle(FurnaceMenuSyncPacket packet, IPayloadContext context) { + if (FMLEnvironment.dist != Dist.CLIENT) return; + context.enqueueWork(() -> ClientPacketHandlers.handleFurnaceMenuSync(packet)); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/com/sorrowmist/useless/network/RedstoneControlPacket.java b/src/main/java/com/sorrowmist/useless/network/RedstoneControlPacket.java index 8bfc6db4..59c6eb6f 100644 --- a/src/main/java/com/sorrowmist/useless/network/RedstoneControlPacket.java +++ b/src/main/java/com/sorrowmist/useless/network/RedstoneControlPacket.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.network; import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.content.menus.FurnaceMenuAccess; import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; import com.sorrowmist.useless.content.blockentities.multiblock.MultiblockAlloyFurnaceCoreBlockEntity; import net.minecraft.core.BlockPos; @@ -33,7 +34,7 @@ public RedstoneControlPacket(BlockPos pos) { public static void handle(RedstoneControlPacket msg, IPayloadContext ctx) { ctx.enqueueWork(() -> { ServerPlayer player = (ServerPlayer) ctx.player(); - if (player == null) return; + if (!FurnaceMenuAccess.canUse(player, msg.pos)) return; BlockEntity be = player.level().getBlockEntity(msg.pos); if (be instanceof AdvancedAlloyFurnaceBlockEntity furnace) { diff --git a/src/main/java/com/sorrowmist/useless/network/TankClearPacket.java b/src/main/java/com/sorrowmist/useless/network/TankClearPacket.java index 5062c497..db007ae5 100644 --- a/src/main/java/com/sorrowmist/useless/network/TankClearPacket.java +++ b/src/main/java/com/sorrowmist/useless/network/TankClearPacket.java @@ -1,6 +1,7 @@ package com.sorrowmist.useless.network; import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.content.menus.FurnaceMenuAccess; import com.sorrowmist.useless.content.blockentities.AdvancedAlloyFurnaceBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; @@ -44,7 +45,7 @@ public TankClearPacket(BlockPos pos, int tankIndex, boolean isInput, boolean che public static void handle(TankClearPacket msg, IPayloadContext ctx) { ctx.enqueueWork(() -> { ServerPlayer player = (ServerPlayer) ctx.player(); - if (player == null) return; + if (!FurnaceMenuAccess.canUse(player, msg.pos)) return; BlockEntity be = player.level().getBlockEntity(msg.pos); if (be instanceof AdvancedAlloyFurnaceBlockEntity furnace) { diff --git a/src/main/resources/META-INF/JEI-texture-LICENSE.txt b/src/main/resources/META-INF/JEI-texture-LICENSE.txt new file mode 100644 index 00000000..ec2d94f0 --- /dev/null +++ b/src/main/resources/META-INF/JEI-texture-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 mezz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/main/resources/META-INF/JEI-texture-NOTICE.txt b/src/main/resources/META-INF/JEI-texture-NOTICE.txt new file mode 100644 index 00000000..f184a154 --- /dev/null +++ b/src/main/resources/META-INF/JEI-texture-NOTICE.txt @@ -0,0 +1,4 @@ +textures/gui/exmachinis_recipe.png is the original JEI gui_vanilla.png from +jei-1.20.1-forge-15.2.0.27, retained for Ex Machinis 3.0.3 compatibility. +Source: https://github.com/mezz/JustEnoughItems +License: MIT (see JEI-texture-LICENSE.txt in this directory). diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 64df9f3d..2a125dd7 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -46,7 +46,7 @@ side="BOTH" [[dependencies.useless_mod]] modId="advanced_ae" mandatory=false -versionRange="[1.2.7-1.20.1,2)" +versionRange="[1.3.6-1.20.1,2)" ordering="AFTER" side="BOTH" @@ -308,3 +308,52 @@ mandatory=false versionRange="[1.0.4,2)" ordering="AFTER" side="BOTH" + +[[dependencies.useless_mod]] +modId="avaritia" +mandatory=false +versionRange="[1.4.0,1.5)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="ae2addonlib" +mandatory=false +versionRange="[1.0.4-1.20.1,1.1)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="thermal" +mandatory=false +versionRange="[11.0.7,12)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="cofh_core" +mandatory=false +versionRange="[11.0.2,12)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="bloodmagic" +mandatory=false +versionRange="[3.3.5-47,3.4)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="embers" +mandatory=false +versionRange="[1.20.1-1.4.7,1.20.2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="mekmm" +mandatory=false +versionRange="[1.2.1,1.3)" +ordering="AFTER" +side="BOTH" diff --git a/src/main/resources/assets/useless_mod/lang/en_us.json b/src/main/resources/assets/useless_mod/lang/en_us.json index 2fb39f47..1773e212 100644 --- a/src/main/resources/assets/useless_mod/lang/en_us.json +++ b/src/main/resources/assets/useless_mod/lang/en_us.json @@ -1308,5 +1308,18 @@ "commands.useless_mod.patterns.repair.no_machines": "This network has no powered omniversal furnaces or ME pattern assemblies, so there is nothing to repair", "commands.useless_mod.patterns.repair.summary": "Scanned %s omniversal patterns, found %s invalid; repaired %s, %s could not be repaired (recipe removed or ambiguous).", "commands.useless_mod.patterns.repair.summary.preview": "Scanned %s omniversal patterns, found %s invalid; %s are repairable and %s could not be repaired (recipe removed or ambiguous). This was a preview; nothing was written back.", - "commands.useless_mod.patterns.repair.detail": "Could not repair: %s, bound recipe %s" + "commands.useless_mod.patterns.repair.detail": "Could not repair: %s, bound recipe %s", + "item.useless_mod.useless_integral_component_tier_1": "Useless Integral Component Tier 1", + "item.useless_mod.useless_integral_component_tier_2": "Useless Integral Component Tier 2", + "item.useless_mod.useless_integral_component_tier_3": "Useless Integral Component Tier 3", + "item.useless_mod.useless_integral_component_tier_4": "Useless Integral Component Tier 4", + "item.useless_mod.useless_integral_component_tier_5": "Useless Integral Component Tier 5", + "item.useless_mod.useless_integral_component_tier_6": "Useless Integral Component Tier 6", + "item.useless_mod.useless_integral_component_tier_7": "Useless Integral Component Tier 7", + "item.useless_mod.useless_integral_component_tier_8": "Useless Integral Component Tier 8", + "item.useless_mod.useless_integral_component_tier_9": "Useless Integral Component Tier 9", + "useless_mod.augment.upgrade": "Upgrade Augment", + "item.useless_mod.augment_parallel_1": "Parallel Processing Augment I", + "item.useless_mod.augment_parallel_2": "Parallel Processing Augment II", + "item.useless_mod.augment_parallel_3": "Parallel Processing Augment III" } diff --git a/src/main/resources/assets/useless_mod/lang/zh_cn.json b/src/main/resources/assets/useless_mod/lang/zh_cn.json index 50f5401a..7e447f4d 100644 --- a/src/main/resources/assets/useless_mod/lang/zh_cn.json +++ b/src/main/resources/assets/useless_mod/lang/zh_cn.json @@ -1312,5 +1312,18 @@ "commands.useless_mod.patterns.repair.no_machines": "该网络上没有已通电的万象炉或 ME 样板总成,无需修复", "commands.useless_mod.patterns.repair.summary": "已扫描 %s 张万象样板,发现 %s 张失效;已修复 %s 张,%s 张无法修复(配方已移除或存在歧义)。", "commands.useless_mod.patterns.repair.summary.preview": "已扫描 %s 张万象样板,发现 %s 张失效;其中 %s 张可修复,%s 张无法修复(配方已移除或存在歧义)。本次为预览,未写回任何槽位。", - "commands.useless_mod.patterns.repair.detail": "无法修复:%s,绑定配方 %s" + "commands.useless_mod.patterns.repair.detail": "无法修复:%s,绑定配方 %s", + "item.useless_mod.useless_integral_component_tier_1": "无用整合组件 等级 1", + "item.useless_mod.useless_integral_component_tier_2": "无用整合组件 等级 2", + "item.useless_mod.useless_integral_component_tier_3": "无用整合组件 等级 3", + "item.useless_mod.useless_integral_component_tier_4": "无用整合组件 等级 4", + "item.useless_mod.useless_integral_component_tier_5": "无用整合组件 等级 5", + "item.useless_mod.useless_integral_component_tier_6": "无用整合组件 等级 6", + "item.useless_mod.useless_integral_component_tier_7": "无用整合组件 等级 7", + "item.useless_mod.useless_integral_component_tier_8": "无用整合组件 等级 8", + "item.useless_mod.useless_integral_component_tier_9": "无用整合组件 等级 9", + "useless_mod.augment.upgrade": "升级增强", + "item.useless_mod.augment_parallel_1": "并行处理增强 I", + "item.useless_mod.augment_parallel_2": "并行处理增强 II", + "item.useless_mod.augment_parallel_3": "并行处理增强 III" } diff --git a/src/main/resources/assets/useless_mod/textures/gui/exmachinis_recipe.png b/src/main/resources/assets/useless_mod/textures/gui/exmachinis_recipe.png new file mode 100644 index 0000000000000000000000000000000000000000..14ada120c983cb0220b1627f84db2db5df4984a8 GIT binary patch literal 3459 zcma)9c{r49+rREvGL{BevrhCdO_s@%J!X`(sA-|H(>5&#c@z&u5t%fJLc)j|*|L)| zM!jAnhD1ceqtQ%+l(h!$?RmfNJ&yOU@B8CE&g*wy=XGAkdEUQsxo=SRJ8YCtkpKW} zbaJ#k1ONmtA%NKc3(t_k)36Y~=y*I705RS52b%cnnJuhr>||?m@X+8%bf0$N2SDcV=1_$M-|CCh93`PH4YADX4eTaujE`|YZ4oxW~Uv8*G#!ZNbfXOV23PPXi8S^r`b%<>WwTOer3ZVN=YUI zt>dL2K;HQv0lcj@F{C_Rs&bDlL@Q$x_OA>z@2WvCU%#MDy-gyS6B3h1rS;K*qz-@56`+%LdbT845_qHqLY@ZEDgFXP zH-e+Mw+kr~eBRN6f5g`v3^3=^$RZ`v`*Jd}Eca_AhvV{nnP{4RwYSteyR*70whynw z;xJUmH{M+dX~(@$zr&O==Z}LAK3r%a8^}j*4L}eh_VXLiiQf5W+LygGKL_Nr7}`%U^P*|y+YsLtkO`^*IJiE$S4rK^UKaMtD-{3|(e zet3F|zhK#joB7s%tM0FR9pk*op3vER(7tACqEFtrmwK*78|RWI%gJlE?!pe|s`2sQ z!okbfQA+G{is)==RQvnao>*r|Aa}$sNh?Q3dlZ)cksh+R)6cwyCigjJHS)~c83 z)J;aq;rlG1#Ew|M;t%f;JGZe_%G;(jYP)iFTEAd)SPbee^Y}MxszG_YD@4rYhMyjB zEt7g675k7Lb2@OL>GQ5A79$BY3P}S)9!MQM-j1ar|3lNA#I(y$BFcv=c#n zE?C@kU}$(x${1HwUp@f(K8-^O?`;NQL6eUnsl`Q0^x<^Fsnr`jvQWxO$2Gx^XK{WR zKZBIl!f!;wwEnG5g`#Z??)<*uI#7LCSS8JK_laIk9U&=7uSN5|Bpq4m$c z1wmGN`p}~2+k{~DlKax<9C=IUw+FIT$~;6pX9g}NetVVs`lnJ;^l#r&HR+v_@C2G4$_Cq9^f z`Co+ENQ-R32NjO>-ZFGX=jq7e<##Q|Luk>j4K^IIdT{zORv1nMU0m*Bj-|DAf9;N< zfrgLd-Vg^c`_gHxAtUp*la;WZunp-R!Xqmdgcz6 z+S=^I;h#|bN%g2xrS6%4FEZ^S=qOxLD?0E0Jl3GyN{*6HJs5Gfb#mJu%#7&FA2Y7_ zkis%Y$h7ezS!urGK}^sl_$bk-*Z~^LC_vtS?+yUfXFJ5nv*1W2=SsYRrCO<%B)I}P z)vE&3ijZ&mRi!peHfWChV&6H{>>7QmxEPb%dLl6N`pdMB%KUDXsDMAQ0t{F-lfszY z1VGIm0IW6u39^9p3IjNPC@@MyfQ!ly_^<7nS`FHHozxo(Glh||3d4KAPRH=r4K}#m zFGnWBnI2RKBy4-WBk^o566X_BaqOLUysmqDYXLVn%nrwtazhdPo_TK46Uu)Ed)*Dv z1A-7tDTo4Xp_SnPK?~K5Mv*thfeQ}coh^oQdVh|1VFT6N0(k5|P~uX+1zUiBi2)`d zmFPnElH-wAk~C}mQ53lr=`2}bOc9lAgxzwY8B0I^G8sY#xcFf^@xt7c* z(G7)A`)3lF_9_Dj)IRf{Z~}Y-kvdvHW$cPZfcVWllK<`O{}GD{*kn1+mw4>`ij3Rn zVEqce;?LErk$Vv%mv>En;ThoQl?t)-?_BxQ`EurwT9#XGOT`Zz(CGN+pljs8cA!2C zdjsL+dG77-3rp@JD{N5d{b4Np3O$?kuyJj^aMV904OGVg%1O&NYbV1GtxZ=Q2p_pT|8u2jt>#82 z0;>h&2c;$r`Cp|;Bi41|@$}-9X8XCH@ZC2N-4+P)-D0hnh3Waf8eQ-ra@TZIRn*Yu z+oLV}@gQIeNP8wDn=)C|)Q$K3J(FhZsH{E3y%GF9Vq;F*PSN z6prpu6o<&hpMexSHhkcr%*|OvwtH9pK)Ak6OhJ76fF1yZol^{LDLtPr!FFAUJ$GdGg6P)TCBvzz2 z*~>ccJ87o*npl{LxLMnhIc;m|9`dP*Ln7+S{h(lD75%2f_d*SJmKuUU5mA$jLxGc5 zwj9jEf&g$*;mDvM>urlHXkk2_>V)_TZtpgw9jrN4#ZDL#duKL)sBTO@uxA!I@fA(( zw;j!U4=ax;9gLg<_!C2jH1!XrUN;{rktF}%*vH>ORDYDn@D|ZZLl}sYX9@E7XmW10 zBJPPWw1Fa`dH3gFP)s&E$u%_JH5eLrWOyj1&r_jz3_;b;6xNcEy=Isjez%%vXK*_G4RZy|ctiz=vkPx2C{!Z$i4}y?grg0NqP|yR8}wPFDt^Wc;>*cdQF`%*Q(r zU}7>dhZVI$I`AJ*Mtg%I4W>i|0l=TNG#lV8D*FG4QfrYeJkx@KcjgAf0r$tk!t_~2 zmY!phW_PO)W|g$?uB8~|Dqq?@dR34E}bGWAJr6 zps^pKzVrqK>YwSPT7CKZ_5DzJUo9GMSCHI zBdl}hCn4hG$fCKiayiAPAey0uioGWpSTBZSrqAjFmKyS>vL?`q15COCgxie)7RTiN zhWB0nh6Ljf9p=QtaUoz{57VY$v>~STc_d91P<*uY$$kEvA36?;Xe#~5>rqoLMy!Xh zQfD1lZC?j-MC)~+nU7ot6QPTq9IvS99Lf3)p(3Dp!_BU|Yeb1=j>UNhkn2KqJyrhg eB>W#`Hog|&vRW~#l^Oyg;AFSowrscW)qeq$k`M?0 literal 0 HcmV?d00001 diff --git a/src/main/resources/data/useless_mod/recipes/augment_parallel_1.json b/src/main/resources/data/useless_mod/recipes/augment_parallel_1.json new file mode 100644 index 00000000..b0e9f83a --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/augment_parallel_1.json @@ -0,0 +1,29 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "AAA", + "BBB", + "CCC" + ], + "key": { + "A": { + "item": "useless_mod:useless_integral_component_tier_1" + }, + "B": { + "item": "useless_mod:useless_integral_component_tier_2" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_3" + } + }, + "result": { + "item": "useless_mod:augment_parallel_1", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/augment_parallel_2.json b/src/main/resources/data/useless_mod/recipes/augment_parallel_2.json new file mode 100644 index 00000000..74bd551c --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/augment_parallel_2.json @@ -0,0 +1,29 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "AAA", + "BBB", + "CCC" + ], + "key": { + "A": { + "item": "useless_mod:augment_parallel_1" + }, + "B": { + "item": "useless_mod:useless_integral_component_tier_4" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_5" + } + }, + "result": { + "item": "useless_mod:augment_parallel_2", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/augment_parallel_3.json b/src/main/resources/data/useless_mod/recipes/augment_parallel_3.json new file mode 100644 index 00000000..c1451029 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/augment_parallel_3.json @@ -0,0 +1,29 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "AAA", + "BBB", + "CCC" + ], + "key": { + "A": { + "item": "useless_mod:augment_parallel_2" + }, + "B": { + "item": "useless_mod:useless_integral_component_tier_6" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_7" + } + }, + "result": { + "item": "useless_mod:augment_parallel_3", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_1.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_1.json new file mode 100644 index 00000000..328d1128 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_1.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_1" + }, + "O": { + "item": "useless_mod:useless_glass_tier_1" + }, + "G": { + "item": "useless_mod:useless_gear_tier_1" + }, + "C": { + "item": "thermal:upgrade_augment_3" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_1", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_2.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_2.json new file mode 100644 index 00000000..7e718785 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_2.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_2" + }, + "O": { + "item": "useless_mod:useless_glass_tier_2" + }, + "G": { + "item": "useless_mod:useless_gear_tier_2" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_1" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_2", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_3.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_3.json new file mode 100644 index 00000000..6db374f3 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_3.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_3" + }, + "O": { + "item": "useless_mod:useless_glass_tier_3" + }, + "G": { + "item": "useless_mod:useless_gear_tier_3" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_2" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_3", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_4.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_4.json new file mode 100644 index 00000000..06518ac2 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_4.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_4" + }, + "O": { + "item": "useless_mod:useless_glass_tier_4" + }, + "G": { + "item": "useless_mod:useless_gear_tier_4" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_3" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_4", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_5.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_5.json new file mode 100644 index 00000000..43b221f3 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_5.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_5" + }, + "O": { + "item": "useless_mod:useless_glass_tier_5" + }, + "G": { + "item": "useless_mod:useless_gear_tier_5" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_4" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_5", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_6.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_6.json new file mode 100644 index 00000000..23d92603 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_6.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_6" + }, + "O": { + "item": "useless_mod:useless_glass_tier_6" + }, + "G": { + "item": "useless_mod:useless_gear_tier_6" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_5" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_6", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_7.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_7.json new file mode 100644 index 00000000..935ffc28 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_7.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_7" + }, + "O": { + "item": "useless_mod:useless_glass_tier_7" + }, + "G": { + "item": "useless_mod:useless_gear_tier_7" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_6" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_7", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_8.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_8.json new file mode 100644 index 00000000..138d2a46 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_8.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_8" + }, + "O": { + "item": "useless_mod:useless_glass_tier_8" + }, + "G": { + "item": "useless_mod:useless_gear_tier_8" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_7" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_8", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_9.json b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_9.json new file mode 100644 index 00000000..9b48da7d --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/useless_integral_component_tier_9.json @@ -0,0 +1,32 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#O#", + "GCG", + "#O#" + ], + "key": { + "#": { + "item": "useless_mod:useless_ingot_tier_9" + }, + "O": { + "item": "useless_mod:useless_glass_tier_9" + }, + "G": { + "item": "useless_mod:useless_gear_tier_9" + }, + "C": { + "item": "useless_mod:useless_integral_component_tier_8" + } + }, + "result": { + "item": "useless_mod:useless_integral_component_tier_9", + "count": 1 + }, + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal" + } + ] +} diff --git a/src/main/resources/useless_mod.mixins.json b/src/main/resources/useless_mod.mixins.json index 3b78aa3a..71631102 100644 --- a/src/main/resources/useless_mod.mixins.json +++ b/src/main/resources/useless_mod.mixins.json @@ -45,6 +45,7 @@ "ServerGamePacketListenerImplMixin" ], "client": [ + "exmachinis.CompactingCategoryMixin", "ClientLevelMixin", "ClientPacketListenerMixin", "LocalPlayerMixin", diff --git a/src/main/resources/useless_mod.thermal.mixins.json b/src/main/resources/useless_mod.thermal.mixins.json new file mode 100644 index 00000000..aaca12d8 --- /dev/null +++ b/src/main/resources/useless_mod.thermal.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "package": "com.sorrowmist.useless.mixin.thermal", + "plugin": "com.sorrowmist.useless.compat.thermal.ThermalMixinPlugin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "AugmentableBlockEntityMixin", + "ItemStorageCoFHMixin", + "MachineBlockEntityMixin", + "Reconfigurable4WayBlockEntityMixin", + "ThermalAugmentRulesMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "refmap": "useless_mod.refmap.json", + "minVersion": "0.8" +} diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java index ddf88f23..459fc0dc 100644 --- a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java @@ -65,7 +65,7 @@ private static Map requirements() throws Exception { } } } - assertEquals(41, checked.size(), "All compiled mod metadata must be inspected"); + assertEquals(48, checked.size(), "All compiled mod metadata must be inspected"); } @Test void obsoleteApisAreRejectedButTheUpdatedPackVersionsRemainAllowed() throws Exception { diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/RecipeTestBootstrap.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/RecipeTestBootstrap.java index ed30923e..d8ac0410 100644 --- a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/RecipeTestBootstrap.java +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/RecipeTestBootstrap.java @@ -11,7 +11,9 @@ public final class RecipeTestBootstrap { public static synchronized void initialize() { if (initialized) return; SharedConstants.tryDetectVersion(); - try (var loading = mockStatic(LoadingModList.class)) { + // Network event listeners require Forge's transformer, exercised by the GameTests instead. + try (var network = mockStatic(net.minecraftforge.network.NetworkHooks.class); + var loading = mockStatic(LoadingModList.class)) { var mods = mock(LoadingModList.class); when(mods.getModFiles()).thenReturn(List.of()); loading.when(LoadingModList::get).thenReturn(mods); diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeTestCapabilities.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeTestCapabilities.java new file mode 100644 index 00000000..744ce445 --- /dev/null +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeTestCapabilities.java @@ -0,0 +1,25 @@ +package com.sorrowmist.useless.compat.forge1201.capability; + +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.energy.IEnergyStorage; +import net.minecraftforge.items.IItemHandler; + +/** Native tokens without the launcher's CapabilityToken bytecode transformation. */ +final class NativeTestCapabilities { + static final Capability ENERGY = create(IEnergyStorage.class.getName()); + static final Capability ITEM_HANDLER = create(IItemHandler.class.getName()); + + private NativeTestCapabilities() { + } + + @SuppressWarnings("unchecked") + private static Capability create(String name) { + try { + var constructor = Capability.class.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + return (Capability) constructor.newInstance(name); + } catch (ReflectiveOperationException exception) { + throw new IllegalStateException("Cannot create native Forge capability test token", exception); + } + } +} diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/jei/AEKeyBookmarkPortTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/jei/AEKeyBookmarkPortTest.java index c76dc840..77e5e92a 100644 --- a/src/portBridgeTest/java/com/sorrowmist/useless/compat/jei/AEKeyBookmarkPortTest.java +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/jei/AEKeyBookmarkPortTest.java @@ -10,10 +10,10 @@ import mezz.jei.api.ingredients.*; import mezz.jei.api.ingredients.subtypes.UidContext; import mezz.jei.api.runtime.IIngredientManager; -import mezz.jei.common.config.file.serializers.TypedIngredientSerializer; +import mezz.jei.gui.config.file.serializers.TypedIngredientSerializer; import mezz.jei.gui.bookmarks.IngredientBookmark; import mezz.jei.library.ingredients.IngredientSet; -import mezz.jei.library.ingredients.TypedIngredient; +import mezz.jei.common.ingredients.TypedIngredient; import net.minecraft.nbt.*; import net.minecraft.world.item.*; import net.minecraft.world.level.material.Fluids; diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/content/recipe/adapters/ae/extendedae/NativeAeRecipeTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/content/recipe/adapters/ae/extendedae/NativeAeRecipeTest.java index 11935c7f..e38fdacf 100644 --- a/src/portBridgeTest/java/com/sorrowmist/useless/content/recipe/adapters/ae/extendedae/NativeAeRecipeTest.java +++ b/src/portBridgeTest/java/com/sorrowmist/useless/content/recipe/adapters/ae/extendedae/NativeAeRecipeTest.java @@ -81,9 +81,9 @@ static CircuitCutterRecipe cutting(IngredientStack.Fluid fluid) { } static ReactionChamberRecipe reaction(GenericStack output,int secondCount) { return new ReactionChamberRecipe(ID,output,List.of( - new net.pedroksl.advanced_ae.recipes.IngredientStack.Item(Ingredient.of(Items.IRON_INGOT),2), - new net.pedroksl.advanced_ae.recipes.IngredientStack.Item(Ingredient.of(Items.IRON_INGOT),secondCount)), - new net.pedroksl.advanced_ae.recipes.IngredientStack.Fluid(Fluids.WATER,500),12000); + new net.pedroksl.ae2addonlib.recipes.IngredientStack.Item(Ingredient.of(Items.IRON_INGOT),2), + new net.pedroksl.ae2addonlib.recipes.IngredientStack.Item(Ingredient.of(Items.IRON_INGOT),secondCount)), + new net.pedroksl.ae2addonlib.recipes.IngredientStack.Fluid(Fluids.WATER,500),12000); } @Test void reactionAllocatesDuplicateInputsAndFullFluidCost() { var output=new GenericStack(AEItemKey.of(Items.DIAMOND),3); diff --git a/src/test/java/com/sorrowmist/useless/InventoryTestBootstrap.java b/src/test/java/com/sorrowmist/useless/InventoryTestBootstrap.java index f417fd98..72821dea 100644 --- a/src/test/java/com/sorrowmist/useless/InventoryTestBootstrap.java +++ b/src/test/java/com/sorrowmist/useless/InventoryTestBootstrap.java @@ -10,16 +10,27 @@ /** Installs the launcher context needed by Minecraft's inventory and fluid classes in unit tests. */ public final class InventoryTestBootstrap { + private static boolean initialized; private InventoryTestBootstrap() { } - public static void initialize() { + public static synchronized void initialize() { + if (initialized) return; SharedConstants.tryDetectVersion(); - try (var loading = mockStatic(LoadingModList.class)) { + // Forge 47.4 initializes handshake listeners here; plain JUnit has no event transformer. + try (var network = mockStatic(net.minecraftforge.network.NetworkHooks.class); + var loading = mockStatic(LoadingModList.class)) { var mods = mock(LoadingModList.class); when(mods.getModFiles()).thenReturn(List.of()); loading.when(LoadingModList::get).thenReturn(mods); Bootstrap.bootStrap(); } + net.minecraftforge.common.crafting.CraftingHelper.register(new net.minecraft.resources.ResourceLocation("minecraft:item"), + net.minecraftforge.common.crafting.VanillaIngredientSerializer.INSTANCE); + net.minecraftforge.common.crafting.CraftingHelper.register(new net.minecraft.resources.ResourceLocation("forge:nbt"), + net.minecraftforge.common.crafting.StrictNBTIngredient.Serializer.INSTANCE); + net.minecraftforge.common.crafting.CraftingHelper.register(new net.minecraft.resources.ResourceLocation("cofh_core:count"), + cofh.lib.util.crafting.IngredientWithCount.Serializer.INSTANCE); + initialized = true; } } diff --git a/src/test/java/com/sorrowmist/useless/client/gui/FurnaceScreenScalerTest.java b/src/test/java/com/sorrowmist/useless/client/gui/FurnaceScreenScalerTest.java new file mode 100644 index 00000000..d27977fb --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/client/gui/FurnaceScreenScalerTest.java @@ -0,0 +1,17 @@ +package com.sorrowmist.useless.client.gui; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class FurnaceScreenScalerTest { + @Test void smallWindowFitsEveryFurnacePanelAndLargeWindowPreservesPreference() { + for (int panel : new int[]{178, 222, 260, 306, 336}) { + double scale = FurnaceScreenScaler.scaleForWindow(2.0D, 480, panel); + assertTrue((panel + 16) * scale <= 480); + assertTrue(scale >= 1.0D && scale <= 2.0D); + } + assertEquals(3.0D, FurnaceScreenScaler.scaleForWindow(3.0D, 2160, 336)); + assertEquals(1.0D, FurnaceScreenScaler.scaleForWindow(4.0D, 200, 336)); + } +} diff --git a/src/test/java/com/sorrowmist/useless/compat/ae/AeHostLookupTest.java b/src/test/java/com/sorrowmist/useless/compat/ae/AeHostLookupTest.java new file mode 100644 index 00000000..aab878b9 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/compat/ae/AeHostLookupTest.java @@ -0,0 +1,39 @@ +package com.sorrowmist.useless.compat.ae; + +import appeng.api.networking.IInWorldGridNodeHost; +import com.sorrowmist.useless.InventoryTestBootstrap; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class AeHostLookupTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + @Test void nativeInterfaceResolvesWithoutRequiringACapability() { + var level = mock(Level.class); + var pos = BlockPos.ZERO; + when(level.isLoaded(pos)).thenReturn(true); + var nativeMachine = mock(BlockEntity.class, withSettings().extraInterfaces(IInWorldGridNodeHost.class)); + when(level.getBlockEntity(pos)).thenReturn(nativeMachine); + assertSame(nativeMachine, AeNetworks.host(level, pos)); + verify(nativeMachine, never()).getCapability(any(), any()); + } + + @Test void unloadedAndRemovedMachinesCannotBecomeEndpoints() { + var level = mock(Level.class); + assertNull(AeNetworks.host(level, BlockPos.ZERO)); + verify(level, never()).getBlockEntity(any()); + when(level.isLoaded(BlockPos.ZERO)).thenReturn(true); + var removed = mock(BlockEntity.class, withSettings().extraInterfaces(IInWorldGridNodeHost.class)); + when(removed.isRemoved()).thenReturn(true); + when(level.getBlockEntity(BlockPos.ZERO)).thenReturn(removed); + assertNull(AeNetworks.host(level, BlockPos.ZERO)); + assertNull(AeNetworks.host(null, BlockPos.ZERO)); + assertNull(AeNetworks.host(level, null)); + } +} diff --git a/src/test/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferTest.java b/src/test/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferTest.java new file mode 100644 index 00000000..63bf5a26 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/compat/jei/OmniversalPatternJeiTransferTest.java @@ -0,0 +1,111 @@ +package com.sorrowmist.useless.compat.jei; + +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.GenericStack; +import appeng.integration.modules.jeirei.EncodingHelper; +import appeng.menu.me.items.PatternEncodingTermMenu; +import appeng.menu.slot.FakeSlot; +import com.sorrowmist.useless.InventoryTestBootstrap; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.network.CustomPacketPayload; +import com.sorrowmist.useless.compat.forge1201.network.PacketDistributor; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeCatalog; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeIdentity; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import com.sorrowmist.useless.network.SelectOmniversalPatternRecipePacket; +import mezz.jei.api.gui.ingredient.IRecipeSlotsView; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.Level; +import net.minecraftforge.fml.ModList; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +class OmniversalPatternJeiTransferTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + private static AlloyFurnaceRecipeCatalog.Entry entry() { + var recipe = new AdvancedAlloyFurnaceRecipe(new ResourceLocation("test", "smelting"), + List.of(new CountedIngredient(Ingredient.of(Items.RAW_IRON), 1L)), + List.of(), List.of(new ItemStack(Items.IRON_INGOT)), List.of(), + 2000L, 200, Ingredient.EMPTY, 0, Ingredient.of(Items.FURNACE), AlloyFurnaceMode.NORMAL); + return new AlloyFurnaceRecipeCatalog.Entry( + new AlloyFurnaceRecipeIdentity(recipe.id(), "test-fingerprint"), recipe, "minecraft"); + } + + private static PatternEncodingTermMenu menu() { + var menu = mock(PatternEncodingTermMenu.class); + when(menu.getProcessingInputSlots()).thenReturn(new FakeSlot[9]); + when(menu.getProcessingOutputSlots()).thenReturn(new FakeSlot[3]); + return menu; + } + + @Test void installedExtendedAePlusCannotBlockNormalAe2PatternTransfer() { + var menu = menu(); + var player = mock(Player.class); + var level = mock(Level.class); + when(player.level()).thenReturn(level); + when(level.registryAccess()).thenReturn(RegistryAccess.EMPTY); + var entry = entry(); + var helper = mock(IRecipeTransferHandlerHelper.class); + try (var mods = mockStatic(ModList.class); + var packets = mockStatic(PacketDistributor.class); + var encoding = mockStatic(EncodingHelper.class)) { + var modList = mock(ModList.class); + mods.when(ModList::get).thenReturn(modList); + when(modList.isLoaded("extendedae_plus")).thenReturn(true); + assertNull(OmniversalPatternJeiTransferHandler.transferOmniversalRecipe(menu, entry, + mock(IRecipeSlotsView.class), player, false, true, helper)); + var selected = ArgumentCaptor.forClass(CustomPacketPayload[].class); + packets.verify(() -> PacketDistributor.sendToServer(selected.capture())); + var choice = assertInstanceOf(SelectOmniversalPatternRecipePacket.class, selected.getValue()[0]); + assertEquals(entry.recipe().id(), choice.recipeId()); + assertEquals(entry.sourceId(), choice.sourceId()); + assertFalse(choice.fingerprint().isBlank()); + encoding.verify(() -> EncodingHelper.encodeProcessingRecipe(menu, + List.of(List.of(new GenericStack(AEItemKey.of(Items.RAW_IRON), 1L))), + List.of(new GenericStack(AEItemKey.of(Items.IRON_INGOT), 1L)))); + verifyNoInteractions(helper); + } + } + + @Test void jeiSimulationDoesNotSendSelectionOrModifyTerminal() { + try (var packets = mockStatic(PacketDistributor.class); + var encoding = mockStatic(EncodingHelper.class)) { + assertNull(OmniversalPatternJeiTransferHandler.transferOmniversalRecipe(menu(), entry(), + mock(IRecipeSlotsView.class), mock(Player.class), false, false, + mock(IRecipeTransferHandlerHelper.class))); + packets.verifyNoInteractions(); + encoding.verifyNoInteractions(); + } + } + + @Test void insufficientTerminalSlotsRejectBeforeSendingSelection() { + var menu = menu(); + when(menu.getProcessingInputSlots()).thenReturn(new FakeSlot[0]); + var helper = mock(IRecipeTransferHandlerHelper.class); + var error = mock(IRecipeTransferError.class); + when(helper.createUserErrorWithTooltip(any())).thenReturn(error); + try (var packets = mockStatic(PacketDistributor.class); + var encoding = mockStatic(EncodingHelper.class)) { + assertSame(error, OmniversalPatternJeiTransferHandler.transferOmniversalRecipe(menu, entry(), + mock(IRecipeSlotsView.class), mock(Player.class), false, true, helper)); + packets.verifyNoInteractions(); + encoding.verifyNoInteractions(); + } + } +} diff --git a/src/test/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMathTest.java b/src/test/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMathTest.java new file mode 100644 index 00000000..87bf2b96 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/compat/thermal/ThermalParallelMathTest.java @@ -0,0 +1,21 @@ +package com.sorrowmist.useless.compat.thermal; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ThermalParallelMathTest { + @Test void noAugmentKeepsNativeCapacityAndOneExtraDoublesExactlyOnce() { + assertEquals(0, ThermalParallelMath.extraOperations(0.0F, 5000.0F, false)); + assertEquals(64, ThermalParallelMath.scale(64, 0)); + assertEquals(128, ThermalParallelMath.scale(64, 1)); + assertEquals(9, ThermalParallelMath.extraOperations(0.0F, 10.0F, true)); + } + + @Test void largeAugmentsAndTransfersSaturateInsteadOfBecomingNegative() { + assertEquals(Integer.MAX_VALUE, ThermalParallelMath.scale(Integer.MAX_VALUE, 256)); + assertEquals(Integer.MAX_VALUE, ThermalParallelMath.scale(1000000, Integer.MAX_VALUE)); + assertEquals(Integer.MAX_VALUE - 1, ThermalParallelMath.extraOperations(Float.MAX_VALUE, Float.MAX_VALUE, true)); + assertEquals(0, ThermalParallelMath.extraOperations(-1.0F, 1.0F, false)); + } +} diff --git a/src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccessTest.java b/src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccessTest.java new file mode 100644 index 00000000..9a6133a5 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuAccessTest.java @@ -0,0 +1,47 @@ +package com.sorrowmist.useless.content.menus; + +import com.sorrowmist.useless.InventoryTestBootstrap; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AbstractContainerMenu; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class FurnaceMenuAccessTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + @Test void controlPacketsRequireLoadedNearbyMatchingOpenMachine() { + var player = mock(ServerPlayer.class); + var level = mock(ServerLevel.class); + var menu = mock(MultiblockAlloyFurnaceMenu.class); + var pos = new BlockPos(1, 2, 3); + player.containerMenu = menu; + when(player.level()).thenReturn(level); + when(level.hasChunkAt(pos)).thenReturn(true); + when(menu.stillValid(player)).thenReturn(true); + when(menu.getBlockPos()).thenReturn(pos); + assertTrue(FurnaceMenuAccess.canUse(player, pos)); + when(player.isSpectator()).thenReturn(true); + assertFalse(FurnaceMenuAccess.canUse(player, pos)); + when(player.isSpectator()).thenReturn(false); + when(level.hasChunkAt(pos)).thenReturn(false); + assertFalse(FurnaceMenuAccess.canUse(player, pos)); + when(level.hasChunkAt(pos)).thenReturn(true); + when(player.distanceToSqr(1.5D, 2.5D, 3.5D)).thenReturn(65.0D); + assertFalse(FurnaceMenuAccess.canUse(player, pos)); + when(player.distanceToSqr(1.5D, 2.5D, 3.5D)).thenReturn(0.0D); + when(menu.stillValid(player)).thenReturn(false); + assertFalse(FurnaceMenuAccess.canUse(player, pos)); + when(menu.stillValid(player)).thenReturn(true); + when(menu.getBlockPos()).thenReturn(pos.above()); + assertFalse(FurnaceMenuAccess.canUse(player, pos)); + player.containerMenu = mock(AbstractContainerMenu.class); + when(player.containerMenu.stillValid(player)).thenReturn(true); + assertFalse(FurnaceMenuAccess.canUse(player, pos)); + assertFalse(FurnaceMenuAccess.canUse(null, pos)); + } +} diff --git a/src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizerTest.java b/src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizerTest.java new file mode 100644 index 00000000..c68fa596 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/content/menus/FurnaceMenuSynchronizerTest.java @@ -0,0 +1,28 @@ +package com.sorrowmist.useless.content.menus; + +import net.minecraft.world.inventory.SimpleContainerData; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; +import com.sorrowmist.useless.InventoryTestBootstrap; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class FurnaceMenuSynchronizerTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + @Test void combinedPageAndHatchDataUsesFullIntsAndRejectsWrongShapeWithoutMutation() { + var pages = new SimpleContainerData(3); + var hatch = new SimpleContainerData(2); + var sync = new FurnaceMenuSynchronizer(); + sync.addData(pages); sync.addData(hatch); + assertTrue(sync.applyData(List.of(2, 50, 49, Integer.MAX_VALUE, 100000))); + assertEquals(2, pages.get(0)); + assertEquals(Integer.MAX_VALUE, hatch.get(0)); + assertEquals(100000, hatch.get(1)); + assertFalse(sync.applyData(List.of(9, 10))); + assertEquals(2, pages.get(0)); + assertEquals(Integer.MAX_VALUE, hatch.get(0)); + assertTrue(sync.applyData(List.of())); + } +} diff --git a/src/test/java/com/sorrowmist/useless/content/recipe/RetainedIngredientTest.java b/src/test/java/com/sorrowmist/useless/content/recipe/RetainedIngredientTest.java new file mode 100644 index 00000000..e90c0026 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/content/recipe/RetainedIngredientTest.java @@ -0,0 +1,78 @@ +package com.sorrowmist.useless.content.recipe; + +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.GenericStack; +import com.sorrowmist.useless.InventoryTestBootstrap; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class RetainedIngredientTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + @Test void overlappingRetainedMoldMustBeReservedBeforeConsumingOtherInputs() { + var requirements = List.of(new CountedIngredient(Ingredient.of(Items.IRON_INGOT, Items.GOLD_INGOT), 1L), + new CountedIngredient(Ingredient.of(Items.IRON_INGOT), 1L, false)); + var inputs = List.of(new ItemStack(Items.IRON_INGOT), new ItemStack(Items.GOLD_INGOT, 2)); + assertEquals(2, ItemIngredientAllocator.maxOperations(requirements, inputs)); + var allocation = ItemIngredientAllocator.allocate(requirements, inputs, 2L); + assertNotNull(allocation); + assertEquals(0L, allocation.consumedFromInput(0)); + assertEquals(2L, allocation.consumedFromInput(1)); + assertNull(ItemIngredientAllocator.allocate(requirements, List.of(new ItemStack(Items.IRON_INGOT)), 1L)); + } + + @Test void exactAndSingleIngredientPathsAlsoReserveWithoutConsuming() { + var seed = new CountedIngredient(Ingredient.of(Items.WHEAT_SEEDS), 1L, false); + var seeds = List.of(new ItemStack(Items.WHEAT_SEEDS)); + assertEquals(Integer.MAX_VALUE, ItemIngredientAllocator.maxOperations(List.of(seed), seeds)); + assertEquals(0L, ItemIngredientAllocator.allocate(List.of(seed), seeds, Integer.MAX_VALUE).consumedFromInput(0)); + assertEquals(0, ItemIngredientAllocator.maxOperations(List.of(seed), List.of())); + var recipe = List.of(seed, new CountedIngredient(Ingredient.of(Items.IRON_INGOT), 2)); + var allocation = ItemIngredientAllocator.allocate(recipe, + List.of(new ItemStack(Items.WHEAT_SEEDS), new ItemStack(Items.IRON_INGOT, 6)), 3L); + assertNotNull(allocation); + assertEquals(0L, allocation.consumedFromInput(0)); + assertEquals(6L, allocation.consumedFromInput(1)); + } + + @Test void retainedFlagAndLongQuantitySurviveTheActualIngredientCodec() { + var value = new CountedIngredient(Ingredient.of(Items.DIAMOND), 3_000_000_000L, false); + var buffer = new FriendlyByteBuf(Unpooled.buffer()); + try { + CountedIngredient.STREAM_CODEC.encode(buffer, value); + var decoded = CountedIngredient.STREAM_CODEC.decode(buffer); + assertEquals(value.count(), decoded.count()); + assertFalse(decoded.consumed()); + assertTrue(decoded.ingredient().test(new ItemStack(Items.DIAMOND))); + assertEquals(0, buffer.readableBytes()); + } finally { buffer.release(); } + } + + @Test void aeReturnsOnlyActualUnconsumedMaterialsWithTheirNbtAndLongAmounts() { + ItemStack tool = new ItemStack(Items.DIAMOND); + tool.getOrCreateTag().putLong("custom", Long.MAX_VALUE); + var key = AEItemKey.of(tool); + var recipe = new AdvancedAlloyFurnaceRecipe(new ResourceLocation("test", "retained"), + List.of(new CountedIngredient(Ingredient.of(Items.IRON_INGOT), 1), + new CountedIngredient(Ingredient.of(Items.DIAMOND), 1, false)), List.of(), + List.of(new ItemStack(Items.GOLD_INGOT)), List.of(), 1L, 1, + Ingredient.EMPTY, 0, Ingredient.EMPTY, AlloyFurnaceMode.NORMAL); + var remainder = RetainedRecipeInputs.remainders(recipe, List.of(new ItemStack(Items.IRON_INGOT, 64)), + List.of(new GenericStack(key, 3_000_000_000L)), 64L); + assertNotNull(remainder); + assertEquals(List.of(new GenericStack(key, 3_000_000_000L)), remainder); + assertEquals(tool.getTag(), ((AEItemKey) remainder.get(0).what()).toStack(1).getTag()); + assertNull(RetainedRecipeInputs.remainders(recipe, List.of(), List.of(new GenericStack(key, 1)), 1L)); + } +} diff --git a/src/test/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapterTest.java b/src/test/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapterTest.java new file mode 100644 index 00000000..b0b70d69 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/content/recipe/adapters/BatchRecipeAdapterTest.java @@ -0,0 +1,174 @@ +package com.sorrowmist.useless.content.recipe.adapters; + +import cofh.lib.util.crafting.IngredientWithCount; +import cofh.thermal.core.util.recipes.machine.InsolatorRecipe; +import cofh.thermal.core.util.recipes.machine.PressRecipe; +import cofh.thermal.core.util.recipes.machine.SmelterRecipe; +import com.google.gson.JsonParser; +import com.rekindled.embers.recipe.IAlchemyRecipe; +import com.rekindled.embers.recipe.IMeltingRecipe; +import com.rekindled.embers.recipe.IMixingRecipe; +import com.rekindled.embers.recipe.IStampingRecipe; +import com.sorrowmist.useless.InventoryTestBootstrap; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.adapters.bloodmagic.BloodMagicRecipeAdapter; +import com.sorrowmist.useless.content.recipe.adapters.embers.EmbersRecipeAdapter; +import com.sorrowmist.useless.content.recipe.adapters.thermal.ThermalRecipeAdapter; +import com.sorrowmist.useless.content.recipe.adapters.thermal.NativeThermalIngredients; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.Fluids; +import net.minecraftforge.common.crafting.StrictNBTIngredient; +import net.minecraftforge.fluids.FluidStack; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import wayoftime.bloodmagic.recipe.RecipeAlchemyTable; +import wayoftime.bloodmagic.recipe.RecipeBloodAltar; +import wayoftime.bloodmagic.recipe.RecipeTartaricForge; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class BatchRecipeAdapterTest { + private static final ResourceLocation ID = new ResourceLocation("test", "batch"); + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + private static > T adapter(T value) { + T spy = spy(value); + doReturn(new ItemStack(Items.FURNACE)).when(spy).getMoldItem(); + return spy; + } + + @Test void absentNativeMoldNeverBecomesAWildcard() { + var value = spy(new EmbersRecipeAdapter<>(IMixingRecipe.class, "missing", "test_", false)); + doReturn(ItemStack.EMPTY).when(value).getMoldItem(); + assertFalse(value.matchesMold(new ItemStack(Items.FURNACE))); + assertFalse(value.matchesMold(ItemStack.EMPTY)); + assertTrue(value.findMatchingRecipes(mock(Level.class), Map.of(), Map.of(), Map.of(), + new ItemStack(Items.FURNACE), List.of()).isEmpty()); + } + + @Test void badExternalRecipeCannotCrashMachineLookup() { + var value = adapter(new EmbersRecipeAdapter<>(IMixingRecipe.class, "mixer_centrifuge", "test_", false)); + var source = mock(IMixingRecipe.class); + when(source.getId()).thenReturn(ID); + when(source.getDisplayInputFluids()).thenThrow(new IllegalArgumentException("broken external recipe")); + var level = mock(Level.class); + var manager = mock(RecipeManager.class); + when(level.getRecipeManager()).thenReturn(manager); + when(manager.getRecipes()).thenReturn(List.of(source)); + assertTrue(value.findMatchingRecipes(level, Map.of(), Map.of(), Map.of(), + new ItemStack(Items.FURNACE), List.of()).isEmpty()); + } + + @Test void cofhCountedNbtIngredientKeepsItsCountAndPredicate() { + ItemStack required = new ItemStack(Items.DIAMOND); + required.getOrCreateTag().putInt("grade", 9); + var input = new IngredientWithCount(StrictNBTIngredient.of(required), 3); + var converted = NativeThermalIngredients.items(List.of(input), 64L).get(0); + assertEquals(192L, converted.count()); + assertTrue(converted.ingredient().test(required)); + assertFalse(converted.ingredient().test(new ItemStack(Items.DIAMOND))); + } + + @Test void fluidAlternativesKeepTheirDifferentAmountsAndLiveTags() { + var json = JsonParser.parseString("[{\"fluid\":\"minecraft:water\",\"amount\":5}," + + "{\"fluid_tag\":\"forge:lava\",\"amount\":10}]"); + var alternatives = BatchRecipeAdapter.fluids(List.of(json), 64L); + assertEquals(2, alternatives.size()); + assertEquals(320L, alternatives.get(0).get(0).amount()); + assertEquals(640L, alternatives.get(1).get(0).amount()); + assertTrue(alternatives.get(1).get(0).ingredient().toJson().toString().contains("forge:lava")); + assertThrows(IllegalArgumentException.class, () -> BatchRecipeAdapter.fluids( + List.of(JsonParser.parseString("{\"fluid\":\"minecraft:water\",\"amount\":0}")), 1L)); + } + + @Test void nativeThermalInsolatorRetainsSeedsWhilePressRetainsOnlyItsMold() { + var seed = new InsolatorRecipe(ID, 1000, 0.0F, List.of(Ingredient.of(Items.WHEAT_SEEDS)), + List.of(cofh.lib.common.fluid.FluidIngredient.of(new FluidStack(Fluids.WATER, 500))), + List.of(new ItemStack(Items.WHEAT)), List.of(1.5F), List.of()); + var insolator = adapter(new ThermalRecipeAdapter<>(InsolatorRecipe.class, "machine_insolator", + "thermal_insolator_", AlloyFurnaceMode.INSOLATOR)); + var converted = insolator.convertAll(RecipeHolder.of(seed), null).get(0); + assertFalse(converted.inputs().get(0).consumed()); + assertEquals(1L, converted.inputs().get(0).count()); + assertEquals(500L, converted.inputFluids().get(0).amount()); + assertEquals(2, converted.outputs().get(0).getCount()); + var press = new PressRecipe(ID, 1000, 0.0F, + List.of(Ingredient.of(Items.IRON_INGOT), Ingredient.of(Items.DIAMOND)), List.of(), + List.of(new ItemStack(Items.GOLD_INGOT)), List.of(1.0F), List.of()); + var pressing = adapter(new ThermalRecipeAdapter<>(PressRecipe.class, "machine_press", + "thermal_press_", AlloyFurnaceMode.PRESS)).convertAll(RecipeHolder.of(press), null).get(0); + assertEquals(64L, pressing.inputs().get(0).count()); + assertTrue(pressing.inputs().get(0).consumed()); + assertEquals(1L, pressing.inputs().get(1).count()); + assertFalse(pressing.inputs().get(1).consumed()); + var smelter = new SmelterRecipe(ID, 100, 0.0F, List.of(Ingredient.of(Items.IRON_INGOT)), + List.of(), List.of(new ItemStack(Items.GOLD_INGOT)), List.of(1.0F), List.of()); + var smelting = adapter(new ThermalRecipeAdapter<>(SmelterRecipe.class, "machine_smelter", + "thermal_smelter_", AlloyFurnaceMode.NORMAL)).convertAll(RecipeHolder.of(smelter), null).get(0); + assertEquals(6400L, smelting.energy()); + assertEquals(64, smelting.outputs().get(0).getCount()); + } + + @Test void allThreeNativeBloodMagicFamiliesProduceTheirBatchAndPreserveOutputNbt() { + var output = new ItemStack(Items.DIAMOND); + output.getOrCreateTag().putString("kept", "foreign"); + var inputs = List.of(Ingredient.of(Items.IRON_INGOT)); + var altar = new RecipeBloodAltar(ID, inputs.get(0), output, 1, 1000, 10, 10); + var forge = new RecipeTartaricForge(ID, inputs, output, 1.0D, 1.0D); + var table = new RecipeAlchemyTable(ID, inputs, output, 100, 20, 1); + var a = adapter(new BloodMagicRecipeAdapter<>(RecipeBloodAltar.class, "altar", "bm_altar_")); + var f = adapter(new BloodMagicRecipeAdapter<>(RecipeTartaricForge.class, "soulforge", "bm_soulforge_")); + var t = adapter(new BloodMagicRecipeAdapter<>(RecipeAlchemyTable.class, "alchemytable", "bm_alchemytable_")); + for (var recipe : List.of(a.convertAll(RecipeHolder.of(altar), null).get(0), + f.convertAll(RecipeHolder.of(forge), null).get(0), t.convertAll(RecipeHolder.of(table), null).get(0))) { + assertEquals(64L, recipe.inputs().get(0).count()); + assertEquals(64, recipe.outputs().get(0).getCount()); + assertEquals(output.getTag(), recipe.outputs().get(0).getTag()); + } + } + + @Test void nativeEmbersUsesPublicRecipesAndKeepsAllRequiredMixingFluids() { + var mixing = mock(IMixingRecipe.class); + when(mixing.getDisplayInputFluids()).thenReturn(new ArrayList<>(List.of( + com.rekindled.embers.recipe.FluidIngredient.of(Fluids.WATER, 5), + com.rekindled.embers.recipe.FluidIngredient.of(Fluids.LAVA, 10)))); + when(mixing.getDisplayOutput()).thenReturn(new FluidStack(Fluids.WATER, 15)); + var recipe = adapter(new EmbersRecipeAdapter<>(IMixingRecipe.class, "mixer", "embers_mixing_", false)) + .convertAll(new RecipeHolder<>(ID, mixing), null).get(0); + assertEquals(2, recipe.inputFluids().size()); + assertEquals(320L, recipe.inputFluids().get(0).amount()); + assertEquals(640L, recipe.inputFluids().get(1).amount()); + assertEquals(960, recipe.outputFluids().get(0).getAmount()); + var melting = mock(IMeltingRecipe.class); + when(melting.getDisplayInput()).thenReturn(Ingredient.of(Items.IRON_INGOT)); + when(melting.getDisplayOutput()).thenReturn(new FluidStack(Fluids.WATER, 5)); + when(melting.getBonus()).thenReturn(new FluidStack(Fluids.LAVA, 1)); + assertTrue(adapter(new EmbersRecipeAdapter<>(IMeltingRecipe.class, "melter", "embers_melter_", false)) + .convertAll(new RecipeHolder<>(ID, melting), null).isEmpty()); + assertEquals(2, adapter(new EmbersRecipeAdapter<>(IMeltingRecipe.class, "geologic_separator", "embers_geo_separator_", true)) + .convertAll(new RecipeHolder<>(ID, melting), null).get(0).outputFluids().size()); + var alchemy = mock(IAlchemyRecipe.class); + when(alchemy.getCenterInput()).thenReturn(Ingredient.of(Items.IRON_INGOT)); + when(alchemy.getInputs()).thenReturn(List.of(Ingredient.of(Items.GOLD_INGOT))); + when(alchemy.getResultItem()).thenReturn(new ItemStack(Items.DIAMOND)); + assertEquals(2, adapter(new EmbersRecipeAdapter<>(IAlchemyRecipe.class, "alchemy_tablet", "embers_alchemy_", false)) + .convertAll(new RecipeHolder<>(ID, alchemy), null).get(0).inputs().size()); + var stamp = mock(IStampingRecipe.class); + when(stamp.getDisplayInput()).thenReturn(Ingredient.of(Items.IRON_INGOT)); + when(stamp.getDisplayInputFluid()).thenReturn(com.rekindled.embers.recipe.FluidIngredient.of(Fluids.WATER, 5)); + when(stamp.getResultItem()).thenReturn(new ItemStack(Items.DIAMOND)); + assertEquals(320L, adapter(new EmbersRecipeAdapter<>(IStampingRecipe.class, "stamper", "embers_stamping_", false)) + .convertAll(new RecipeHolder<>(ID, stamp), null).get(0).inputFluids().get(0).amount()); + } +} diff --git a/src/test/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeLookupTest.java b/src/test/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeLookupTest.java new file mode 100644 index 00000000..5aad8c73 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/content/recipe/adapters/exdeorum/HammerRecipeLookupTest.java @@ -0,0 +1,75 @@ +package com.sorrowmist.useless.content.recipe.adapters.exdeorum; + +import com.sorrowmist.useless.InventoryTestBootstrap; +import com.sorrowmist.useless.api.enums.AlloyFurnaceMode; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.CountedIngredient; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.Level; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class HammerRecipeLookupTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + private static RecipeHolder recipe(String id, Ingredient mold) { + var converted = new AdvancedAlloyFurnaceRecipe(new ResourceLocation("test", id), + List.of(new CountedIngredient(Ingredient.of(Items.COBBLESTONE), 2L)), + List.of(), List.of(new ItemStack(Items.GRAVEL)), List.of(), + 2000L, 200, Ingredient.EMPTY, 0, mold, AlloyFurnaceMode.NORMAL); + return RecipeHolder.of(new HammerSyntheticRecipe(converted)); + } + + @Test void generatedJeiRecipeIsAlsoAvailableToMachineLookup() { + var level = mock(Level.class); + var adapter = spy(new HammerRecipeAdapter()); + var normal = recipe("normal", Ingredient.of(Items.DIAMOND_PICKAXE)); + var compressed = recipe("compressed", Ingredient.of(Items.IRON_PICKAXE)); + doReturn(true).when(adapter).matchesMold(any()); + doReturn(List.of(normal, compressed)).when(adapter).getGeneratedRecipes(level); + var inputs = Map.of(Ingredient.of(Items.COBBLESTONE), 2L); + var mold = new ItemStack(Items.DIAMOND_PICKAXE); + assertEquals(List.of(normal), adapter.findMatchingRecipes(level, inputs, Map.of(), Map.of(), mold)); + assertEquals(List.of(compressed), adapter.findMatchingRecipes(level, inputs, Map.of(), Map.of(), + new ItemStack(Items.IRON_PICKAXE))); + } + + @Test void damageAndCustomNbtDoNotPreventOrdinaryMoldMatching() { + var level = mock(Level.class); + var adapter = spy(new HammerRecipeAdapter()); + var normal = recipe("normal", Ingredient.of(Items.DIAMOND_PICKAXE)); + doReturn(true).when(adapter).matchesMold(any()); + doReturn(List.of(normal)).when(adapter).getGeneratedRecipes(level); + var mold = new ItemStack(Items.DIAMOND_PICKAXE); + mold.setDamageValue(37); + mold.getOrCreateTag().putString("custom_marker", "keep"); + var inputs = Map.of(Ingredient.of(Items.COBBLESTONE), 2L); + assertEquals(List.of(normal), adapter.findMatchingRecipes(level, inputs, Map.of(), Map.of(), mold, + List.of(new ItemStack(Items.COBBLESTONE, 2)))); + assertEquals(37, mold.getDamageValue()); + assertEquals("keep", mold.getTag().getString("custom_marker")); + } + + @Test void missingMoldOrInsufficientBatchInputsCannotMatch() { + var level = mock(Level.class); + var adapter = spy(new HammerRecipeAdapter()); + doReturn(true).when(adapter).matchesMold(any()); + doReturn(List.of(recipe("normal", Ingredient.of(Items.DIAMOND_PICKAXE)))) + .when(adapter).getGeneratedRecipes(level); + assertTrue(adapter.findMatchingRecipes(level, Map.of(Ingredient.of(Items.COBBLESTONE), 1L), + Map.of(), Map.of(), new ItemStack(Items.DIAMOND_PICKAXE)).isEmpty()); + assertTrue(adapter.findMatchingRecipes(level, Map.of(Ingredient.of(Items.COBBLESTONE), 2L), + Map.of(), Map.of(), ItemStack.EMPTY).isEmpty()); + } +} diff --git a/src/test/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacketTest.java b/src/test/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacketTest.java new file mode 100644 index 00000000..732e2248 --- /dev/null +++ b/src/test/java/com/sorrowmist/useless/network/FurnaceMenuSyncPacketTest.java @@ -0,0 +1,49 @@ +package com.sorrowmist.useless.network; + +import com.sorrowmist.useless.InventoryTestBootstrap; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class FurnaceMenuSyncPacketTest { + @BeforeAll static void bootstrap() { InventoryTestBootstrap.initialize(); } + + @Test void actualPacketRoundTripPreservesIntCountsDataAndForeignNbt() { + ItemStack huge = new ItemStack(Items.DIAMOND, Integer.MAX_VALUE); + huge.getOrCreateTag().putLong("foreign", Long.MAX_VALUE); + var packet = new FurnaceMenuSyncPacket(12, 32768, List.of(huge, ItemStack.EMPTY), huge.copy(), + List.of(100000, Integer.MIN_VALUE, Integer.MAX_VALUE, -1)); + var buffer = new FriendlyByteBuf(Unpooled.buffer()); + try { + FurnaceMenuSyncPacket.STREAM_CODEC.encode(buffer, packet); + var decoded = FurnaceMenuSyncPacket.STREAM_CODEC.decode(buffer); + assertEquals(packet.containerId(), decoded.containerId()); + assertEquals(packet.stateId(), decoded.stateId()); + assertEquals(packet.data(), decoded.data()); + assertEquals(Integer.MAX_VALUE, decoded.slots().get(0).getCount()); + assertEquals(huge.getTag(), decoded.slots().get(0).getTag()); + assertEquals(Integer.MAX_VALUE, decoded.carried().getCount()); + assertTrue(decoded.slots().get(1).isEmpty()); + assertEquals(0, buffer.readableBytes()); + } finally { buffer.release(); } + } + + @Test void malformedSlotListLengthIsRejectedBeforeAllocatingEntries() { + var buffer = new FriendlyByteBuf(Unpooled.buffer()); + try { + buffer.writeVarInt(1); buffer.writeVarInt(1); buffer.writeVarInt(513); + assertThrows(IllegalArgumentException.class, () -> FurnaceMenuSyncPacket.STREAM_CODEC.decode(buffer)); + buffer.clear(); + var packet = new FurnaceMenuSyncPacket(1, 1, Collections.nCopies(513, ItemStack.EMPTY), ItemStack.EMPTY, List.of()); + assertThrows(IllegalArgumentException.class, () -> FurnaceMenuSyncPacket.STREAM_CODEC.encode(buffer, packet)); + } finally { buffer.release(); } + } +} diff --git a/src/test/python/test_ae_crafting_contract.py b/src/test/python/test_ae_crafting_contract.py index 9fcc8043..376926d8 100644 --- a/src/test/python/test_ae_crafting_contract.py +++ b/src/test/python/test_ae_crafting_contract.py @@ -51,6 +51,11 @@ def load(jar, name): class AeCraftingMixinContractTest(unittest.TestCase): + def test_pinned_library_matches_native_advancedae_bundle(self): + with zipfile.ZipFile(self.native['advancedae']) as archive: + self.assertEqual(archive.read('META-INF/jarjar/ae2addonlib-1.0.4-1.20.1.jar'), + (ROOT / 'build/compat-repository/ae2addonlib-1.0.4-1.20.1.jar').read_bytes()) + @classmethod def setUpClass(cls): version = re.search(r'(?m)^mod_version=(.+)$', (ROOT / 'gradle.properties').read_text(encoding='utf-8'))[1].strip() From f5aeefba56b4349bb4e8744b519074c6d2519a8c Mon Sep 17 00:00:00 2001 From: P1chC Date: Fri, 23 Oct 2026 17:22:52 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=81=A2=E5=A4=8D=20Forge=201.20.1=20?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E6=A0=87=E7=AD=BE=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/data/apotheosis/tags/items/pickaxe.json | 6 ++++++ .../data/blue_skies/tags/items/tools/pickaxes.json | 11 +++++++++++ src/main/resources/data/forge/tags/items/shears.json | 6 ++++++ .../resources/data/forge/tags/items/tools/axes.json | 5 +++++ .../data/forge/tags/items/tools/crowbars.json | 6 ++++++ .../resources/data/forge/tags/items/tools/files.json | 6 ++++++ .../data/forge/tags/items/tools/hammers.json | 6 ++++++ .../resources/data/forge/tags/items/tools/hoes.json | 11 +++++++++++ .../resources/data/forge/tags/items/tools/knives.json | 11 +++++++++++ .../data/forge/tags/items/tools/mallets.json | 6 ++++++ .../data/forge/tags/items/tools/mortars.json | 6 ++++++ .../data/forge/tags/items/tools/pickaxes.json | 11 +++++++++++ .../resources/data/forge/tags/items/tools/saws.json | 6 ++++++ .../data/forge/tags/items/tools/screwdrivers.json | 6 ++++++ .../resources/data/forge/tags/items/tools/swords.json | 5 +++++ .../data/forge/tags/items/tools/wire_cutters.json | 6 ++++++ .../resources/data/forge/tags/items/tools/wrench.json | 6 ++++++ .../data/forge/tags/items/tools/wrenches.json | 6 ++++++ .../resources/data/forge/tags/items/wrenches.json | 6 ++++++ .../gtceu/tags/items/tools/crafting_crowbars.json | 6 ++++++ .../data/gtceu/tags/items/tools/crafting_files.json | 6 ++++++ .../data/gtceu/tags/items/tools/crafting_hammers.json | 6 ++++++ .../data/gtceu/tags/items/tools/crafting_knives.json | 6 ++++++ .../data/gtceu/tags/items/tools/crafting_mallets.json | 6 ++++++ .../data/gtceu/tags/items/tools/crafting_mortars.json | 6 ++++++ .../data/gtceu/tags/items/tools/crafting_saws.json | 6 ++++++ .../gtceu/tags/items/tools/crafting_screwdrivers.json | 6 ++++++ .../gtceu/tags/items/tools/crafting_wire_cutters.json | 6 ++++++ .../gtceu/tags/items/tools/crafting_wrenches.json | 6 ++++++ .../resources/data/minecraft/tags/items/shears.json | 6 ++++++ .../resources/data/minecraft/tags/items/tools.json | 6 ++++++ 31 files changed, 204 insertions(+) create mode 100644 src/main/resources/data/apotheosis/tags/items/pickaxe.json create mode 100644 src/main/resources/data/blue_skies/tags/items/tools/pickaxes.json create mode 100644 src/main/resources/data/forge/tags/items/shears.json create mode 100644 src/main/resources/data/forge/tags/items/tools/axes.json create mode 100644 src/main/resources/data/forge/tags/items/tools/crowbars.json create mode 100644 src/main/resources/data/forge/tags/items/tools/files.json create mode 100644 src/main/resources/data/forge/tags/items/tools/hammers.json create mode 100644 src/main/resources/data/forge/tags/items/tools/hoes.json create mode 100644 src/main/resources/data/forge/tags/items/tools/knives.json create mode 100644 src/main/resources/data/forge/tags/items/tools/mallets.json create mode 100644 src/main/resources/data/forge/tags/items/tools/mortars.json create mode 100644 src/main/resources/data/forge/tags/items/tools/pickaxes.json create mode 100644 src/main/resources/data/forge/tags/items/tools/saws.json create mode 100644 src/main/resources/data/forge/tags/items/tools/screwdrivers.json create mode 100644 src/main/resources/data/forge/tags/items/tools/swords.json create mode 100644 src/main/resources/data/forge/tags/items/tools/wire_cutters.json create mode 100644 src/main/resources/data/forge/tags/items/tools/wrench.json create mode 100644 src/main/resources/data/forge/tags/items/tools/wrenches.json create mode 100644 src/main/resources/data/forge/tags/items/wrenches.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_crowbars.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_files.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_hammers.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_knives.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_mallets.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_mortars.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_saws.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_screwdrivers.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_wire_cutters.json create mode 100644 src/main/resources/data/gtceu/tags/items/tools/crafting_wrenches.json create mode 100644 src/main/resources/data/minecraft/tags/items/shears.json create mode 100644 src/main/resources/data/minecraft/tags/items/tools.json diff --git a/src/main/resources/data/apotheosis/tags/items/pickaxe.json b/src/main/resources/data/apotheosis/tags/items/pickaxe.json new file mode 100644 index 00000000..5b66bd09 --- /dev/null +++ b/src/main/resources/data/apotheosis/tags/items/pickaxe.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/blue_skies/tags/items/tools/pickaxes.json b/src/main/resources/data/blue_skies/tags/items/tools/pickaxes.json new file mode 100644 index 00000000..69f35346 --- /dev/null +++ b/src/main/resources/data/blue_skies/tags/items/tools/pickaxes.json @@ -0,0 +1,11 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item", + "useless_mod:endless_beaf_wrench", + "useless_mod:endless_beaf_screwdriver", + "useless_mod:endless_beaf_mallet", + "useless_mod:endless_beaf_crowbar", + "useless_mod:endless_beaf_hammer" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/forge/tags/items/shears.json b/src/main/resources/data/forge/tags/items/shears.json new file mode 100644 index 00000000..ff7b7ef5 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/shears.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/shear" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/axes.json b/src/main/resources/data/forge/tags/items/tools/axes.json new file mode 100644 index 00000000..a22514f6 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/axes.json @@ -0,0 +1,5 @@ +{ + "replace": false, + "values": [ + ] +} \ No newline at end of file diff --git a/src/main/resources/data/forge/tags/items/tools/crowbars.json b/src/main/resources/data/forge/tags/items/tools/crowbars.json new file mode 100644 index 00000000..6f655749 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/crowbars.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/crowbar" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/files.json b/src/main/resources/data/forge/tags/items/tools/files.json new file mode 100644 index 00000000..7368a26b --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/files.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/file" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/hammers.json b/src/main/resources/data/forge/tags/items/tools/hammers.json new file mode 100644 index 00000000..94c55545 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/hammers.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/hammer" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/hoes.json b/src/main/resources/data/forge/tags/items/tools/hoes.json new file mode 100644 index 00000000..69f35346 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/hoes.json @@ -0,0 +1,11 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item", + "useless_mod:endless_beaf_wrench", + "useless_mod:endless_beaf_screwdriver", + "useless_mod:endless_beaf_mallet", + "useless_mod:endless_beaf_crowbar", + "useless_mod:endless_beaf_hammer" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/forge/tags/items/tools/knives.json b/src/main/resources/data/forge/tags/items/tools/knives.json new file mode 100644 index 00000000..69f35346 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/knives.json @@ -0,0 +1,11 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item", + "useless_mod:endless_beaf_wrench", + "useless_mod:endless_beaf_screwdriver", + "useless_mod:endless_beaf_mallet", + "useless_mod:endless_beaf_crowbar", + "useless_mod:endless_beaf_hammer" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/forge/tags/items/tools/mallets.json b/src/main/resources/data/forge/tags/items/tools/mallets.json new file mode 100644 index 00000000..2b86694c --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/mallets.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/mallet" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/mortars.json b/src/main/resources/data/forge/tags/items/tools/mortars.json new file mode 100644 index 00000000..a1ca1d45 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/mortars.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/mortar" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/pickaxes.json b/src/main/resources/data/forge/tags/items/tools/pickaxes.json new file mode 100644 index 00000000..69f35346 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/pickaxes.json @@ -0,0 +1,11 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item", + "useless_mod:endless_beaf_wrench", + "useless_mod:endless_beaf_screwdriver", + "useless_mod:endless_beaf_mallet", + "useless_mod:endless_beaf_crowbar", + "useless_mod:endless_beaf_hammer" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/forge/tags/items/tools/saws.json b/src/main/resources/data/forge/tags/items/tools/saws.json new file mode 100644 index 00000000..afc57885 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/saws.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/saw" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/screwdrivers.json b/src/main/resources/data/forge/tags/items/tools/screwdrivers.json new file mode 100644 index 00000000..c7643deb --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/screwdrivers.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/screwdriver" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/swords.json b/src/main/resources/data/forge/tags/items/tools/swords.json new file mode 100644 index 00000000..a22514f6 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/swords.json @@ -0,0 +1,5 @@ +{ + "replace": false, + "values": [ + ] +} \ No newline at end of file diff --git a/src/main/resources/data/forge/tags/items/tools/wire_cutters.json b/src/main/resources/data/forge/tags/items/tools/wire_cutters.json new file mode 100644 index 00000000..5e3fc74d --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/wire_cutters.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/wire_cutter" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/wrench.json b/src/main/resources/data/forge/tags/items/tools/wrench.json new file mode 100644 index 00000000..ee83dd9f --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/wrench.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/wrench" + ] +} diff --git a/src/main/resources/data/forge/tags/items/tools/wrenches.json b/src/main/resources/data/forge/tags/items/tools/wrenches.json new file mode 100644 index 00000000..ee83dd9f --- /dev/null +++ b/src/main/resources/data/forge/tags/items/tools/wrenches.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/wrench" + ] +} diff --git a/src/main/resources/data/forge/tags/items/wrenches.json b/src/main/resources/data/forge/tags/items/wrenches.json new file mode 100644 index 00000000..6d917266 --- /dev/null +++ b/src/main/resources/data/forge/tags/items/wrenches.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#forge:tools/wrench" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_crowbars.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_crowbars.json new file mode 100644 index 00000000..5f97606f --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_crowbars.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/crowbar" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_files.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_files.json new file mode 100644 index 00000000..03bb94ce --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_files.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/file" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_hammers.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_hammers.json new file mode 100644 index 00000000..04a9d055 --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_hammers.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/hammer" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_knives.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_knives.json new file mode 100644 index 00000000..5b66bd09 --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_knives.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_mallets.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_mallets.json new file mode 100644 index 00000000..2edb592e --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_mallets.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/mallet" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_mortars.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_mortars.json new file mode 100644 index 00000000..ea988111 --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_mortars.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/mortar" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_saws.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_saws.json new file mode 100644 index 00000000..5776687b --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_saws.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/saw" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_screwdrivers.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_screwdrivers.json new file mode 100644 index 00000000..d0859aea --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_screwdrivers.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/screwdriver" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_wire_cutters.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_wire_cutters.json new file mode 100644 index 00000000..aa61017e --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_wire_cutters.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#gtceu:crafting_tools/wire_cutter" + ] +} diff --git a/src/main/resources/data/gtceu/tags/items/tools/crafting_wrenches.json b/src/main/resources/data/gtceu/tags/items/tools/crafting_wrenches.json new file mode 100644 index 00000000..5b66bd09 --- /dev/null +++ b/src/main/resources/data/gtceu/tags/items/tools/crafting_wrenches.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "useless_mod:endless_beaf_item" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/minecraft/tags/items/shears.json b/src/main/resources/data/minecraft/tags/items/shears.json new file mode 100644 index 00000000..ff7b7ef5 --- /dev/null +++ b/src/main/resources/data/minecraft/tags/items/shears.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/shear" + ] +} diff --git a/src/main/resources/data/minecraft/tags/items/tools.json b/src/main/resources/data/minecraft/tags/items/tools.json new file mode 100644 index 00000000..71fbbb9b --- /dev/null +++ b/src/main/resources/data/minecraft/tags/items/tools.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#c:tools/tools" + ] +} From a41a6714cb882e6e85474886dc03f78e3fbe0bfd Mon Sep 17 00:00:00 2001 From: P1chC Date: Fri, 23 Oct 2026 17:23:18 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=85=8D=E6=96=B9=E5=B9=B6=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=E9=81=97=E6=BC=8F=E5=9B=9E=E5=BD=92=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/OmissionRegressionGameTests.java | 172 ++++++++++++++++++ .../smelter/ae2/advacedae/quantum_alloy.json | 61 +++++++ .../ae2/advacedae/quantum_alloy_plate.json | 61 +++++++ .../ae2/advacedae/quantum_infused_dust.json | 45 +++++ .../advacedae/quantum_infusion_source.json | 46 +++++ .../ae2/advacedae/quantum_processor.json | 55 ++++++ .../ae2/advacedae/shattered_singularity.json | 61 +++++++ .../smelter/ae2/appflux/energy_processor.json | 55 ++++++ .../smelter/ae2/appflux/redstone_crystal.json | 55 ++++++ .../ae2/common/calculation_processor.json | 55 ++++++ .../ae2/common/certus_quartz_dust.json | 48 +++++ .../common/charged_certus_quartz_crystal.json | 45 +++++ .../smelter/ae2/common/ender_dust.json | 48 +++++ .../ae2/common/engineering_processor.json | 55 ++++++ .../smelter/ae2/common/fluix_crystal.json | 61 +++++++ .../smelter/ae2/common/fluix_dust.json | 43 +++++ .../smelter/ae2/common/logic_processor.json | 55 ++++++ .../machine/smelter/ae2/common/sky_dust.json | 45 +++++ .../ae2/megacells/accumulation_processor.json | 55 ++++++ .../ae2/megacells/sky_steel_ingot.json | 61 +++++++ .../ae2/omnicells/complex_link_processor.json | 55 ++++++ .../smelter/ae2/omnicells/ender_ingot.json | 60 ++++++ .../multidimensional_expansion_processor.json | 55 ++++++ .../ae2/omnicells/omni_link_processor.json | 55 ++++++ .../smelter/thermal/gear/bronze_gear.json | 43 +++++ .../smelter/thermal/gear/constantan_gear.json | 43 +++++ .../smelter/thermal/gear/copper_gear.json | 43 +++++ .../smelter/thermal/gear/diamond_gear.json | 43 +++++ .../smelter/thermal/gear/electrum_gear.json | 43 +++++ .../smelter/thermal/gear/emerald_gear.json | 43 +++++ .../smelter/thermal/gear/enderium_gear.json | 43 +++++ .../smelter/thermal/gear/gold_gear.json | 43 +++++ .../smelter/thermal/gear/invar_gear.json | 43 +++++ .../smelter/thermal/gear/iron_gear.json | 43 +++++ .../smelter/thermal/gear/lapis_gear.json | 43 +++++ .../smelter/thermal/gear/lead_gear.json | 43 +++++ .../smelter/thermal/gear/lumium_gear.json | 43 +++++ .../smelter/thermal/gear/netherite_gear.json | 43 +++++ .../smelter/thermal/gear/nickel_gear.json | 43 +++++ .../smelter/thermal/gear/quartz_gear.json | 43 +++++ .../smelter/thermal/gear/ruby_gear.json | 43 +++++ .../smelter/thermal/gear/sapphire_gear.json | 43 +++++ .../smelter/thermal/gear/signalum_gear.json | 43 +++++ .../smelter/thermal/gear/silver_gear.json | 43 +++++ .../smelter/thermal/gear/tin_gear.json | 43 +++++ .../smelter/thermal/plate/bronze_plate.json | 43 +++++ .../thermal/plate/constantan_plate.json | 43 +++++ .../smelter/thermal/plate/copper_plate.json | 43 +++++ .../smelter/thermal/plate/electrum_plate.json | 43 +++++ .../smelter/thermal/plate/enderium_plate.json | 43 +++++ .../smelter/thermal/plate/gold_plate.json | 43 +++++ .../smelter/thermal/plate/invar_plate.json | 43 +++++ .../smelter/thermal/plate/iron_plate.json | 43 +++++ .../smelter/thermal/plate/lead_plate.json | 43 +++++ .../smelter/thermal/plate/lumium_plate.json | 43 +++++ .../thermal/plate/netherite_plate.json | 43 +++++ .../smelter/thermal/plate/nickel_plate.json | 43 +++++ .../smelter/thermal/plate/signalum_plate.json | 43 +++++ .../smelter/thermal/plate/silver_plate.json | 43 +++++ .../smelter/thermal/plate/tin_plate.json | 43 +++++ 60 files changed, 2955 insertions(+) create mode 100644 src/compatGameTest/java/com/sorrowmist/useless/tests/OmissionRegressionGameTests.java create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infused_dust.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infusion_source.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/shattered_singularity.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/energy_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/redstone_crystal.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/calculation_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/certus_quartz_dust.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/charged_certus_quartz_crystal.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/ender_dust.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/engineering_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_crystal.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_dust.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/logic_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/sky_dust.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/accumulation_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/sky_steel_ingot.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/complex_link_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/ender_ingot.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/multidimensional_expansion_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/omni_link_processor.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/bronze_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/constantan_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/copper_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/diamond_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/electrum_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/emerald_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/enderium_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/gold_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/invar_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/iron_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lapis_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lead_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lumium_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/netherite_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/nickel_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/quartz_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/ruby_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/sapphire_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/signalum_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/silver_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/tin_gear.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/bronze_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/constantan_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/copper_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/electrum_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/enderium_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/gold_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/invar_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/iron_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lead_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lumium_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/netherite_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/nickel_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/signalum_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/silver_plate.json create mode 100644 src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/tin_plate.json diff --git a/src/compatGameTest/java/com/sorrowmist/useless/tests/OmissionRegressionGameTests.java b/src/compatGameTest/java/com/sorrowmist/useless/tests/OmissionRegressionGameTests.java new file mode 100644 index 00000000..44ce6a27 --- /dev/null +++ b/src/compatGameTest/java/com/sorrowmist/useless/tests/OmissionRegressionGameTests.java @@ -0,0 +1,172 @@ +package com.sorrowmist.useless.tests; + +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.compat.forge1201.component.ComponentAccess; +import com.sorrowmist.useless.compat.forge1201.recipe.RecipeHolder; +import com.sorrowmist.useless.content.items.BeefToolVariants; +import com.sorrowmist.useless.content.recipe.AdvancedAlloyFurnaceRecipe; +import com.sorrowmist.useless.content.recipe.AlloyFurnaceRecipeManager; +import com.sorrowmist.useless.content.recipe.adapters.avaritia.*; +import com.sorrowmist.useless.core.component.UComponents; +import com.sorrowmist.useless.init.ModItems; +import committee.nova.mods.avaritia.common.crafting.recipe.ExtremeSmithingRecipe; +import committee.nova.mods.avaritia.init.registry.ModRecipeTypes; +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraftforge.gametest.GameTestHolder; +import net.minecraftforge.gametest.PrefixGameTestTemplate; + +import java.util.List; + +@GameTestHolder(UselessMod.MODID) +@PrefixGameTestTemplate(false) +public final class OmissionRegressionGameTests { + @GameTest(template = "furnace_empty") + public static void thermalUpgradesAndLegacyShortcutsLoad(GameTestHelper helper) { + var manager = helper.getLevel().getRecipeManager(); + for (int tier = 1; tier <= 9; tier++) { + helper.assertTrue(manager.byKey(UselessMod.id("useless_integral_component_tier_" + tier)).isPresent(), + "Missing integral component recipe " + tier); + } + for (int tier = 1; tier <= 3; tier++) { + helper.assertTrue(manager.byKey(UselessMod.id("augment_parallel_" + tier)).isPresent(), + "Missing parallel augment recipe " + tier); + } + for (String id : List.of("machine/smelter/thermal/plate/iron_plate", "machine/smelter/ae2/common/fluix_crystal")) { + var recipe = (AdvancedAlloyFurnaceRecipe) manager.byKey(UselessMod.id(id)).orElseThrow(); + helper.assertTrue(recipe.outputs().get(0).getCount() == 64 && recipe.catalystUses() == 1, + "Legacy batch and catalyst costs must survive the codec conversion"); + if (id.endsWith("fluix_crystal")) { + helper.assertTrue(recipe.inputFluids().get(0).amount() == 16000, "Fluix must consume 16 buckets"); + } else { + helper.assertTrue(recipe.energy() == 64000, "Iron plate energy must remain 64000"); + } + } + int checked = 0; + var resources = helper.getLevel().getServer().getResourceManager().listResources("recipes/machine/smelter", + id -> id.getNamespace().equals(UselessMod.MODID) + && (id.getPath().startsWith("recipes/machine/smelter/thermal/") + || id.getPath().startsWith("recipes/machine/smelter/ae2/"))); + for (var entry : resources.entrySet()) { + try (var reader = entry.getValue().openAsReader()) { + var json = com.google.gson.JsonParser.parseReader(reader).getAsJsonObject(); + var conditional = json.getAsJsonArray("recipes").get(0).getAsJsonObject(); + boolean enabled = true; + for (var element : conditional.getAsJsonArray("conditions")) { + var condition = element.getAsJsonObject(); + if (condition.get("type").getAsString().equals("forge:mod_loaded")) { + enabled &= net.minecraftforge.fml.ModList.get().isLoaded(condition.get("modid").getAsString()); + } else if (condition.get("type").getAsString().equals("forge:item_exists")) { + enabled &= net.minecraftforge.registries.ForgeRegistries.ITEMS.containsKey( + new ResourceLocation(condition.get("item").getAsString())); + } else { + throw new AssertionError("Unexpected legacy recipe condition: " + condition); + } + } + var path = entry.getKey().getPath(); + var id = UselessMod.id(path.substring("recipes/".length(), path.length() - ".json".length())); + var loaded = manager.byKey(id); + helper.assertTrue(loaded.isPresent() == enabled, "Conditional loading mismatch: " + id); + if (enabled) { + var recipe = (AdvancedAlloyFurnaceRecipe) loaded.orElseThrow(); + helper.assertTrue(!recipe.outputs().isEmpty() || !recipe.outputFluids().isEmpty(), "Empty output: " + id); + helper.assertTrue(recipe.catalystUses() == 1 && !recipe.mold().isEmpty(), "Lost mold/catalyst: " + id); + } + checked++; + } catch (java.io.IOException exception) { + throw new AssertionError(exception); + } + } + helper.assertTrue(checked == 59, "All 59 legacy shortcuts must be checked, got " + checked); + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void nativeAvaritiaRecipesRegisterAndConvert(GameTestHelper helper) { + var adapters = AlloyFurnaceRecipeManager.getInstance().getRegisteredAdapters(); + for (Class type : List.of(ReAvaritiaTableRecipeAdapter.class, ReAvaritiaExtremeSmithingRecipeAdapter.class, + ReAvaritiaCompressorRecipeAdapter.class, ReAvaritiaCollectorRecipeAdapter.class)) { + helper.assertTrue(adapters.stream().anyMatch(type::isInstance), "Missing native adapter " + type.getSimpleName()); + } + var tables = helper.getLevel().getRecipeManager().getAllRecipesFor(ModRecipeTypes.CRAFTING_TABLE_RECIPE.get()); + int converted = 0; + var tableAdapter = new ReAvaritiaTableRecipeAdapter(); + for (var recipe : tables) { + if (!tableAdapter.convertAll(RecipeHolder.of(recipe), helper.getLevel()).isEmpty()) converted++; + } + helper.assertTrue(converted > 0, "Native tier table recipes must actually convert"); + var compressor = new ReAvaritiaCompressorRecipeAdapter(); + var compressions = helper.getLevel().getRecipeManager().getAllRecipesFor(ModRecipeTypes.COMPRESSOR_RECIPE.get()); + helper.assertTrue(!compressions.isEmpty(), "Native compressor recipes must load"); + helper.assertTrue(compressor.convertAll(RecipeHolder.of(compressions.get(0)), helper.getLevel()).size() == 4, + "All four native compressor tiers must convert"); + helper.assertTrue(new ReAvaritiaCollectorRecipeAdapter().getGeneratedRecipes(helper.getLevel()).size() == 4, + "All four native collector tiers must generate recipes"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void tierTableKeepsDifferentNbtAndCraftingRemainders(GameTestHelper helper) { + var first = new ItemStack(Items.PAPER); + first.getOrCreateTag().putString("singularity", "first"); + var second = first.copy(); + second.getOrCreateTag().putString("singularity", "second"); + var slots = net.minecraft.core.NonNullList.create(); + slots.add(net.minecraftforge.common.crafting.StrictNBTIngredient.of(first)); + slots.add(net.minecraftforge.common.crafting.StrictNBTIngredient.of(second)); + slots.add(net.minecraftforge.common.crafting.StrictNBTIngredient.of(first)); + slots.add(Ingredient.of(Items.WATER_BUCKET)); + var source = new committee.nova.mods.avaritia.common.crafting.recipe.ShapedTableCraftingRecipe( + UselessMod.id("test_table_nbt"), 2, 2, slots, new ItemStack(Items.DIAMOND), 1, false); + var converted = new ReAvaritiaTableRecipeAdapter().convertAll(RecipeHolder.of(source), helper.getLevel()); + helper.assertTrue(converted.size() == 1, "Native NBT table recipe must convert"); + var recipe = converted.get(0); + helper.assertTrue(recipe.inputs().size() == 3, "Different NBT must not collapse into one requirement"); + helper.assertTrue(recipe.inputs().stream().anyMatch(i -> i.count() == 2 && i.ingredient().test(first) + && !i.ingredient().test(second)), "Repeated exact stacks must aggregate without losing NBT"); + helper.assertTrue(recipe.outputs().stream().anyMatch(s -> s.is(Items.BUCKET)), "Native bucket remainder must return"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void extremeSmithingPreservesBaseNbtAndRequiresThreeAdditions(GameTestHelper helper) { + var source = new ExtremeSmithingRecipe(UselessMod.id("test_smithing"), Ingredient.of(Items.PAPER), + Ingredient.of(Items.IRON_SWORD), Ingredient.of(Items.DIAMOND), new ItemStack(Items.DIAMOND_SWORD)); + var base = new ItemStack(Items.IRON_SWORD); + base.getOrCreateTag().putString("owner", "retained"); + var adapter = new ReAvaritiaExtremeSmithingRecipeAdapter(); + var inputs = List.of(new ItemStack(Items.PAPER), base, new ItemStack(Items.DIAMOND, 3)); + var result = adapter.convertAll(RecipeHolder.of(source), helper.getLevel(), inputs); + helper.assertTrue(result.size() == 1, "Five native slots must convert"); + helper.assertTrue(result.get(0).inputs().size() == 5, "Smithing must require all three addition slots"); + helper.assertTrue("retained".equals(result.get(0).outputs().get(0).getTag().getString("owner")), + "Native assemble must copy base NBT"); + helper.assertTrue(adapter.convertAll(RecipeHolder.of(source), helper.getLevel(), + List.of(new ItemStack(Items.PAPER), base, new ItemStack(Items.DIAMOND, 2))).isEmpty(), + "Two additions cannot satisfy three slots"); + helper.assertTrue(base.getCount() == 1 && inputs.get(2).getCount() == 3, "Conversion cannot mutate inputs"); + helper.succeed(); + } + + @GameTest(template = "furnace_empty") + public static void forgeWrenchAliasesFollowBothToolsSwitches(GameTestHelper helper) { + var beef = new ItemStack(ModItems.ENDLESS_BEAF_ITEM.get()); + var disabled = BeefToolVariants.withWrenchTag(beef, false); + var staff = new ItemStack(com.sorrowmist.useless.stretcher.init.ModItems.WONDROUS_STAFF.get()); + for (String id : List.of("forge:tools/wrench", "forge:tools/wrenches", "forge:wrenches", "mekanism:configurators")) { + var tag = TagKey.create(Registries.ITEM, new ResourceLocation(id)); + helper.assertTrue(beef.is(tag) && !disabled.is(tag), "Core wrench aliases must honor G toggle: " + id); + ComponentAccess.set(staff, UComponents.WrenchTagEnabledComponent.get(), true); + helper.assertTrue(staff.is(tag), "Enabled staff must match " + id); + ComponentAccess.set(staff, UComponents.WrenchTagEnabledComponent.get(), false); + helper.assertTrue(!staff.is(tag), "Disabled staff must reject " + id); + } + helper.succeed(); + } +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy.json new file mode 100644 index 00000000..95c8de8a --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy.json @@ -0,0 +1,61 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "advanced_ae" + }, + { + "type": "forge:item_exists", + "item": "advanced_ae:quantum_alloy" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/copper" + }, + "count": 256 + }, + { + "ingredient": { + "item": "advanced_ae:shattered_singularity" + }, + "count": 256 + }, + { + "ingredient": { + "item": "ae2:singularity" + }, + "count": 256 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "advanced_ae:quantum_alloy", + "count": 64 + } + ], + "input_fluids": [ + { + "fluid": "advanced_ae:quantum_infusion_source", + "amount": 64000 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy_plate.json new file mode 100644 index 00000000..85d452dc --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_alloy_plate.json @@ -0,0 +1,61 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "advanced_ae" + }, + { + "type": "forge:item_exists", + "item": "advanced_ae:quantum_alloy_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "advanced_ae:quantum_alloy" + }, + "count": 512 + }, + { + "ingredient": { + "tag": "forge:ingots/netherite" + }, + "count": 128 + }, + { + "ingredient": { + "tag": "forge:nether_stars" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "advanced_ae:quantum_alloy_plate", + "count": 64 + } + ], + "input_fluids": [ + { + "fluid": "advanced_ae:quantum_infusion_source", + "amount": 64000 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infused_dust.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infused_dust.json new file mode 100644 index 00000000..63a8d631 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infused_dust.json @@ -0,0 +1,45 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "advanced_ae" + }, + { + "type": "forge:item_exists", + "item": "advanced_ae:quantum_infused_dust" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": [ + { + "item": "advanced_ae:shattered_singularity" + } + ], + "count": 64 + } + ], + "mold": { + "item": "ae2:certus_quartz_cutting_knife" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "advanced_ae:quantum_infused_dust", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infusion_source.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infusion_source.json new file mode 100644 index 00000000..62dd98dc --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_infusion_source.json @@ -0,0 +1,46 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "advanced_ae" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "advanced_ae:quantum_infused_dust" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [], + "input_fluids": [ + { + "fluid": "minecraft:water", + "amount": 64000 + } + ], + "output_fluids": [ + { + "fluid": "advanced_ae:quantum_infusion_source", + "amount": 64000 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_processor.json new file mode 100644 index 00000000..e8fd5d9e --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/quantum_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "advanced_ae" + }, + { + "type": "forge:item_exists", + "item": "advanced_ae:quantum_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "advanced_ae:quantum_alloy" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "advanced_ae:quantum_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/shattered_singularity.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/shattered_singularity.json new file mode 100644 index 00000000..7def18dd --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/advacedae/shattered_singularity.json @@ -0,0 +1,61 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "advanced_ae" + }, + { + "type": "forge:item_exists", + "item": "advanced_ae:shattered_singularity" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "ae2:singularity" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/ender_pearl" + }, + "count": 128 + }, + { + "ingredient": { + "item": "ae2:sky_dust" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "advanced_ae:shattered_singularity", + "count": 64 + } + ], + "input_fluids": [ + { + "fluid": "minecraft:lava", + "amount": 64000 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/energy_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/energy_processor.json new file mode 100644 index 00000000..c1761d0d --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/energy_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "appflux" + }, + { + "type": "forge:item_exists", + "item": "appflux:energy_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "appflux:redstone_crystal" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "appflux:energy_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/redstone_crystal.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/redstone_crystal.json new file mode 100644 index 00000000..d4dc936f --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/appflux/redstone_crystal.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "appflux" + }, + { + "type": "forge:item_exists", + "item": "appflux:redstone_crystal" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:storage_blocks/redstone" + }, + "count": 16 + }, + { + "ingredient": { + "tag": "forge:gems/fluix" + }, + "count": 16 + }, + { + "ingredient": { + "tag": "forge:dusts/glowstone" + }, + "count": 16 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "appflux:redstone_crystal", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/calculation_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/calculation_processor.json new file mode 100644 index 00000000..18fbc68b --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/calculation_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:calculation_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "ae2:all_certus_quartz" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:calculation_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/certus_quartz_dust.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/certus_quartz_dust.json new file mode 100644 index 00000000..8e60edd4 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/certus_quartz_dust.json @@ -0,0 +1,48 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:certus_quartz_dust" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": [ + { + "tag": "forge:gems/certus_quartz" + }, + { + "tag": "ae2:all_certus_quartz" + } + ], + "count": 64 + } + ], + "mold": { + "item": "ae2:certus_quartz_cutting_knife" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:certus_quartz_dust", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/charged_certus_quartz_crystal.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/charged_certus_quartz_crystal.json new file mode 100644 index 00000000..092446b4 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/charged_certus_quartz_crystal.json @@ -0,0 +1,45 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:charged_certus_quartz_crystal" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": [ + { + "item": "ae2:certus_quartz_crystal" + } + ], + "count": 64 + } + ], + "mold": { + "item": "ae2:charger" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:charged_certus_quartz_crystal", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/ender_dust.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/ender_dust.json new file mode 100644 index 00000000..28c033d6 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/ender_dust.json @@ -0,0 +1,48 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:ender_dust" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": [ + { + "tag": "forge:gems/ender_pearl" + }, + { + "tag": "forge:ender_pearls" + } + ], + "count": 64 + } + ], + "mold": { + "item": "ae2:certus_quartz_cutting_knife" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:ender_dust", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/engineering_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/engineering_processor.json new file mode 100644 index 00000000..17275414 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/engineering_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:engineering_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/diamond" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:engineering_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_crystal.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_crystal.json new file mode 100644 index 00000000..a21acdd1 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_crystal.json @@ -0,0 +1,61 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:fluix_crystal" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "ae2:charged_certus_quartz_crystal" + }, + "count": 16 + }, + { + "ingredient": { + "tag": "forge:gems/quartz" + }, + "count": 16 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 16 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:fluix_crystal", + "count": 64 + } + ], + "input_fluids": [ + { + "fluid": "minecraft:water", + "amount": 16000 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_dust.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_dust.json new file mode 100644 index 00000000..e1b40afd --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/fluix_dust.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:fluix_dust" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/fluix" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:certus_quartz_cutting_knife" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:fluix_dust", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/logic_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/logic_processor.json new file mode 100644 index 00000000..3512266c --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/logic_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:logic_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/gold" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:logic_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/sky_dust.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/sky_dust.json new file mode 100644 index 00000000..3085857c --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/common/sky_dust.json @@ -0,0 +1,45 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2" + }, + { + "type": "forge:item_exists", + "item": "ae2:sky_dust" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": [ + { + "item": "ae2:sky_stone_block" + } + ], + "count": 64 + } + ], + "mold": { + "item": "ae2:certus_quartz_cutting_knife" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2:sky_dust", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/accumulation_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/accumulation_processor.json new file mode 100644 index 00000000..0e2858ed --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/accumulation_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "megacells" + }, + { + "type": "forge:item_exists", + "item": "megacells:accumulation_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "megacells:sky_steel_ingot" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 20, + "outputs": [ + { + "item": "megacells:accumulation_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/sky_steel_ingot.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/sky_steel_ingot.json new file mode 100644 index 00000000..c58791d6 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/megacells/sky_steel_ingot.json @@ -0,0 +1,61 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "megacells" + }, + { + "type": "forge:item_exists", + "item": "megacells:sky_steel_ingot" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "ae2:charged_certus_quartz_crystal" + }, + "count": 16 + }, + { + "ingredient": { + "tag": "forge:ingots/iron" + }, + "count": 16 + }, + { + "ingredient": { + "item": "ae2:sky_stone_block" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "megacells:sky_steel_ingot", + "count": 64 + } + ], + "input_fluids": [ + { + "fluid": "minecraft:lava", + "amount": 500 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/complex_link_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/complex_link_processor.json new file mode 100644 index 00000000..4412da39 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/complex_link_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2omnicells" + }, + { + "type": "forge:item_exists", + "item": "ae2omnicells:complex_link_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "minecraft:netherite_scrap" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2omnicells:complex_link_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/ender_ingot.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/ender_ingot.json new file mode 100644 index 00000000..e8779f45 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/ender_ingot.json @@ -0,0 +1,60 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2omnicells" + }, + { + "type": "forge:item_exists", + "item": "ae2omnicells:ender_ingot" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:dusts/ender_pearl" + }, + "count": 64 + }, + { + "ingredient": [ + { + "tag": "ae2:all_quartz_dust" + }, + { + "tag": "forge:dusts/certus_quartz" + } + ], + "count": 64 + }, + { + "ingredient": { + "tag": "forge:ingots/iron" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2omnicells:ender_ingot", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/multidimensional_expansion_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/multidimensional_expansion_processor.json new file mode 100644 index 00000000..fd85b4e5 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/multidimensional_expansion_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2omnicells" + }, + { + "type": "forge:item_exists", + "item": "ae2omnicells:multidimensional_expansion_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "ae2:singularity" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2omnicells:multidimensional_expansion_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/omni_link_processor.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/omni_link_processor.json new file mode 100644 index 00000000..d5e97035 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/ae2/omnicells/omni_link_processor.json @@ -0,0 +1,55 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "ae2omnicells" + }, + { + "type": "forge:item_exists", + "item": "ae2omnicells:omni_link_processor" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "item": "ae2omnicells:ender_ingot" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:dusts/redstone" + }, + "count": 64 + }, + { + "ingredient": { + "tag": "forge:silicon" + }, + "count": 64 + } + ], + "mold": { + "item": "ae2:inscriber" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 10000, + "process_time": 40, + "outputs": [ + { + "item": "ae2omnicells:omni_link_processor", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/bronze_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/bronze_gear.json new file mode 100644 index 00000000..abef0520 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/bronze_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:bronze_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/bronze" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:bronze_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/constantan_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/constantan_gear.json new file mode 100644 index 00000000..0ba691be --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/constantan_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:constantan_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/constantan" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:constantan_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/copper_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/copper_gear.json new file mode 100644 index 00000000..e4c4d940 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/copper_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:copper_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/copper" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:copper_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/diamond_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/diamond_gear.json new file mode 100644 index 00000000..1ead8b1a --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/diamond_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:diamond_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/diamond" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:diamond_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/electrum_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/electrum_gear.json new file mode 100644 index 00000000..c94cf37f --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/electrum_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:electrum_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/electrum" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:electrum_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/emerald_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/emerald_gear.json new file mode 100644 index 00000000..d4810fc7 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/emerald_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:emerald_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/emerald" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:emerald_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/enderium_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/enderium_gear.json new file mode 100644 index 00000000..f3b21dec --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/enderium_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:enderium_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/enderium" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:enderium_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/gold_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/gold_gear.json new file mode 100644 index 00000000..dbc0e928 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/gold_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:gold_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/gold" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:gold_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/invar_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/invar_gear.json new file mode 100644 index 00000000..78726059 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/invar_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:invar_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/invar" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:invar_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/iron_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/iron_gear.json new file mode 100644 index 00000000..383f0f1b --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/iron_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:iron_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/iron" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:iron_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lapis_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lapis_gear.json new file mode 100644 index 00000000..b759c770 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lapis_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:lapis_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/lapis" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:lapis_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lead_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lead_gear.json new file mode 100644 index 00000000..d563fa8f --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lead_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:lead_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/lead" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:lead_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lumium_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lumium_gear.json new file mode 100644 index 00000000..0bfff035 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/lumium_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:lumium_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/lumium" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:lumium_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/netherite_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/netherite_gear.json new file mode 100644 index 00000000..020f2ba7 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/netherite_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:netherite_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/netherite" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:netherite_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/nickel_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/nickel_gear.json new file mode 100644 index 00000000..ca698e38 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/nickel_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:nickel_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/nickel" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:nickel_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/quartz_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/quartz_gear.json new file mode 100644 index 00000000..70e6ee88 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/quartz_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:quartz_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/quartz" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:quartz_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/ruby_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/ruby_gear.json new file mode 100644 index 00000000..adaaaf09 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/ruby_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:ruby_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/ruby" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:ruby_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/sapphire_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/sapphire_gear.json new file mode 100644 index 00000000..491fa459 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/sapphire_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:sapphire_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:gems/sapphire" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:sapphire_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/signalum_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/signalum_gear.json new file mode 100644 index 00000000..194bcca8 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/signalum_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:signalum_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/signalum" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:signalum_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/silver_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/silver_gear.json new file mode 100644 index 00000000..cdcfa3bf --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/silver_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:silver_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/silver" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:silver_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/tin_gear.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/tin_gear.json new file mode 100644 index 00000000..9198ef06 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/gear/tin_gear.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:tin_gear" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/tin" + }, + "count": 256 + } + ], + "mold": { + "item": "useless_mod:metal_mold_gear" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:tin_gear", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/bronze_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/bronze_plate.json new file mode 100644 index 00000000..5c8325ca --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/bronze_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:bronze_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/bronze" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:bronze_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/constantan_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/constantan_plate.json new file mode 100644 index 00000000..80f19edd --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/constantan_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:constantan_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/constantan" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:constantan_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/copper_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/copper_plate.json new file mode 100644 index 00000000..3830144a --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/copper_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:copper_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/copper" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:copper_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/electrum_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/electrum_plate.json new file mode 100644 index 00000000..e6a7355a --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/electrum_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:electrum_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/electrum" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:electrum_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/enderium_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/enderium_plate.json new file mode 100644 index 00000000..1e5f1031 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/enderium_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:enderium_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/enderium" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:enderium_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/gold_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/gold_plate.json new file mode 100644 index 00000000..abccbbe0 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/gold_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:gold_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/gold" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:gold_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/invar_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/invar_plate.json new file mode 100644 index 00000000..1da62265 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/invar_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:invar_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/invar" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:invar_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/iron_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/iron_plate.json new file mode 100644 index 00000000..a5c38af4 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/iron_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:iron_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/iron" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:iron_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lead_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lead_plate.json new file mode 100644 index 00000000..52d29efe --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lead_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:lead_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/lead" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:lead_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lumium_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lumium_plate.json new file mode 100644 index 00000000..967f5762 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/lumium_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:lumium_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/lumium" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:lumium_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/netherite_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/netherite_plate.json new file mode 100644 index 00000000..f6f96e0b --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/netherite_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:netherite_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/netherite" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:netherite_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/nickel_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/nickel_plate.json new file mode 100644 index 00000000..f96c6881 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/nickel_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:nickel_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/nickel" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:nickel_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/signalum_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/signalum_plate.json new file mode 100644 index 00000000..a685dfc4 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/signalum_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:signalum_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/signalum" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:signalum_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/silver_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/silver_plate.json new file mode 100644 index 00000000..7868c014 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/silver_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:silver_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/silver" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:silver_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} diff --git a/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/tin_plate.json b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/tin_plate.json new file mode 100644 index 00000000..adad6bc8 --- /dev/null +++ b/src/main/resources/data/useless_mod/recipes/machine/smelter/thermal/plate/tin_plate.json @@ -0,0 +1,43 @@ +{ + "type": "forge:conditional", + "recipes": [ + { + "conditions": [ + { + "type": "forge:mod_loaded", + "modid": "thermal_foundation" + }, + { + "type": "forge:item_exists", + "item": "thermal:tin_plate" + } + ], + "recipe": { + "type": "useless_mod:advanced_alloy_furnace", + "ingredients": [ + { + "ingredient": { + "tag": "forge:ingots/tin" + }, + "count": 64 + } + ], + "mold": { + "item": "useless_mod:metal_mold_plate" + }, + "catalyst": { + "tag": "useless_mod:useless_ingots" + }, + "energy": 64000, + "process_time": 40, + "outputs": [ + { + "item": "thermal:tin_plate", + "count": 64 + } + ], + "catalyst_uses": 1 + } + } + ] +} From f1e20343153a57f17778ab28719d31b5571962f3 Mon Sep 17 00:00:00 2001 From: P1chC Date: Fri, 23 Oct 2026 19:49:40 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=8C=89=E6=9C=80=E4=BD=8E=20API=20?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=20Thermal=20=E5=92=8C=20CoFH=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=B8=8B=E9=99=90=E5=B9=B6=E9=AA=8C=E8=AF=81=E5=85=BC?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++-- gradle.properties | 2 +- gradle/merge-game-tests.gradle | 14 +++++++++---- .../useless/tests/MergedFurnaceGameTests.java | 21 ++++++++++++++++++- src/main/resources/META-INF/mods.toml | 4 ++-- .../forge1201/DependencyMetadataTest.java | 14 +++++++++++++ 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 31feb88a..a5da8686 100644 --- a/build.gradle +++ b/build.gradle @@ -44,8 +44,8 @@ configurations.modCompileOnly.transitive = false dependencies { modCompileOnly 'maven.modrinth:QeB3NRC5:5muSI5qX' // Re-Avaritia 1.4.0 (Forge 1.20.1) - modCompileOnly 'curse.maven:cofh_core-69162:5374122' - modCompileOnly 'com.teamcofh:thermal_core:1.20.1-11.0.7.25' + modCompileOnly 'com.teamcofh:cofh_core:1.20.1-11.0.0.51' + modCompileOnly 'com.teamcofh:thermal_core:1.20.1-11.0.0.12' modCompileOnly 'curse.maven:blood-magic-224791:7253333' modCompileOnly 'curse.maven:embers-rekindled-300777:7446478' jarJar('io.github.llamalad7:mixinextras-forge:0.5.0') { diff --git a/gradle.properties b/gradle.properties index b29caf99..d9338d6c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,6 +9,6 @@ forge_version=47.4.10 loader_version_range=[47,) mod_id=useless_mod mod_name=Useless Mod -mod_version=20.2.4.6-merged.5 +mod_version=20.2.4.6-merged.6 mod_group_id=com.sorrowmist.useless mod_license=MIT diff --git a/gradle/merge-game-tests.gradle b/gradle/merge-game-tests.gradle index 3c245387..90b0b9d9 100644 --- a/gradle/merge-game-tests.gradle +++ b/gradle/merge-game-tests.gradle @@ -8,15 +8,21 @@ sourceSets { } } +// Verify both the oldest supported API and the exact Thermal versions in the local pack. +def thermalTestVersions = providers.gradleProperty('thermalPackGameTest').isPresent() + ? [cofh_core: '11.0.2.56', thermal_core: '11.0.6.24', + thermal_foundation: '11.0.6.70', thermal_expansion: '11.0.1.29'] + : [cofh_core: '11.0.0.51', thermal_core: '11.0.0.12', + thermal_foundation: '11.0.0.61', thermal_expansion: '11.0.0.26'] + dependencies { modRuntimeOnly 'maven.modrinth:QeB3NRC5:5muSI5qX' modRuntimeOnly 'curse.maven:ex-deorum-901420:6614784' modRuntimeOnly 'maven.modrinth:XxWD5pD3:jYgSnxye' modRuntimeOnly 'maven.modrinth:Ck4E7v7R:i7Tp1AHw' - modRuntimeOnly 'curse.maven:cofh_core-69162:5374122' - modRuntimeOnly 'com.teamcofh:thermal_core:1.20.1-11.0.7.25' - modRuntimeOnly 'curse.maven:thermal_foundation-222880:5443583' - modRuntimeOnly 'curse.maven:thermal_expansion-69163:5372749' + thermalTestVersions.each { artifact, version -> + modRuntimeOnly "com.teamcofh:${artifact}:1.20.1-${version}" + } modRuntimeOnly 'curse.maven:blood-magic-224791:7253333' modRuntimeOnly 'curse.maven:embers-rekindled-300777:7446478' modRuntimeOnly 'curse.maven:Patchouli-306770:6164575' diff --git a/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java b/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java index 14436526..9cc621d0 100644 --- a/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java +++ b/src/gameTest/java/com/sorrowmist/useless/tests/MergedFurnaceGameTests.java @@ -143,7 +143,8 @@ private static void runHammerRecipe(GameTestHelper helper, boolean tagged) { } @GameTest(template = "furnace_empty") - public static void thermalMachineLoadsParallelMixinAndRestoredAugments(GameTestHelper helper) { + public static void thermalMachineLoadsParallelMixinAndRestoredAugments(GameTestHelper helper) + throws ReflectiveOperationException { var machine = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("thermal", "machine_smelter")); helper.assertTrue(machine != null && machine != Blocks.AIR, "Native Thermal smelter must be registered"); var pos = new BlockPos(2, 1, 2); @@ -160,6 +161,24 @@ public static void thermalMachineLoadsParallelMixinAndRestoredAugments(GameTestH helper.assertTrue(ForgeRegistries.ITEMS.containsKey(UselessMod.id("augment_parallel_" + index)), "Restored parallel augment must be registered"); } + var nativeType = cofh.thermal.lib.common.block.entity.AugmentableBlockEntity.class; + var install = nativeType.getDeclaredMethod("attemptAugmentInstall", ItemStack.class); + install.setAccessible(true); + var augment = ForgeRegistries.ITEMS.getValue(UselessMod.id("augment_parallel_1")); + helper.assertTrue((boolean) install.invoke(blockEntity, new ItemStack(augment)), + "The native machine must accept the parallel augment"); + helper.assertTrue(((ThermalParallelAccess) blockEntity).useless_mod$getParallel() == 1, + "Native augment finalization must apply exactly one extra operation"); + var slots = nativeType.getDeclaredField("augments"); + slots.setAccessible(true); + for (var slot : (java.util.List) slots.get(blockEntity)) { + ((cofh.lib.common.inventory.ItemStorageCoFH) slot).setItemStack(ItemStack.EMPTY); + } + var refresh = nativeType.getDeclaredMethod("updateAugmentState"); + refresh.setAccessible(true); + refresh.invoke(blockEntity); + helper.assertTrue(((ThermalParallelAccess) blockEntity).useless_mod$getParallel() == 0, + "Removing the augment must clear parallel operations"); helper.succeed(); } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 2a125dd7..1db259bb 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -326,14 +326,14 @@ side="BOTH" [[dependencies.useless_mod]] modId="thermal" mandatory=false -versionRange="[11.0.7,12)" +versionRange="[11.0.0,12)" ordering="AFTER" side="BOTH" [[dependencies.useless_mod]] modId="cofh_core" mandatory=false -versionRange="[11.0.2,12)" +versionRange="[11.0.0,12)" ordering="AFTER" side="BOTH" diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java index 459fc0dc..335d11a9 100644 --- a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java @@ -84,4 +84,18 @@ private static Map requirements() throws Exception { assertTrue(requirements.get("minecraft").get("mandatory")); assertFalse(requirements.get("create").get("mandatory")); } + + @Test void thermalRequirementsAllowTheOldestApiAndThePacksEmbeddedCore() throws Exception { + var requirements = requirements(); + for (String id : List.of("thermal", "cofh_core")) { + var range = VersionRange.createFromVersionSpec(requirements.get(id).get("versionRange")); + assertTrue(range.containsVersion(new DefaultArtifactVersion("11.0.0")), id); + assertFalse(range.containsVersion(new DefaultArtifactVersion("10.3.0")), id); + assertFalse(range.containsVersion(new DefaultArtifactVersion("12.0.0")), id); + } + assertTrue(VersionRange.createFromVersionSpec(requirements.get("thermal").get("versionRange")) + .containsVersion(new DefaultArtifactVersion("11.0.6"))); + assertTrue(VersionRange.createFromVersionSpec(requirements.get("cofh_core").get("versionRange")) + .containsVersion(new DefaultArtifactVersion("11.0.2"))); + } } From 604fdeb8968109f8c1fbe7de6ace32a08d1771fe Mon Sep 17 00:00:00 2001 From: P1chC Date: Fri, 23 Oct 2026 22:12:46 +0800 Subject: [PATCH 5/5] fix: restore Configured config screen when available --- build.gradle | 2 + gradle.properties | 2 +- gradle/config-ui-check.gradle | 29 +++ .../useless/tests/ConfigUiClientCheck.java | 231 ++++++++++++++++++ .../com/sorrowmist/useless/UselessClient.java | 3 + 5 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 gradle/config-ui-check.gradle create mode 100644 src/configUiCheck/java/com/sorrowmist/useless/tests/ConfigUiClientCheck.java diff --git a/build.gradle b/build.gradle index a5da8686..4980c2d8 100644 --- a/build.gradle +++ b/build.gradle @@ -135,3 +135,5 @@ apply from: 'gradle/bytecode-contract-tests.gradle' apply from: 'gradle/merge-game-tests.gradle' apply from: 'gradle/bundled-compat-libraries.gradle' + +apply from: 'gradle/config-ui-check.gradle' diff --git a/gradle.properties b/gradle.properties index d9338d6c..18dd51fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,6 +9,6 @@ forge_version=47.4.10 loader_version_range=[47,) mod_id=useless_mod mod_name=Useless Mod -mod_version=20.2.4.6-merged.6 +mod_version=20.2.4.6-merged.7 mod_group_id=com.sorrowmist.useless mod_license=MIT diff --git a/gradle/config-ui-check.gradle b/gradle/config-ui-check.gradle new file mode 100644 index 00000000..c41153b2 --- /dev/null +++ b/gradle/config-ui-check.gradle @@ -0,0 +1,29 @@ +// Isolated client checks; no test classes or optional GUI mod enter the release JAR. +if (!providers.gradleProperty('configUiCheck').isPresent()) return + +sourceSets { + configUiCheck { + compileClasspath += sourceSets.main.output + sourceSets.main.compileClasspath + runtimeClasspath += sourceSets.main.output + sourceSets.main.runtimeClasspath + } +} +dependencies { + modRuntimeOnly 'maven.modrinth:XxWD5pD3:jYgSnxye' + modRuntimeOnly 'maven.modrinth:Ck4E7v7R:i7Tp1AHw' + modRuntimeOnly 'io.github.llamalad7:mixinextras-forge:0.5.0' + if (!providers.gradleProperty('configUiWithoutConfigured').isPresent()) { + modRuntimeOnly 'curse.maven:configured-457570:5180900' + } +} +legacyForge { + mods { "${mod_id}" { sourceSet sourceSets.configUiCheck } } + runs { + configUiCheck { + client() + gameDirectory = file('run/config-ui-check') + systemProperty 'useless.configUiCheck', 'true' + systemProperty 'useless.configUiScreenshots', providers.gradleProperty('configUiScreenshots') + .getOrElse(file('run/config-ui-check/screenshots').absolutePath) + } + } +} diff --git a/src/configUiCheck/java/com/sorrowmist/useless/tests/ConfigUiClientCheck.java b/src/configUiCheck/java/com/sorrowmist/useless/tests/ConfigUiClientCheck.java new file mode 100644 index 00000000..14bd055f --- /dev/null +++ b/src/configUiCheck/java/com/sorrowmist/useless/tests/ConfigUiClientCheck.java @@ -0,0 +1,231 @@ +package com.sorrowmist.useless.tests; + +import com.sorrowmist.useless.UselessMod; +import com.sorrowmist.useless.client.gui.PortConfigScreen; +import com.sorrowmist.useless.core.config.ConfigManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Screenshot; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.ConfigScreenHandler.ConfigScreenFactory; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; + +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** Opens the real registered factory and native Configured widgets in an isolated client. */ +@Mod.EventBusSubscriber(modid = UselessMod.MODID, value = Dist.CLIENT) +public final class ConfigUiClientCheck { + private static final ResourceLocation BACKGROUND = new ResourceLocation("minecraft", "textures/block/stone.png"); + private static int ticks; + private static int step; + private static boolean languageLoaded; + private static Screen parent; + private static Screen selection; + private static Screen common; + private static Object config; + private static Object root; + private static String query; + + @SubscribeEvent + public static void tick(TickEvent.ClientTickEvent event) { + if (!Boolean.getBoolean("useless.configUiCheck") || event.phase != TickEvent.Phase.END) return; + var mc = Minecraft.getInstance(); + if (mc.getOverlay() != null || ++ticks % 10 != 0) return; + if (step == 0 && !(mc.screen instanceof TitleScreen)) return; + try { + if (!languageLoaded) { + languageLoaded = true; + mc.options.languageCode = "zh_cn"; + mc.getLanguageManager().setSelected("zh_cn"); + mc.reloadResourcePacks(); + return; + } + if (step == 0) { + parent = mc.screen; + var factory = ModList.get().getModContainerById(UselessMod.MODID).orElseThrow() + .getCustomExtension(ConfigScreenFactory.class).orElseThrow(); + selection = factory.screenFunction().apply(mc, parent); + if (!ModList.get().isLoaded("configured")) { + check(selection instanceof PortConfigScreen, "Missing standalone fallback factory"); + mc.setScreen(selection); + step = 100; + return; + } + check(selection.getClass().getName().startsWith("com.mrcrayfish.configured."), + "Installed Configured must own the mod-list config factory"); + mc.setScreen(selection); + } else if (step == 1) { + capture("01-config-selection"); + selectConfig("-common.toml"); + common = screen(root); + mc.setScreen(common); + } else if (step == 2) { + capture("02-config-categories"); + check(!entries(common).isEmpty(), "Categories must be generated from the registered Forge spec"); + common = screen(folder(root, Boolean.class)); + mc.setScreen(common); + query = invoke(entry(common, "BooleanItem"), "getLabel").toString(); + ((EditBox) field(common, "searchTextField")).setValue(query); + } else if (step == 3) { + capture("03-config-search"); + var results = ((net.minecraft.client.gui.components.AbstractSelectionList) field(common, "list")).children(); + check(!results.isEmpty() && results.size() <= entries(common).size(), "Search must return matching entries"); + ((EditBox) field(common, "searchTextField")).setValue(""); + var booleanScreen = screen(folder(root, Boolean.class)); + mc.setScreen(booleanScreen); + var row = entry(booleanScreen, "BooleanItem"); + Object holder = field(row, "holder"); + Object before = invoke(holder, "get"); + ((Button) field(row, "button")).onPress(); + check(!before.equals(invoke(holder, "get")), "Boolean control must toggle its draft value"); + ((Button) field(row, "resetButton")).onPress(); + check(invoke(holder, "getDefault").equals(invoke(holder, "get")), "Per-value reset must restore defaults"); + } else if (step == 4) { + capture("04-boolean-controls"); + mc.setScreen(selection); + selectConfig("-client.toml"); + var numeric = screen(folder(root, Number.class)); + mc.setScreen(numeric); + var row = entries(numeric).stream().filter(v -> { + try { return invoke(field(v, "holder"), "get") instanceof Number; } + catch (RuntimeException ignored) { return false; } + }).findFirst().orElseThrow(); + Object holder = field(row, "holder"); + Object before = invoke(holder, "get"); + var editor = (EditBox) field(row, "textField"); + editor.setValue("not-a-number"); + check(before.equals(invoke(holder, "get")), "Invalid input must not replace the numeric draft"); + editor.setValue(before.toString()); + } else if (step == 5) { + capture("05-number-controls"); + mc.setScreen(selection); + // Only this test process: provide world-config defaults without opening a user world. + ConfigManager.SERVER_SPEC.setConfig(com.electronwill.nightconfig.core.CommentedConfig.inMemory()); + selectConfig("-server.toml"); + var lists = screen(folder(root, List.class)); + mc.setScreen(lists); + ((Button) field(entry(lists, "ListItem"), "button")).onPress(); + check(mc.screen.getClass().getSimpleName().equals("EditListScreen"), "List control must open a list editor"); + } else if (step == 6) { + capture("06-list-editor"); + mc.setScreen(selection); + selectConfig("-common.toml"); + common = screen(root); + mc.setScreen(common); + ((Button) field(common, "restoreButton")).onPress(); + check(mc.screen.getClass().getSimpleName().contains("Confirmation"), "Restore-all must require confirmation"); + } else if (step == 7) { + capture("07-restore-confirmation"); + mc.setScreen(common); + ((Button) field(entry(common, "FolderItem"), "button")).onPress(); + var edits = mc.screen; + ((Button) field(entry(edits, "BooleanItem"), "button")).onPress(); + back(edits); + check(mc.screen == common, "Category navigation must return to its root without losing the draft"); + back(common); + check(mc.screen.getClass().getSimpleName().contains("Confirmation"), "Closing unsaved edits must show confirmation"); + } else if (step == 8) { + capture("08-unsaved-confirmation"); + System.out.println("CONFIG_UI_CHECK_OK configured factory, categories, search, boolean/reset, numeric validation, lists, confirmations"); + mc.stop(); + } else if (step == 100) { + capture("09-standalone-fallback"); + System.out.println("CONFIG_UI_CHECK_OK standalone fallback without Configured"); + mc.stop(); + } + step++; + } catch (Throwable failure) { + throw new IllegalStateException("Config UI client check failed at step " + step, failure); + } + } + + private static Object invoke(Object object, String name) { + try { + var method = object.getClass().getMethod(name); + method.setAccessible(true); + return method.invoke(object); + } catch (ReflectiveOperationException failure) { + throw new IllegalStateException(name, failure); + } + } + + private static Object field(Object object, String name) { + for (Class type = object.getClass(); type != null; type = type.getSuperclass()) { + try { + Field field = type.getDeclaredField(name); + field.setAccessible(true); + return field.get(object); + } catch (NoSuchFieldException ignored) { + } catch (IllegalAccessException failure) { + throw new IllegalStateException(name, failure); + } + } + throw new IllegalStateException("Missing UI field " + name); + } + + private static List entries(Object screen) { return (List) field(screen, "entries"); } + + private static void back(Screen screen) { + screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast) + .filter(b -> b.getY() >= screen.height - 35) + .max(java.util.Comparator.comparingInt(Button::getX)).orElseThrow().onPress(); + } + + private static void selectConfig(String suffix) { + var configs = (Map) field(selection, "configMap"); + config = configs.values().stream().flatMap(v -> ((Collection) v).stream()) + .filter(v -> invoke(v, "getFileName").toString().endsWith(suffix)).findFirst().orElseThrow(); + root = invoke(config, "getRoot"); + } + + private static Object entry(Object screen, String kind) { + return entries(screen).stream().filter(v -> v.getClass().getSimpleName().equals(kind)).findFirst().orElseThrow(); + } + + private static Object folder(Object entry, Class valueType) { + for (Object child : (List) invoke(entry, "getChildren")) { + if ((boolean) invoke(child, "isLeaf") && valueType.isInstance(invoke(invoke(child, "getValue"), "get"))) return entry; + } + for (Object child : (List) invoke(entry, "getChildren")) { + if (!(boolean) invoke(child, "isLeaf")) { + Object found = folder(child, valueType); + if (found != null) return found; + } + } + return null; + } + + private static Screen screen(Object folder) throws ReflectiveOperationException { + check(folder != null, "Expected typed configuration category"); + var constructor = Class.forName("com.mrcrayfish.configured.client.screen.ConfigScreen").getDeclaredConstructor( + Screen.class, Component.class, Class.forName("com.mrcrayfish.configured.api.IModConfig"), + ResourceLocation.class, Class.forName("com.mrcrayfish.configured.api.IConfigEntry")); + constructor.setAccessible(true); + return (Screen) constructor.newInstance(selection, Component.literal("Useless Mod"), config, BACKGROUND, folder); + } + + private static void capture(String name) throws java.io.IOException { + var directory = Path.of(System.getProperty("useless.configUiScreenshots")); + Files.createDirectories(directory); + try (var image = Screenshot.takeScreenshot(Minecraft.getInstance().getMainRenderTarget())) { + image.writeToFile(directory.resolve(name + ".png")); + } + } + + private static void check(boolean valid, String message) { + if (!valid) throw new AssertionError(message); + } +} diff --git a/src/main/java/com/sorrowmist/useless/UselessClient.java b/src/main/java/com/sorrowmist/useless/UselessClient.java index 3bc75cd0..017b0fc9 100644 --- a/src/main/java/com/sorrowmist/useless/UselessClient.java +++ b/src/main/java/com/sorrowmist/useless/UselessClient.java @@ -6,12 +6,15 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.client.ConfigScreenHandler.ConfigScreenFactory; import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.ModList; import com.sorrowmist.useless.client.gui.PortConfigScreen; @EventBusSubscriber(modid = UselessMod.MODID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) public final class UselessClient { @SubscribeEvent public static void onClientSetup(FMLClientSetupEvent event) { + // Configured generates the full editor only when a mod has not supplied its own factory. + if (ModList.get().isLoaded("configured")) return; ModLoadingContext.get().registerExtensionPoint(ConfigScreenFactory.class, () -> new ConfigScreenFactory((minecraft, parent) -> new PortConfigScreen(parent))); }