From 14044f3b571761fb9c8920cbd2ed63912fc4b254 Mon Sep 17 00:00:00 2001 From: Relentless Date: Thu, 22 Aug 2024 15:06:58 +0200 Subject: [PATCH] 1.21 port (#86) Co-authored-by: LLytho --- .editorconfig | 10 +- .github/workflows/build.yml | 62 --- .github/workflows/release.yml | 265 ----------- CHANGELOG.md | 13 + Common/build.gradle.kts | 19 +- .../almostreliable/unified/AlmostUnified.java | 98 ---- .../unified/AlmostUnifiedCommon.java | 44 ++ .../unified/AlmostUnifiedFallbackRuntime.java | 78 ---- .../unified/AlmostUnifiedLookupImpl.java | 87 ---- .../unified/AlmostUnifiedPlatform.java | 26 +- .../unified/AlmostUnifiedRuntime.java | 22 - .../unified/AlmostUnifiedRuntimeImpl.java | 74 --- .../unified/ReplacementData.java | 42 -- .../unified/api/AlmostUnified.java | 168 +++++++ .../unified/api/AlmostUnifiedLookup.java | 106 ----- .../unified/api/AlmostUnifiedRuntime.java | 67 +++ .../unified/api/ModConstants.java | 25 -- .../unified/api/StoneStrataHandler.java | 107 ----- .../unified/api/constant/ModConstants.java | 31 ++ .../unified/api/constant/RecipeConstants.java | 52 +++ .../constant}/package-info.java | 2 +- .../api/plugin/AlmostUnifiedNeoPlugin.java | 8 + .../api/plugin/AlmostUnifiedPlugin.java | 36 ++ .../api/{recipe => plugin}/package-info.java | 2 +- .../unified/api/recipe/RecipeConstants.java | 55 --- .../unified/api/recipe/RecipeContext.java | 38 -- .../unified/api/recipe/RecipeUnifier.java | 5 - .../api/recipe/RecipeUnifierBuilder.java | 15 - .../api/unification/ModPriorities.java | 70 +++ .../unified/api/unification/Placeholders.java | 52 +++ .../api/unification/StoneVariants.java | 40 ++ .../api/unification/TagSubstitutions.java | 51 +++ .../api/unification/UnificationEntry.java | 57 +++ .../api/unification/UnificationLookup.java | 242 ++++++++++ .../api/unification/UnificationSettings.java | 110 +++++ .../bundled/GenericRecipeUnifier.java | 41 ++ .../bundled/ShapedRecipeUnifier.java | 63 +++ .../bundled/SmithingRecipeUnifier.java | 61 +++ .../api/unification/bundled/package-info.java | 6 + .../unified/api/unification/package-info.java | 6 + .../api/unification/recipe/RecipeData.java | 33 ++ .../api/unification/recipe/RecipeJson.java | 30 ++ .../api/unification/recipe/RecipeUnifier.java | 34 ++ .../recipe/RecipeUnifierRegistry.java | 47 ++ .../unification/recipe/UnificationHelper.java | 247 ++++++++++ .../api/unification/recipe/package-info.java | 6 + .../unified/compat/AdAstraRecipeUnifier.java | 14 - .../unified/compat/AlmostKube.java | 89 ---- .../compat/GregTechModernRecipeUnifier.java | 74 --- .../unified/compat/HideHelper.java | 125 ------ .../unified/compat/PluginManager.java | 78 ++++ .../unified/compat/kube/AlmostKube.java | 71 +++ .../unified/compat/kube/package-info.java | 6 + .../GregTechModernRecipeUnifier.java | 51 +++ .../compat/unification/package-info.java | 6 + .../unified/compat/viewer/AlmostEMI.java | 57 +++ .../compat/{ => viewer}/AlmostJEI.java | 26 +- .../compat/{ => viewer}/AlmostREI.java | 32 +- .../{recipe => compat/viewer}/CRTLookup.java | 10 +- .../viewer}/ClientRecipeTracker.java | 175 ++++---- .../unified/compat/viewer/ItemHider.java | 99 +++++ .../compat/{ => viewer}/RecipeIndicator.java | 4 +- .../unified/compat/viewer/package-info.java | 6 + .../almostreliable/unified/config/Config.java | 135 +++--- .../unified/config/DebugConfig.java | 110 ++--- .../unified/config/Defaults.java | 241 +++++----- ...cationConfig.java => DuplicateConfig.java} | 75 ++-- .../unified/config/PlaceholderConfig.java | 103 +++++ .../unified/config/ServerConfigs.java | 51 --- .../unified/config/StartupConfig.java | 41 +- .../unified/config/TagConfig.java | 193 ++++++++ .../unified/config/UnificationConfig.java | 354 +++++++++++++++ .../unified/config/UnifyConfig.java | 420 ------------------ .../unified/core/AlmostUnifiedImpl.java | 85 ++++ .../core/AlmostUnifiedRuntimeImpl.java | 306 +++++++++++++ .../unified/core/CommonPlugin.java | 23 + .../unified/core/TagReloadHandler.java | 99 +++++ .../unified/{mixin => core}/package-info.java | 2 +- .../mixin/loot/CompositeEntryBaseMixin.java | 29 ++ .../unified/mixin/loot/LootItemMixin.java | 27 ++ .../unified/mixin/loot/LootPoolMixin.java | 30 ++ .../unified/mixin/loot/LootTableMixin.java | 26 ++ .../unified/mixin/loot/package-info.java | 6 + .../runtime/ClientPacketListenerMixin.java | 4 +- .../mixin/runtime/RecipeManagerMixin.java | 11 +- .../unified/mixin/runtime/TagLoaderMixin.java | 12 +- .../ArmorItemMixin.java | 21 +- .../TieredItemMixin.java | 18 +- .../mixin/unification/package-info.java | 6 + .../unified/recipe/RecipeContextImpl.java | 168 ------- .../unified/recipe/RecipeDumper.java | 174 -------- .../recipe/RecipeUnifierBuilderImpl.java | 94 ---- .../recipe/unifier/GenericRecipeUnifier.java | 36 -- .../recipe/unifier/RecipeHandlerFactory.java | 52 --- .../unifier/ShapedRecipeKeyUnifier.java | 25 -- .../recipe/unifier/SmithingRecipeUnifier.java | 20 - .../unified/recipe/unifier/package-info.java | 6 - .../unification/ModPrioritiesImpl.java | 78 ++++ .../unification/StoneVariantsImpl.java | 148 ++++++ .../unified/unification/TagInheritance.java | 246 ++++++++++ .../unification/TagSubstitutionsImpl.java | 140 ++++++ .../unification/UnificationEntryImpl.java | 91 ++++ .../unification/UnificationLookupImpl.java | 159 +++++++ .../unification/UnificationSettingsImpl.java | 169 +++++++ .../unification/loot/LootUnification.java | 60 +++ .../loot/LootUnificationHandler.java | 18 + .../unification/loot/package-info.java | 6 + .../unified/unification/package-info.java | 6 + .../unification/recipe/RecipeJsonImpl.java | 48 ++ .../{ => unification}/recipe/RecipeLink.java | 26 +- .../recipe/RecipeTransformer.java | 158 +++---- .../recipe/RecipeUnifierRegistryImpl.java | 52 +++ .../recipe/UnificationHelperImpl.java | 221 +++++++++ .../unification/recipe/package-info.java | 6 + .../{ => utils}/ClientTagUpdateEvent.java | 2 +- .../unified/utils/CustomLogger.java | 91 ++++ .../unified/utils/DebugHandler.java | 260 +++++++++++ .../unified/utils/FileUtils.java | 26 +- .../unified/utils/JsonCompare.java | 20 +- .../unified/utils/JsonUtils.java | 30 +- .../utils/RecipeTypePropertiesLogger.java | 12 +- .../unified/utils/ReplacementMap.java | 151 ------- .../almostreliable/unified/utils/TagMap.java | 162 ------- .../unified/utils/TagOwnerships.java | 144 ------ .../unified/utils/TagReloadHandler.java | 263 ----------- .../unified/utils/UnifyTag.java | 20 - .../almostreliable/unified/utils/Utils.java | 35 +- .../unified/utils/VanillaTagWrapper.java | 104 +++++ .../almostunified-common.mixins.json | 10 +- Common/src/main/resources/kubejs.bindings.txt | 2 +- Common/src/main/resources/pack.mcmeta | 2 +- .../unified/util/JsonCompareTest.java | 145 ------ .../unified/utils/TagMapTests.java | 59 --- Common/src/test/java/testmod/CommonTest.java | 26 ++ Common/src/test/java/testmod/TestItems.java | 50 +++ Common/src/test/java/testmod/TestUtils.java | 170 +++++++ .../gametest_core/AlmostGameTestHelper.java | 70 +++ .../testmod/gametest_core/GameTestLoader.java | 196 ++++++++ .../testmod/gametest_core/SimpleGameTest.java | 19 + .../mixin/GameTestHelperAccessor.java | 14 + .../mixin/GameTestRegistryAccessor.java | 43 ++ .../gametest_core/mixin/ItemTooltipMixin.java | 30 ++ .../testmod/gametest_core}/package-info.java | 2 +- .../old}/FakeResourceKeyRegistry.java | 8 +- .../unified => testmod/old}/TestUtils.java | 144 +++--- .../test/java/testmod/old/package-info.java | 6 + .../old}/recipe/RecipeContextImplTest.java | 12 +- .../testmod/old/util/JsonCompareTest.java | 134 ++++++ .../old}/utils/ReplacementMapTests.java | 7 +- .../src/test/java/testmod/package-info.java | 6 + .../test/java/testmod/tests/ExampleTest.java | 15 + .../GregTechModernRecipeUnifierTests.java | 227 ++++++++++ .../testmod/tests/LootUnificationTests.java | 69 +++ .../java/testmod/tests/ReplacementsTests.java | 40 ++ .../tests/ShapedRecipeUnifierTests.java | 58 +++ .../tests/SmithingRecipeUnifierTest.java | 80 ++++ .../tests/UnificationHandlerTests.java | 160 +++++++ .../test/java/testmod/tests/UnifyTests.java | 127 ++++++ .../tests/core/TagInheritanceTests.java | 43 ++ .../tests/core/TagSubstitutionTests.java | 42 ++ .../java/testmod/tests/core/package-info.java | 6 + .../test/java/testmod/tests/package-info.java | 6 + .../data/c/tags/item/ingots/osmium.json | 8 + .../data/c/tags/item/ores/osmium.json | 8 + .../data/c/tags/item/ores/silver.json | 6 + .../data/c/tags/item/silver_ores.json | 5 + .../ie_fake/loot_table/blocks/osmium_ore.json | 52 +++ .../loot_table/blocks/osmium_ore.json | 52 +++ .../tags/block/mineable/pickaxe.json | 8 + .../tags/item/beacon_payment_items.json | 6 + .../testmod/loot_table/blocks/osmium_ore.json | 52 +++ .../loot_table/blocks/osmium_ore.json | 52 +++ Common/src/test/resources/testmod.mixins.json | 16 + Fabric/build.gradle.kts | 31 +- .../unified/AlmostUnifiedFabric.java | 39 +- .../unified/AlmostUnifiedPlatformFabric.java | 35 +- .../AmethystImbuementRecipeUnifier.java | 29 -- .../ModernIndustrializationRecipeUnifier.java | 13 - .../AmethystImbuementRecipeUnifier.java | 32 ++ .../unified/core/FabricPlugin.java | 22 + Fabric/src/main/resources/fabric.mod.json | 14 +- Fabric/src/test/java/testmod/FabricTest.java | 13 + .../AmethystImbuementRecipeUnifierTests.java | 110 +++++ .../structure/empty_test_structure.snbt | 38 ++ Fabric/src/test/resources/fabric.mod.json | 13 + Forge/build.gradle.kts | 89 ---- Forge/gradle.properties | 2 - .../unified/AlmostUnifiedForge.java | 26 -- .../unified/AlmostUnifiedPlatformForge.java | 78 ---- .../compat/ArsNouveauRecipeUnifier.java | 40 -- .../unified/compat/CyclicRecipeUnifier.java | 13 - .../unified/compat/EnderIORecipeUnifier.java | 17 - .../ImmersiveEngineeringRecipeUnifier.java | 61 --- .../IntegratedDynamicsRecipeUnifier.java | 43 -- .../unified/compat/MekanismRecipeUnifier.java | 18 - .../compat/ie/IERecipeUnifierTest.java | 90 ---- NeoForge/build.gradle.kts | 80 ++++ NeoForge/gradle.properties | 2 + .../unified/AlmostUnifiedNeoForge.java | 126 ++++++ .../AlmostUnifiedPlatformNeoForge.java | 44 ++ .../unification/ArsNouveauRecipeUnifier.java | 41 ++ .../unification/CyclicRecipeUnifier.java | 15 + .../unification/EnderIORecipeUnifier.java | 14 + .../ImmersiveEngineeringRecipeUnifier.java | 66 +++ .../IntegratedDynamicsRecipeUnifier.java | 60 +++ .../unification/MekanismRecipeUnifier.java | 29 ++ .../ModernIndustrializationRecipeUnifier.java | 19 + .../unification/OccultismRecipeUnifier.java | 54 +++ .../ProductiveTreesRecipeUnifier.java | 20 + .../unification/TheurgyRecipeUnifier.java | 32 ++ .../compat/viewer}/AlmostREIForge.java | 4 +- .../unified/core/AlmostUnifiedCommands.java | 65 +++ .../unified/core/NeoForgePlugin.java | 44 ++ .../worldgen/OreConfigurationAccessor.java | 18 + .../worldgen/ServerLifecycleHooksMixin.java | 31 ++ .../mixin/neoforge/worldgen/package-info.java | 6 + .../worldgen/WorldGenBiomeModifier.java | 82 ++++ .../unification/worldgen/WorldGenUnifier.java | 117 +++++ .../unification/worldgen/WorldStripper.java | 93 ++++ .../unification/worldgen/package-info.java | 6 + .../resources/META-INF/neoforge.mods.toml | 32 +- .../almostunified-neoforge.mixins.json | 15 + .../neoforge/biome_modifier/worldgen.json | 3 + .../java/testmod/neoforge/NeoForgeTest.java | 45 ++ .../neoforge/tests/ArsNouveauRecipeTests.java | 159 +++++++ .../tests/EnderIORecipeUnifierTests.java | 50 +++ ...mmersiveEngineeringRecipeUnifierTests.java | 366 +++++++++++++++ .../IntegratedDynamicsRecipeUnifierTests.java | 108 +++++ .../tests/MekanismRecipeUnifierTests.java | 310 +++++++++++++ ...rnIndustrializationRecipeUnifierTests.java | 132 ++++++ .../resources/META-INF/neoforge.mods.toml | 25 ++ .../structure/empty_test_structure.nbt | Bin 0 -> 223 bytes build.gradle.kts | 77 +++- gradle.properties | 26 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 18 + settings.gradle.kts | 3 +- testmod_configs/debug.json | 7 + testmod_configs/duplicates.json | 35 ++ testmod_configs/placeholders.json | 51 +++ testmod_configs/startup.json | 4 + testmod_configs/tags.json | 18 + testmod_configs/unification/materials.json | 48 ++ 243 files changed, 10664 insertions(+), 4728 deletions(-) delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 Common/src/main/java/com/almostreliable/unified/AlmostUnified.java create mode 100644 Common/src/main/java/com/almostreliable/unified/AlmostUnifiedCommon.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/ReplacementData.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/AlmostUnified.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedLookup.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedRuntime.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/ModConstants.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/constant/ModConstants.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/constant/RecipeConstants.java rename Common/src/main/java/com/almostreliable/unified/{mixin/unifier => api/constant}/package-info.java (77%) create mode 100644 Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedNeoPlugin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedPlugin.java rename Common/src/main/java/com/almostreliable/unified/api/{recipe => plugin}/package-info.java (78%) delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeContext.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifier.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifierBuilder.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/ModPriorities.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/Placeholders.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/StoneVariants.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/TagSubstitutions.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/UnificationEntry.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/UnificationLookup.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/UnificationSettings.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/bundled/GenericRecipeUnifier.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/bundled/ShapedRecipeUnifier.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/bundled/SmithingRecipeUnifier.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/bundled/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeData.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeJson.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifier.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifierRegistry.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/recipe/UnificationHelper.java create mode 100644 Common/src/main/java/com/almostreliable/unified/api/unification/recipe/package-info.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/compat/AdAstraRecipeUnifier.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/compat/AlmostKube.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/compat/GregTechModernRecipeUnifier.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/PluginManager.java create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/kube/AlmostKube.java create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/kube/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/unification/GregTechModernRecipeUnifier.java create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/unification/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostEMI.java rename Common/src/main/java/com/almostreliable/unified/compat/{ => viewer}/AlmostJEI.java (78%) rename Common/src/main/java/com/almostreliable/unified/compat/{ => viewer}/AlmostREI.java (83%) rename Common/src/main/java/com/almostreliable/unified/{recipe => compat/viewer}/CRTLookup.java (69%) rename Common/src/main/java/com/almostreliable/unified/{recipe => compat/viewer}/ClientRecipeTracker.java (51%) create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/viewer/ItemHider.java rename Common/src/main/java/com/almostreliable/unified/compat/{ => viewer}/RecipeIndicator.java (95%) create mode 100644 Common/src/main/java/com/almostreliable/unified/compat/viewer/package-info.java rename Common/src/main/java/com/almostreliable/unified/config/{DuplicationConfig.java => DuplicateConfig.java} (60%) create mode 100644 Common/src/main/java/com/almostreliable/unified/config/PlaceholderConfig.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java create mode 100644 Common/src/main/java/com/almostreliable/unified/config/TagConfig.java create mode 100644 Common/src/main/java/com/almostreliable/unified/config/UnificationConfig.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java create mode 100644 Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedRuntimeImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/core/CommonPlugin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/core/TagReloadHandler.java rename Common/src/main/java/com/almostreliable/unified/{mixin => core}/package-info.java (80%) create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/loot/CompositeEntryBaseMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/loot/LootItemMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/loot/LootPoolMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/loot/LootTableMixin.java create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/loot/package-info.java rename Common/src/main/java/com/almostreliable/unified/mixin/{unifier => unification}/ArmorItemMixin.java (51%) rename Common/src/main/java/com/almostreliable/unified/mixin/{unifier => unification}/TieredItemMixin.java (57%) create mode 100644 Common/src/main/java/com/almostreliable/unified/mixin/unification/package-info.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/RecipeDumper.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/RecipeUnifierBuilderImpl.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/unifier/GenericRecipeUnifier.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/unifier/RecipeHandlerFactory.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/unifier/ShapedRecipeKeyUnifier.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/unifier/SmithingRecipeUnifier.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/recipe/unifier/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/ModPrioritiesImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/StoneVariantsImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/TagInheritance.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/TagSubstitutionsImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/UnificationEntryImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/UnificationLookupImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/UnificationSettingsImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnification.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnificationHandler.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/loot/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/package-info.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeJsonImpl.java rename Common/src/main/java/com/almostreliable/unified/{ => unification}/recipe/RecipeLink.java (90%) rename Common/src/main/java/com/almostreliable/unified/{ => unification}/recipe/RecipeTransformer.java (59%) create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeUnifierRegistryImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/recipe/UnificationHelperImpl.java create mode 100644 Common/src/main/java/com/almostreliable/unified/unification/recipe/package-info.java rename Common/src/main/java/com/almostreliable/unified/{ => utils}/ClientTagUpdateEvent.java (91%) create mode 100644 Common/src/main/java/com/almostreliable/unified/utils/CustomLogger.java create mode 100644 Common/src/main/java/com/almostreliable/unified/utils/DebugHandler.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/utils/TagMap.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/utils/TagReloadHandler.java delete mode 100644 Common/src/main/java/com/almostreliable/unified/utils/UnifyTag.java create mode 100644 Common/src/main/java/com/almostreliable/unified/utils/VanillaTagWrapper.java delete mode 100644 Common/src/test/java/com/almostreliable/unified/util/JsonCompareTest.java delete mode 100644 Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java create mode 100644 Common/src/test/java/testmod/CommonTest.java create mode 100644 Common/src/test/java/testmod/TestItems.java create mode 100644 Common/src/test/java/testmod/TestUtils.java create mode 100644 Common/src/test/java/testmod/gametest_core/AlmostGameTestHelper.java create mode 100644 Common/src/test/java/testmod/gametest_core/GameTestLoader.java create mode 100644 Common/src/test/java/testmod/gametest_core/SimpleGameTest.java create mode 100644 Common/src/test/java/testmod/gametest_core/mixin/GameTestHelperAccessor.java create mode 100644 Common/src/test/java/testmod/gametest_core/mixin/GameTestRegistryAccessor.java create mode 100644 Common/src/test/java/testmod/gametest_core/mixin/ItemTooltipMixin.java rename Common/src/{main/java/com/almostreliable/unified/recipe => test/java/testmod/gametest_core}/package-info.java (79%) rename Common/src/test/java/{com/almostreliable/unified => testmod/old}/FakeResourceKeyRegistry.java (75%) rename Common/src/test/java/{com/almostreliable/unified => testmod/old}/TestUtils.java (63%) create mode 100644 Common/src/test/java/testmod/old/package-info.java rename Common/src/test/java/{com/almostreliable/unified => testmod/old}/recipe/RecipeContextImplTest.java (66%) create mode 100644 Common/src/test/java/testmod/old/util/JsonCompareTest.java rename Common/src/test/java/{com/almostreliable/unified => testmod/old}/utils/ReplacementMapTests.java (84%) create mode 100644 Common/src/test/java/testmod/package-info.java create mode 100644 Common/src/test/java/testmod/tests/ExampleTest.java create mode 100644 Common/src/test/java/testmod/tests/GregTechModernRecipeUnifierTests.java create mode 100644 Common/src/test/java/testmod/tests/LootUnificationTests.java create mode 100644 Common/src/test/java/testmod/tests/ReplacementsTests.java create mode 100644 Common/src/test/java/testmod/tests/ShapedRecipeUnifierTests.java create mode 100644 Common/src/test/java/testmod/tests/SmithingRecipeUnifierTest.java create mode 100644 Common/src/test/java/testmod/tests/UnificationHandlerTests.java create mode 100644 Common/src/test/java/testmod/tests/UnifyTests.java create mode 100644 Common/src/test/java/testmod/tests/core/TagInheritanceTests.java create mode 100644 Common/src/test/java/testmod/tests/core/TagSubstitutionTests.java create mode 100644 Common/src/test/java/testmod/tests/core/package-info.java create mode 100644 Common/src/test/java/testmod/tests/package-info.java create mode 100644 Common/src/test/resources/data/c/tags/item/ingots/osmium.json create mode 100644 Common/src/test/resources/data/c/tags/item/ores/osmium.json create mode 100644 Common/src/test/resources/data/c/tags/item/ores/silver.json create mode 100644 Common/src/test/resources/data/c/tags/item/silver_ores.json create mode 100644 Common/src/test/resources/data/ie_fake/loot_table/blocks/osmium_ore.json create mode 100644 Common/src/test/resources/data/meka_fake/loot_table/blocks/osmium_ore.json create mode 100644 Common/src/test/resources/data/minecraft/tags/block/mineable/pickaxe.json create mode 100644 Common/src/test/resources/data/minecraft/tags/item/beacon_payment_items.json create mode 100644 Common/src/test/resources/data/testmod/loot_table/blocks/osmium_ore.json create mode 100644 Common/src/test/resources/data/thermal_fake/loot_table/blocks/osmium_ore.json create mode 100644 Common/src/test/resources/testmod.mixins.json delete mode 100644 Fabric/src/main/java/com/almostreliable/unified/compat/AmethystImbuementRecipeUnifier.java delete mode 100644 Fabric/src/main/java/com/almostreliable/unified/compat/ModernIndustrializationRecipeUnifier.java create mode 100644 Fabric/src/main/java/com/almostreliable/unified/compat/unification/AmethystImbuementRecipeUnifier.java create mode 100644 Fabric/src/main/java/com/almostreliable/unified/core/FabricPlugin.java create mode 100644 Fabric/src/test/java/testmod/FabricTest.java create mode 100644 Fabric/src/test/java/testmod/tests/AmethystImbuementRecipeUnifierTests.java create mode 100644 Fabric/src/test/resources/data/testmod/gametest/structure/empty_test_structure.snbt create mode 100644 Fabric/src/test/resources/fabric.mod.json delete mode 100644 Forge/build.gradle.kts delete mode 100644 Forge/gradle.properties delete mode 100644 Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedForge.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformForge.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/compat/ArsNouveauRecipeUnifier.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/compat/CyclicRecipeUnifier.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/compat/EnderIORecipeUnifier.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/compat/ImmersiveEngineeringRecipeUnifier.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/compat/IntegratedDynamicsRecipeUnifier.java delete mode 100644 Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java delete mode 100644 Forge/src/test/java/com/almostreliable/unified/compat/ie/IERecipeUnifierTest.java create mode 100644 NeoForge/build.gradle.kts create mode 100644 NeoForge/gradle.properties create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedNeoForge.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformNeoForge.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ArsNouveauRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/CyclicRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/EnderIORecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ImmersiveEngineeringRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/IntegratedDynamicsRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/MekanismRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ModernIndustrializationRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/OccultismRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ProductiveTreesRecipeUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/compat/unification/TheurgyRecipeUnifier.java rename {Forge/src/main/java/com/almostreliable/unified/compat => NeoForge/src/main/java/com/almostreliable/unified/compat/viewer}/AlmostREIForge.java (52%) create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/core/AlmostUnifiedCommands.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/core/NeoForgePlugin.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/OreConfigurationAccessor.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/ServerLifecycleHooksMixin.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/package-info.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenBiomeModifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenUnifier.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldStripper.java create mode 100644 NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/package-info.java rename Forge/src/main/resources/META-INF/mods.toml => NeoForge/src/main/resources/META-INF/neoforge.mods.toml (63%) create mode 100644 NeoForge/src/main/resources/almostunified-neoforge.mixins.json create mode 100644 NeoForge/src/main/resources/data/almostunified/neoforge/biome_modifier/worldgen.json create mode 100644 NeoForge/src/test/java/testmod/neoforge/NeoForgeTest.java create mode 100644 NeoForge/src/test/java/testmod/neoforge/tests/ArsNouveauRecipeTests.java create mode 100644 NeoForge/src/test/java/testmod/neoforge/tests/EnderIORecipeUnifierTests.java create mode 100644 NeoForge/src/test/java/testmod/neoforge/tests/ImmersiveEngineeringRecipeUnifierTests.java create mode 100644 NeoForge/src/test/java/testmod/neoforge/tests/IntegratedDynamicsRecipeUnifierTests.java create mode 100644 NeoForge/src/test/java/testmod/neoforge/tests/MekanismRecipeUnifierTests.java create mode 100644 NeoForge/src/test/java/testmod/neoforge/tests/ModernIndustrializationRecipeUnifierTests.java create mode 100644 NeoForge/src/test/resources/META-INF/neoforge.mods.toml create mode 100644 NeoForge/src/test/resources/data/testmod/structure/empty_test_structure.nbt create mode 100644 testmod_configs/debug.json create mode 100644 testmod_configs/duplicates.json create mode 100644 testmod_configs/placeholders.json create mode 100644 testmod_configs/startup.json create mode 100644 testmod_configs/tags.json create mode 100644 testmod_configs/unification/materials.json diff --git a/.editorconfig b/.editorconfig index 53c0cd3..e736305 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ tab_width = 4 ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = false +ij_formatter_tags_enabled = true ij_smart_tabs = false ij_visual_guides = 120 ij_wrap_on_typing = false @@ -348,7 +348,7 @@ ij_kotlin_wrap_elvis_expressions = 1 ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false -[{**.json,mcmod.info,pack.mcmeta}] +[{*.json,mcmod.info,pack.mcmeta}] indent_size = 2 max_line_length = 150 tab_width = 2 @@ -365,7 +365,7 @@ ij_json_spaces_within_braces = false ij_json_spaces_within_brackets = false ij_json_wrap_long_lines = false -[**.md] +[*.md] ij_visual_guides = none ij_markdown_force_one_space_after_blockquote_symbol = true ij_markdown_force_one_space_after_header_symbol = true @@ -379,11 +379,11 @@ ij_markdown_min_lines_around_block_elements = 1 ij_markdown_min_lines_around_header = 1 ij_markdown_min_lines_between_paragraphs = 1 -[**.toml] +[*.toml] ij_visual_guides = none ij_toml_keep_indents_on_empty_lines = false -[{**.yml,**.yaml}] +[{*.yml,*.yaml}] indent_size = 2 ij_visual_guides = none ij_yaml_align_values_properties = do_not_align diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 6822bb1..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Build - -on: - workflow_dispatch: - push: - branches: - - '1.20.1' - tags-ignore: - - '**' - paths: - - 'gradle/**' - - '**.java' - - '**.kts' - - '**.properties' - - '**/build.yml' - pull_request: - branches: - - '1.20.1' - paths: - - 'gradle/**' - - '**.java' - - '**.kts' - - '**.properties' - - '**/build.yml' - -env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 17 - -jobs: - build: - name: Build - runs-on: ubuntu-latest - if: | - !contains(github.event.head_commit.message, '[skip build]') - steps: - - name: Clone Repository - uses: actions/checkout@v3 - - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v3 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: ${{ env.JAVA_DIST }} - cache: gradle - - - name: Cleanup Gradle Cache - run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties - - - name: Make Gradle executable - run: chmod +x ./gradlew - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 - - - name: Build - run: ./gradlew build --stacktrace - - - name: Test - run: ./gradlew test --stacktrace diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 74928a8..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,265 +0,0 @@ -name: Release - -on: - workflow_dispatch: - push: - tags: - - 'v1.20.1-*.*.*' - -env: - JAVA_DIST: 'zulu' - JAVA_VERSION: 17 - MOD_ID: 'almostunified' - MOD_NAME: 'AlmostUnified' - CURSEFORGE_ID: '633823' - MODRINTH_ID: 'sdaSaQEz' - -jobs: - - build: - name: Build, collect info, parse changelog - runs-on: ubuntu-latest - outputs: - JAR_FILE: ${{ steps.collect_info.outputs.JAR_FILE }} - MINECRAFT_VERSION: ${{ steps.collect_info.outputs.MINECRAFT_VERSION }} - MOD_VERSION: ${{ steps.collect_info.outputs.MOD_VERSION }} - RELEASE_TYPE: ${{ steps.collect_info.outputs.RELEASE_TYPE }} - steps: - - name: Clone Repository - uses: actions/checkout@v3 - - - name: Set up JDK ${{ env.JAVA_VERSION }} - uses: actions/setup-java@v3 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: ${{ env.JAVA_DIST }} - cache: gradle - - - name: Cleanup Gradle Cache - run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties - - - name: Make Gradle executable - run: chmod +x ./gradlew - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 - - - name: Assemble the JARs - run: ./gradlew assemble - - - name: Move JARs to central directory - run: | - mkdir output - mv -f Forge/build/libs/*.jar Fabric/build/libs/*.jar output/ - rm -f output/*-dev-shadow.jar output/*-sources.jar - - - name: Collect version information - id: collect_info - run: | - shopt -s failglob # print a warning if a glob does not match anything - set_var() { - echo $1="$2" - echo $1="$2" >> $GITHUB_OUTPUT - declare -g $1="$2" - } - set_var JAR_FILE $(eval echo output/${{ env.MOD_ID }}-*-*-*.jar) - set_var MINECRAFT_VERSION $(echo ${JAR_FILE%.*} | cut -d- -f3) - set_var MOD_VERSION $(echo ${JAR_FILE%.*} | cut -d- -f4) - set_var RELEASE_TYPE "$(echo ${GITHUB_REF##*/} | cut -d- -f3)" - set_var RELEASE_TYPE "$([[ -z $RELEASE_TYPE ]] && echo release || echo $RELEASE_TYPE)" - - - name: Install changelog parser - uses: taiki-e/install-action@parse-changelog - - - name: Parse changelog - run: parse-changelog CHANGELOG.md ${{ steps.collect_info.outputs.MOD_VERSION }} > output/changelog.md - - - name: Archive results - run: tar -zcvf build.tar.gz output - - - name: Upload results - uses: actions/upload-artifact@v3 - with: - name: build-artifacts - path: build.tar.gz - if-no-files-found: error - retention-days: 3 - - - name: Job Summary - run: | - echo "# Version Information" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- Minecraft Version: ${{ steps.collect_info.outputs.MINECRAFT_VERSION }}" >> $GITHUB_STEP_SUMMARY - echo "- Mod Version: ${{ steps.collect_info.outputs.MOD_VERSION }}" >> $GITHUB_STEP_SUMMARY - echo "- Release Type: ${{ steps.collect_info.outputs.RELEASE_TYPE }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "# Build Information" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- JAR files: $(find output -maxdepth 1 -type f -name '*.jar' | wc -l)" >> $GITHUB_STEP_SUMMARY - echo "- Folder size: $(du -sh output | cut -f1)" >> $GITHUB_STEP_SUMMARY - echo "- Archive size: $(du -sh build.tar.gz | cut -f1)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "# Changelog" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - cat output/changelog.md >> $GITHUB_STEP_SUMMARY - - mr-fabric-release: - name: Modrinth Fabric Release - needs: build - runs-on: ubuntu-latest - steps: - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - - name: Extract build artifacts - run: tar -zxvf build.tar.gz - - - name: Release Fabric on Modrinth - uses: Kir-Antipov/mc-publish@v3.3 - with: - modrinth-id: ${{ env.MODRINTH_ID }} - modrinth-token: ${{ secrets.MODRINTH_TOKEN }} - - files: output/*fabric*.jar - name: ${{ env.MOD_NAME }}-Fabric-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }} - version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+fabric - version-type: ${{ needs.build.outputs.RELEASE_TYPE }} - changelog-file: output/changelog.md - - loaders: fabric - game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }} - java: ${{ env.JAVA_VERSION }} - - dependencies: | - jei(optional){curseforge:238222}{modrinth:u6dRKJwZ} - rei(optional){curseforge:310111}{modrinth:nfn13YXA} - - cf-fabric-release: - name: CurseForge Fabric Release - needs: build - runs-on: ubuntu-latest - steps: - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - - name: Extract build artifacts - run: tar -zxvf build.tar.gz - - - name: Release Fabric on CurseForge - uses: Kir-Antipov/mc-publish@v3.3 - with: - curseforge-id: ${{ env.CURSEFORGE_ID }} - curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} - - files: output/*fabric*.jar - name: ${{ env.MOD_NAME }}-Fabric-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }} - version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+fabric - version-type: ${{ needs.build.outputs.RELEASE_TYPE }} - changelog-file: output/changelog.md - - loaders: fabric - game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }} - java: ${{ env.JAVA_VERSION }} - - dependencies: | - jei(optional){curseforge:238222}{modrinth:u6dRKJwZ} - rei(optional){curseforge:310111}{modrinth:nfn13YXA} - - mr-forge-release: - name: Modrinth Forge Release - needs: build - runs-on: ubuntu-latest - steps: - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - - name: Extract build artifacts - run: tar -zxvf build.tar.gz - - - name: Release Forge on Modrinth - uses: Kir-Antipov/mc-publish@v3.3 - with: - modrinth-id: ${{ env.MODRINTH_ID }} - modrinth-token: ${{ secrets.MODRINTH_TOKEN }} - - files: output/*forge*.jar - name: ${{ env.MOD_NAME }}-Forge-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }} - version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+forge - version-type: ${{ needs.build.outputs.RELEASE_TYPE }} - changelog-file: output/changelog.md - - loaders: | - forge - neoforge - game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }} - java: ${{ env.JAVA_VERSION }} - - dependencies: | - jei(optional){curseforge:238222}{modrinth:u6dRKJwZ} - rei(optional){curseforge:310111}{modrinth:nfn13YXA} - - cf-forge-release: - name: CurseForge Forge Release - needs: build - runs-on: ubuntu-latest - steps: - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - - name: Extract build artifacts - run: tar -zxvf build.tar.gz - - - name: Release Forge on CurseForge - uses: Kir-Antipov/mc-publish@v3.3 - with: - curseforge-id: ${{ env.CURSEFORGE_ID }} - curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} - - files: output/*forge*.jar - name: ${{ env.MOD_NAME }}-Forge-${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }} - version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }}+forge - version-type: ${{ needs.build.outputs.RELEASE_TYPE }} - changelog-file: output/changelog.md - - loaders: | - forge - neoforge - game-versions: ${{ needs.build.outputs.MINECRAFT_VERSION }} - java: ${{ env.JAVA_VERSION }} - - dependencies: | - jei(optional){curseforge:238222}{modrinth:u6dRKJwZ} - rei(optional){curseforge:310111}{modrinth:nfn13YXA} - - github-release: - name: GitHub Release - needs: build - runs-on: ubuntu-latest - steps: - - name: Download build artifacts - uses: actions/download-artifact@v3 - with: - name: build-artifacts - - - name: Extract build artifacts - run: tar -zxvf build.tar.gz - - - name: Release on GitHub - uses: Kir-Antipov/mc-publish@v3.3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - files: output/*.jar - name: v${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }} - version: ${{ needs.build.outputs.MINECRAFT_VERSION }}-${{ needs.build.outputs.MOD_VERSION }} - version-type: ${{ needs.build.outputs.RELEASE_TYPE }} - changelog-file: output/changelog.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a3365..bdd6511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. +## Unreleased + +- Introduce data-driven like unification. + - `unify.json` moved into sub-folder `unify` to let the user define different unification rules. + - Custom tags, tag ownerships and tag inheritance moved into `tags.json` + - `material` config inside `unify` moved into `placeholders.json` to let the user define multiple different + placeholders + - `reiJeiHide` key in `unify` renamed to `recipeViewerHiding` +- Plugin system for mods to register their own unifiers +- Missing mods used in mod priorities will now be logged +- fixed a bug where stone stratas were not correctly identified (Fabric only) +- ignore `show_notification` and `category` recipe keys by default + ## [0.7.2] - 2023-11-21 ## Added diff --git a/Common/build.gradle.kts b/Common/build.gradle.kts index a992707..7f32622 100644 --- a/Common/build.gradle.kts +++ b/Common/build.gradle.kts @@ -3,10 +3,10 @@ val minecraftVersion: String by project val modPackage: String by project val modId: String by project val modName: String by project -val junitVersion: String by project val fabricLoaderVersion: String by project val jeiVersion: String by project val reiVersion: String by project +val emiVersion: String by project plugins { id("com.github.gmazzo.buildconfig") version "4.0.4" @@ -29,18 +29,13 @@ dependencies { * required here for the @Environment annotations and the mixin dependencies * do NOT use other classes from the Fabric loader */ - modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") + modCompileOnly("net.fabricmc:fabric-loader:$fabricLoaderVersion") - // compile time mods + // compile time modCompileOnly("mezz.jei:jei-$minecraftVersion-common-api:$jeiVersion") // required for jei plugin modCompileOnly("me.shedaniel:RoughlyEnoughItems-api:$reiVersion") // required for rei plugin - - // compile time dependencies compileOnly("me.shedaniel:REIPluginCompatibilities-forge-annotations:9.+") // required to disable rei compat layer - - // tests - testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") + modCompileOnly("dev.emi:emi-xplat-intermediary:$emiVersion+$minecraftVersion:api") // required for emi plugin } buildConfig { @@ -50,9 +45,3 @@ buildConfig { packageName(modPackage) useJavaOutput() } - -//tasks { -// withType { -// useJUnitPlatform() -// } -//} diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java deleted file mode 100644 index 0ea1cb9..0000000 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnified.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.config.Config; -import com.almostreliable.unified.config.ServerConfigs; -import com.almostreliable.unified.config.StartupConfig; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; -import com.almostreliable.unified.utils.TagOwnerships; -import com.almostreliable.unified.utils.TagReloadHandler; -import com.google.common.base.Preconditions; -import com.google.gson.JsonElement; -import net.minecraft.core.Holder; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Map; - -@SuppressWarnings("UtilityClassWithoutPrivateConstructor") -public final class AlmostUnified { - - public static final Logger LOG = LogManager.getLogger(BuildConfig.MOD_NAME); - - @Nullable private static AlmostUnifiedRuntime RUNTIME; - @Nullable private static StartupConfig STARTUP_CONFIG; - - public static boolean isRuntimeLoaded() { - return RUNTIME != null; - } - - public static AlmostUnifiedRuntime getRuntime() { - if (RUNTIME == null) { - return AlmostUnifiedFallbackRuntime.getInstance(); - } - return RUNTIME; - } - - public static StartupConfig getStartupConfig() { - if (STARTUP_CONFIG == null) { - STARTUP_CONFIG = Config.load(StartupConfig.NAME, new StartupConfig.Serializer()); - } - return STARTUP_CONFIG; - } - - public static void onTagLoaderReload(Map>> tags) { - RecipeHandlerFactory recipeHandlerFactory = new RecipeHandlerFactory(); - AlmostUnifiedPlatform.INSTANCE.bindRecipeHandlers(recipeHandlerFactory); - - ServerConfigs serverConfigs = ServerConfigs.load(); - UnifyConfig unifyConfig = serverConfigs.getUnifyConfig(); - - TagReloadHandler.applyCustomTags(unifyConfig); - - TagOwnerships tagOwnerships = new TagOwnerships( - unifyConfig.bakeAndValidateTags(tags), - unifyConfig.getTagOwnerships() - ); - tagOwnerships.applyOwnerships(tags); - - ReplacementData replacementData = loadReplacementData(tags, unifyConfig, tagOwnerships); - - RUNTIME = new AlmostUnifiedRuntimeImpl( - serverConfigs, - replacementData.filteredTagMap(), - replacementData.replacementMap(), - recipeHandlerFactory - ); - } - - public static void onRecipeManagerReload(Map recipes) { - Preconditions.checkNotNull(RUNTIME, "AlmostUnifiedRuntime was not loaded correctly"); - RUNTIME.run(recipes, getStartupConfig().isServerOnly()); - } - - /** - * Loads the required data for the replacement logic. - *

- * This method applies tag inheritance and rebuilds the replacement data if the - * inheritance mutates the tags. - * - * @param tags The vanilla tag map provided by the TagManager - * @param unifyConfig The mod config to use for unifying - * @param tagOwnerships The tag ownerships to apply - * @return The loaded data - */ - private static ReplacementData loadReplacementData(Map>> tags, UnifyConfig unifyConfig, TagOwnerships tagOwnerships) { - ReplacementData replacementData = ReplacementData.load(tags, unifyConfig, tagOwnerships); - var needsRebuild = TagReloadHandler.applyInheritance(unifyConfig, replacementData); - if (needsRebuild) { - return ReplacementData.load(tags, unifyConfig, tagOwnerships); - } - - return replacementData; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedCommon.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedCommon.java new file mode 100644 index 0000000..a43ddf0 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedCommon.java @@ -0,0 +1,44 @@ +package com.almostreliable.unified; + +import com.almostreliable.unified.api.AlmostUnifiedRuntime; +import com.almostreliable.unified.config.Config; +import com.almostreliable.unified.config.StartupConfig; +import com.almostreliable.unified.core.AlmostUnifiedRuntimeImpl; +import com.almostreliable.unified.unification.loot.LootUnification; +import com.almostreliable.unified.utils.CustomLogger; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.Map; + +@SuppressWarnings({ "UtilityClassWithoutPrivateConstructor", "StaticVariableUsedBeforeInitialization" }) +public final class AlmostUnifiedCommon { + + public static final Logger LOGGER = CustomLogger.create(); + public static final StartupConfig STARTUP_CONFIG = Config.load(StartupConfig.NAME, StartupConfig.SERIALIZER); + + @Nullable private static AlmostUnifiedRuntimeImpl RUNTIME; + + @Nullable + public static AlmostUnifiedRuntime getRuntime() { + return RUNTIME; + } + + public static void onTagLoaderReload(VanillaTagWrapper itemTags, VanillaTagWrapper blockTags) { + RUNTIME = AlmostUnifiedRuntimeImpl.create(itemTags, blockTags); + } + + public static void onRecipeManagerReload(Map recipes, HolderLookup.Provider registries) { + Preconditions.checkNotNull(RUNTIME, "AlmostUnifiedRuntime was not loaded correctly"); + + RUNTIME.run(recipes); + LootUnification.unifyLoot(RUNTIME, registries); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java deleted file mode 100644 index b4410ca..0000000 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedFallbackRuntime.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.api.StoneStrataHandler; -import com.almostreliable.unified.config.Config; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.TagMap; -import com.almostreliable.unified.utils.TagOwnerships; -import com.almostreliable.unified.utils.UnifyTag; -import com.google.gson.JsonElement; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import javax.annotation.Nullable; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -// TODO: Implement sync, so it's not just a fallback -public class AlmostUnifiedFallbackRuntime implements AlmostUnifiedRuntime { - - @Nullable private static AlmostUnifiedFallbackRuntime INSTANCE; - - @Nullable private UnifyConfig unifyConfig; - @Nullable private TagMap filteredTagMap; - @Nullable private ReplacementMap replacementMap; - - public static AlmostUnifiedFallbackRuntime getInstance() { - if (INSTANCE == null) { - INSTANCE = new AlmostUnifiedFallbackRuntime(); - INSTANCE.reload(); - } - - return INSTANCE; - } - - public void reload() { - unifyConfig = null; - filteredTagMap = null; - replacementMap = null; - build(); - } - - private void build() { - unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); - Set> unifyTags = unifyConfig.bakeTags(); - filteredTagMap = TagMap.create(unifyTags).filtered($ -> true, unifyConfig::includeItem); - StoneStrataHandler stoneStrataHandler = createStoneStrataHandler(unifyConfig); - TagOwnerships tagOwnerships = new TagOwnerships(unifyTags, unifyConfig.getTagOwnerships()); - replacementMap = new ReplacementMap(unifyConfig, filteredTagMap, stoneStrataHandler, tagOwnerships); - } - - private static StoneStrataHandler createStoneStrataHandler(UnifyConfig config) { - Set> stoneStrataTags = AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(config.getStoneStrata()); - TagMap stoneStrataTagMap = TagMap.create(stoneStrataTags); - return StoneStrataHandler.create(config.getStoneStrata(), stoneStrataTags, stoneStrataTagMap); - } - - @Override - public void run(Map recipes, boolean skipClientTracking) { - // no-op - } - - @Override - public Optional> getFilteredTagMap() { - return Optional.ofNullable(filteredTagMap); - } - - @Override - public Optional getReplacementMap() { - return Optional.ofNullable(replacementMap); - } - - @Override - public Optional getUnifyConfig() { - return Optional.ofNullable(unifyConfig); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java deleted file mode 100644 index a74cc0f..0000000 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedLookupImpl.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.api.AlmostUnifiedLookup; -import com.almostreliable.unified.utils.UnifyTag; -import com.google.auto.service.AutoService; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.ItemLike; - -import javax.annotation.Nullable; -import java.util.Set; -import java.util.stream.Collectors; - -@AutoService(AlmostUnifiedLookup.class) -public class AlmostUnifiedLookupImpl implements AlmostUnifiedLookup { - - @Override - public boolean isLoaded() { - return AlmostUnified.isRuntimeLoaded(); - } - - @Nullable - @Override - public Item getReplacementForItem(ItemLike itemLike) { - ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemLike.asItem()); - return AlmostUnified - .getRuntime() - .getReplacementMap() - .map(rm -> rm.getReplacementForItem(id)) - .flatMap(BuiltInRegistries.ITEM::getOptional) - .orElse(null); - } - - @Nullable - @Override - public Item getPreferredItemForTag(TagKey tag) { - UnifyTag asUnifyTag = UnifyTag.item(tag.location()); - return AlmostUnified - .getRuntime() - .getReplacementMap() - .map(rm -> rm.getPreferredItemForTag(asUnifyTag, $ -> true)) - .flatMap(BuiltInRegistries.ITEM::getOptional) - .orElse(null); - } - - @Nullable - @Override - public TagKey getPreferredTagForItem(ItemLike itemLike) { - ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemLike.asItem()); - return AlmostUnified - .getRuntime() - .getReplacementMap() - .map(rm -> rm.getPreferredTagForItem(id)) - .map(ut -> TagKey.create(Registries.ITEM, ut.location())) - .orElse(null); - } - - @Override - public Set getPotentialItems(TagKey tag) { - UnifyTag asUnifyTag = UnifyTag.item(tag.location()); - return AlmostUnified - .getRuntime() - .getFilteredTagMap() - .map(tagMap -> tagMap - .getEntriesByTag(asUnifyTag) - .stream() - .flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream()) - .collect(Collectors.toSet())) - .orElseGet(Set::of); - } - - @Override - public Set> getConfiguredTags() { - return AlmostUnified - .getRuntime() - .getFilteredTagMap() - .map(tagMap -> tagMap - .getTags() - .stream() - .map(ut -> TagKey.create(Registries.ITEM, ut.location())) - .collect(Collectors.toSet())) - .orElseGet(Set::of); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatform.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatform.java index 681d4ff..ad35bc8 100644 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatform.java +++ b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatform.java @@ -1,17 +1,13 @@ package com.almostreliable.unified; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; -import com.almostreliable.unified.utils.UnifyTag; -import net.minecraft.world.item.Item; - import java.nio.file.Path; -import java.util.List; import java.util.ServiceLoader; -import java.util.Set; public interface AlmostUnifiedPlatform { - AlmostUnifiedPlatform INSTANCE = load(AlmostUnifiedPlatform.class); + AlmostUnifiedPlatform INSTANCE = ServiceLoader.load(AlmostUnifiedPlatform.class) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load platform service.")); /** * Gets the current platform @@ -32,22 +28,10 @@ public interface AlmostUnifiedPlatform { Path getConfigPath(); - Path getLogPath(); - - void bindRecipeHandlers(RecipeHandlerFactory factory); - - Set> getStoneStrataTags(List stoneStrataIds); - - static T load(Class clazz) { - T loadedService = ServiceLoader.load(clazz) - .findFirst() - .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); - AlmostUnified.LOG.debug("Loaded {} for service {}", loadedService, clazz); - return loadedService; - } + Path getDebugLogPath(); enum Platform { - FORGE, + NEO_FORGE, FABRIC } } diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java deleted file mode 100644 index e96ae03..0000000 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntime.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.TagMap; -import com.google.gson.JsonElement; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import java.util.Map; -import java.util.Optional; - -public interface AlmostUnifiedRuntime { - - void run(Map recipes, boolean skipClientTracking); - - Optional> getFilteredTagMap(); - - Optional getReplacementMap(); - - Optional getUnifyConfig(); -} diff --git a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java b/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java deleted file mode 100644 index 02ec99b..0000000 --- a/Common/src/main/java/com/almostreliable/unified/AlmostUnifiedRuntimeImpl.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.config.DebugConfig; -import com.almostreliable.unified.config.DuplicationConfig; -import com.almostreliable.unified.config.ServerConfigs; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.recipe.RecipeDumper; -import com.almostreliable.unified.recipe.RecipeTransformer; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.TagMap; -import com.google.gson.JsonElement; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import java.util.Map; -import java.util.Optional; - -public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime { - - private final UnifyConfig unifyConfig; - private final DuplicationConfig duplicationConfig; - private final DebugConfig debugConfig; - private final TagMap tagMap; - private final ReplacementMap replacementMap; - private final RecipeHandlerFactory recipeHandlerFactory; - - AlmostUnifiedRuntimeImpl( - ServerConfigs configs, - TagMap tagMap, - ReplacementMap repMap, - RecipeHandlerFactory recipeHandlerFactory - ) { - this.unifyConfig = configs.getUnifyConfig(); - this.duplicationConfig = configs.getDupConfig(); - this.debugConfig = configs.getDebugConfig(); - this.tagMap = tagMap; - this.replacementMap = repMap; - this.recipeHandlerFactory = recipeHandlerFactory; - } - - @Override - public void run(Map recipes, boolean skipClientTracking) { - debugConfig.logRecipes(recipes, "recipes_before_unification.txt"); - debugConfig.logUnifyTagDump(tagMap); - - long startTime = System.currentTimeMillis(); - RecipeTransformer.Result result = new RecipeTransformer( - recipeHandlerFactory, - replacementMap, - unifyConfig, - duplicationConfig - ).transformRecipes(recipes, skipClientTracking); - RecipeDumper dumper = new RecipeDumper(result, startTime, System.currentTimeMillis()); - dumper.dump(debugConfig.dumpOverview, debugConfig.dumpUnification, debugConfig.dumpDuplicates); - - debugConfig.logRecipes(recipes, "recipes_after_unification.txt"); - } - - @Override - public Optional> getFilteredTagMap() { - return Optional.of(tagMap); - } - - @Override - public Optional getReplacementMap() { - return Optional.of(replacementMap); - } - - @Override - public Optional getUnifyConfig() { - return Optional.of(unifyConfig); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/ReplacementData.java b/Common/src/main/java/com/almostreliable/unified/ReplacementData.java deleted file mode 100644 index 6f8d714..0000000 --- a/Common/src/main/java/com/almostreliable/unified/ReplacementData.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.api.StoneStrataHandler; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.TagMap; -import com.almostreliable.unified.utils.TagOwnerships; -import net.minecraft.core.Holder; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import java.util.Collection; -import java.util.Map; - -/** - * Holder class for storing all the data needed for replacements in recipes. - * - * @param globalTagMap The global tag map, containing all tags. - * @param filteredTagMap The filtered tag map, containing only the tags that will be used for replacing. Determined by the unify config. - * @param stoneStrataHandler The stone strata handler, used for replacing stone strata. - * @param replacementMap The replacement map, used for replacing items. - */ -public record ReplacementData(TagMap globalTagMap, TagMap filteredTagMap, - StoneStrataHandler stoneStrataHandler, - ReplacementMap replacementMap) { - - public static ReplacementData load(Map>> tags, UnifyConfig unifyConfig, TagOwnerships tagOwnerships) { - var globalTagMap = TagMap.createFromItemTags(tags); - var unifyTags = unifyConfig.bakeAndValidateTags(tags); - var filteredTagMap = globalTagMap.filtered(unifyTags::contains, unifyConfig::includeItem); - - var stoneStrataHandler = StoneStrataHandler.create( - unifyConfig.getStoneStrata(), - AlmostUnifiedPlatform.INSTANCE.getStoneStrataTags(unifyConfig.getStoneStrata()), - globalTagMap - ); - - var replacementMap = new ReplacementMap(unifyConfig, filteredTagMap, stoneStrataHandler, tagOwnerships); - - return new ReplacementData(globalTagMap, filteredTagMap, stoneStrataHandler, replacementMap); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/AlmostUnified.java b/Common/src/main/java/com/almostreliable/unified/api/AlmostUnified.java new file mode 100644 index 0000000..bbc0799 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/AlmostUnified.java @@ -0,0 +1,168 @@ +package com.almostreliable.unified.api; + +import com.almostreliable.unified.api.unification.Placeholders; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.ServiceLoader; +import java.util.Set; + +/** + * The core interface for the Almost Unified API. + *

+ * Use this to get an instance of the {@link AlmostUnifiedRuntime} or to look up unification information. + * + * @since 1.0.0 + */ +public interface AlmostUnified { + + /** + * The default instance of Almost Unified. + *

+ * If unavailable, it will return an empty instance that only returns default values for each method.
+ * This instance is only available on the logical server side. + */ + @SuppressWarnings("InnerClassReferencedViaSubclass") + AlmostUnified INSTANCE = ServiceLoader.load(AlmostUnified.class).findFirst().orElseGet(Empty::new); + + /** + * Returns whether the {@link AlmostUnifiedRuntime} is loaded and ready to be used. + *

+ * The {@link AlmostUnifiedRuntime} is only available on the logical server side. + * + * @return true if the {@link AlmostUnifiedRuntime} is loaded, false otherwise + */ + boolean isRuntimeLoaded(); + + /** + * Returns the instance of the {@link AlmostUnifiedRuntime}. + *

+ * The {@link AlmostUnifiedRuntime} is only available on the logical server side. This method returns null if the + * runtime is not loaded. To check this beforehand, use {@link #isRuntimeLoaded()}. If you are sure the runtime is + * available, you can use {@link #getRuntimeOrThrow()} instead. + * + * @return the {@link AlmostUnifiedRuntime}, or null if the runtime is not loaded + */ + @Nullable + AlmostUnifiedRuntime getRuntime(); + + /** + * Returns the instance of the {@link AlmostUnifiedRuntime}. + *

+ * The {@link AlmostUnifiedRuntime} is only available on the logical server side. This method throws an exception + * if the runtime is not loaded. To check this beforehand, use {@link #isRuntimeLoaded()}. + * + * @return the {@link AlmostUnifiedRuntime} + */ + AlmostUnifiedRuntime getRuntimeOrThrow(); + + /** + * Returns all {@link TagKey}s used for the unification process. + *

+ * The returned collection will only contain tags that have their {@link Placeholders} replaced and have been + * validated. All tags will be unique. + * + * @return the {@link TagKey}s used for the unification, or empty if no tags are used + */ + Collection> getTags(); + + /** + * Returns all item entries for the given {@link TagKey}. + *

+ * The returned collection will only contain entries if the provided {@link TagKey} is a configured unification tag. + * + * @param tag the {@link TagKey} to get the entries for + * @return the item entries for the {@link TagKey}, or empty if not found + */ + Collection getTagEntries(TagKey tag); + + /** + * Returns the relevant {@link TagKey} for the given {@link ItemLike} + *

+ * Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as + * long as there exists a configured unification tag that includes the item. + * + * @param itemLike the {@link ItemLike} to get the relevant {@link TagKey} for + * @return the relevant {@link TagKey}, or null if not found + */ + @Nullable + TagKey getRelevantItemTag(ItemLike itemLike); + + /** + * Returns the target item for the given variant {@link ItemLike}. + *

+ * The target item describes the item with the highest priority among all variant items within a {@link TagKey}. + * It is used to replace the variant items in the unification process.
+ * This method will return null if no configured unification tag exists that includes the given item. + *

+ * If the item is part of a stone variant, it will only check items within the same stone variant. + * + * @param itemLike the variant {@link ItemLike} to get the target item for + * @return the target item, or null if not found + */ + @Nullable + Item getVariantItemTarget(ItemLike itemLike); + + /** + * Returns the target item for the given {@link TagKey}. + *

+ * The target item describes the item with the highest priority among all variant items within a {@link TagKey}. + * It is used to replace the variant items in the unification process.
+ * This method will return null the given {@link TagKey} is not a configured unification tag. + * + * @param tag the {@link TagKey} to get the target item for + * @return the target item, or null if not found + */ + @Nullable + Item getTagTargetItem(TagKey tag); + + class Empty implements AlmostUnified { + + @Override + public boolean isRuntimeLoaded() { + return false; + } + + @Nullable + @Override + public AlmostUnifiedRuntime getRuntime() { + return null; + } + + @Override + public AlmostUnifiedRuntime getRuntimeOrThrow() { + throw new IllegalStateException("runtime is not loaded"); + } + + @Override + public Collection> getTags() { + return Set.of(); + } + + @Override + public Collection getTagEntries(TagKey tag) { + return Set.of(); + } + + @Nullable + @Override + public TagKey getRelevantItemTag(ItemLike itemLike) { + return null; + } + + @Nullable + @Override + public Item getVariantItemTarget(ItemLike itemLike) { + return null; + } + + @Nullable + @Override + public Item getTagTargetItem(TagKey tag) { + return null; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedLookup.java b/Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedLookup.java deleted file mode 100644 index a2d0fa0..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedLookup.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.almostreliable.unified.api; - -import net.minecraft.tags.TagKey; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.ItemLike; - -import javax.annotation.Nullable; -import java.util.ServiceLoader; -import java.util.Set; - -public interface AlmostUnifiedLookup { - - AlmostUnifiedLookup INSTANCE = ServiceLoader.load(AlmostUnifiedLookup.class).findFirst().orElseGet(Empty::new); - - boolean isLoaded(); - - /** - * Returns the replacement item for a given {@link ItemLike}. Will return null if no configured - * tag exists that includes the item. - *

- * If the item is part of some stone strata, it will only check items within the same stone strata.
- * => e.g. "modid:deepslate_foo_ore" would not return "prio_modid:foo_ore". - * - * @param itemLike The item-like to find the replacement for - * @return The replacement item or null if there is no replacement - */ - @Nullable - Item getReplacementForItem(ItemLike itemLike); - - /** - * Returns the preferred item for a given {@link TagKey}. Will return null if no configured - * tag exists that includes the item. - *

- * The preferred item is selected according to mod priorities, but it's possible to set a - * fixed override in the config. - * - * @param tag The tag to find the preferred item for - * @return The preferred item or null if there is no preferred item - */ - @Nullable - Item getPreferredItemForTag(TagKey tag); - - /** - * Returns the preferred tag for a given {@link ItemLike} Will return null if no configured - * tag exists that includes the item. - * - * @param itemLike The item-like to find the preferred tag for - * @return The preferred tag or null if there is no preferred tag - */ - @Nullable - TagKey getPreferredTagForItem(ItemLike itemLike); - - /** - * Returns all potential items which are part of a given tag. - *

- * Tags are only considered if they are part of the config, - * otherwise, an empty set is always returned. - * - * @param tag The tag to find the potential items for - * @return The potential items or an empty set if there are no potential items - */ - Set getPotentialItems(TagKey tag); - - /** - * Returns all configured tags. - * - * @return The configured tags - */ - Set> getConfiguredTags(); - - class Empty implements AlmostUnifiedLookup { - - @Override - public boolean isLoaded() { - return false; - } - - @Nullable - @Override - public Item getReplacementForItem(ItemLike itemLike) { - return null; - } - - @Nullable - @Override - public Item getPreferredItemForTag(TagKey tag) { - return null; - } - - @Nullable - @Override - public TagKey getPreferredTagForItem(ItemLike itemLike) { - return null; - } - - @Override - public Set getPotentialItems(TagKey tag) { - return Set.of(); - } - - @Override - public Set> getConfiguredTags() { - return Set.of(); - } - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedRuntime.java b/Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedRuntime.java new file mode 100644 index 0000000..ebb3897 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/AlmostUnifiedRuntime.java @@ -0,0 +1,67 @@ +package com.almostreliable.unified.api; + +import com.almostreliable.unified.api.unification.Placeholders; +import com.almostreliable.unified.api.unification.TagSubstitutions; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.api.unification.UnificationSettings; + +import javax.annotation.Nullable; +import java.util.Collection; + +/** + * The runtime is the core of the Almost Unified mod.
+ * It stores all required information about tags, recipes, unification settings, and configs. + *

+ * The runtime is reconstructed every time the game reloads. Within the reconstruction process, all configs are reloaded, + * plugin unifiers are collected, tag changes are applied, and all lookups are recreated. + * + * @since 1.0.0 + */ +public interface AlmostUnifiedRuntime { + + /** + * Returns a composition of all {@link UnificationSettings}s. + *

+ * Because {@link UnificationSettings}s include config-specific settings, and are thus not composable, the + * composition is returned as a {@link UnificationLookup}. + * + * @return the {@link UnificationSettings} composition as a {@link UnificationLookup} + */ + UnificationLookup getUnificationLookup(); + + /** + * Returns an unmodifiable collection of all {@link UnificationSettings}s. + * + * @return the {@link UnificationSettings} collection + */ + Collection getUnificationSettings(); + + /** + * Returns the {@link UnificationSettings} with the given name. + *

+ * The name of a {@link UnificationSettings} is the name of the config file it was created from. + * + * @param name the name of the {@link UnificationSettings} + * @return the {@link UnificationSettings} with the given name or null if not found + */ + @Nullable + UnificationSettings getUnificationSettings(String name); + + /** + * Returns the {@link TagSubstitutions} instance. + *

+ * {@link TagSubstitutions} are defined in the {@code TagConfig}. + * + * @return the {@link TagSubstitutions} + */ + TagSubstitutions getTagSubstitutions(); + + /** + * Returns the {@link Placeholders} instance. + *

+ * {@link Placeholders} are defined in the {@code PlaceholderConfig}. + * + * @return the {@link Placeholders} + */ + Placeholders getPlaceholders(); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/ModConstants.java b/Common/src/main/java/com/almostreliable/unified/api/ModConstants.java deleted file mode 100644 index 7df8297..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/ModConstants.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.almostreliable.unified.api; - -@SuppressWarnings("SpellCheckingInspection") -public final class ModConstants { - // recipe viewers - public static final String JEI = "jei"; - public static final String REI = "roughlyenoughitems"; - - // custom unify handlers - public static final String AD_ASTRA = "ad_astra"; - public static final String AMETHYST_IMBUEMENT = "amethyst_imbuement"; - public static final String ARS_CREO = "ars_creo"; - public static final String ARS_ELEMENTAL = "ars_elemental"; - public static final String ARS_NOUVEAU = "ars_nouveau"; - public static final String ARS_SCALAES = "ars_scalaes"; - public static final String CYCLIC = "cyclic"; - public static final String ENDER_IO = "enderio"; - public static final String GREGTECH_MODERN = "gtceu"; - public static final String IMMERSIVE_ENGINEERING = "immersiveengineering"; - public static final String INTEGRATED_DYNAMICS = "integrateddynamics"; - public static final String MEKANISM = "mekanism"; - public static final String MODERN_INDUSTRIALIZATION = "modern_industrialization"; - - private ModConstants() {} -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java b/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java deleted file mode 100644 index 8d7df6a..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/StoneStrataHandler.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.almostreliable.unified.api; - -import com.almostreliable.unified.AlmostUnifiedPlatform; -import com.almostreliable.unified.utils.TagMap; -import com.almostreliable.unified.utils.UnifyTag; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import java.util.*; -import java.util.regex.Pattern; - -public final class StoneStrataHandler { - - private final List stoneStrata; - private final Pattern tagMatcher; - private final TagMap stoneStrataTagMap; - - // don't clear the caches, so they are available for the runtime and KubeJS binding - // the runtime holding this handler is automatically yeeted on reload - private final Map, Boolean> stoneStrataTagCache; - private final Map stoneStrataCache; - - private StoneStrataHandler(List stoneStrata, Pattern tagMatcher, TagMap stoneStrataTagMap) { - this.stoneStrata = createSortedStoneStrata(stoneStrata); - this.tagMatcher = tagMatcher; - this.stoneStrataTagMap = stoneStrataTagMap; - this.stoneStrataTagCache = new HashMap<>(); - this.stoneStrataCache = new HashMap<>(); - } - - /** - * Returns the stone strata list sorted from longest to shortest. - *

- * This is required to ensure that the longest strata is returned and no sub-matches happen.
- * Example: "nether" and "blue_nether" would both match "nether" if the list is not sorted. - * - * @param stoneStrata The stone strata list to sort. - * @return The sorted stone strata list. - */ - private static List createSortedStoneStrata(List stoneStrata) { - return stoneStrata.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList(); - } - - public static StoneStrataHandler create(List stoneStrataIds, Set> stoneStrataTags, TagMap tagMap) { - var stoneStrataTagMap = tagMap.filtered(stoneStrataTags::contains, item -> true); - Pattern tagMatcher = Pattern.compile(switch (AlmostUnifiedPlatform.INSTANCE.getPlatform()) { - case FORGE -> "forge:ores/.+"; - case FABRIC -> "(c:ores/.+|c:.+_ores)"; - }); - return new StoneStrataHandler(stoneStrataIds, tagMatcher, stoneStrataTagMap); - } - - /** - * Returns the stone strata from the given item. Assumes that the item has a stone strata tag. - * Use {@link #isStoneStrataTag(UnifyTag)} to ensure this requirement. - * - * @param item The item to get the stone strata from. - * @return The stone strata of the item. Clean stone strata returns an empty string for later sorting as a - * fallback variant. - */ - public String getStoneStrata(ResourceLocation item) { - return stoneStrataCache.computeIfAbsent(item, this::computeStoneStrata); - } - - /** - * Implementation logic for {@link #getStoneStrata(ResourceLocation)}. - * - * @param item The item to get the stone strata from. - * @return The stone strata of the item. Clean stone strata returns an empty string for later sorting as a - * fallback variant. - */ - private String computeStoneStrata(ResourceLocation item) { - String strata = stoneStrataTagMap - .getTagsByEntry(item) - .stream() - .findFirst() - .map(UnifyTag::location) - .map(ResourceLocation::toString) - .map(s -> { - int i = s.lastIndexOf('/'); - return i == -1 ? null : s.substring(i + 1); - }) - .orElse(null); - - if (strata != null) { - if (strata.equals("stone")) { - return ""; - } - return strata; - } - - for (String stone : stoneStrata) { - if (item.getPath().contains(stone + "_")) { - if (stone.equals("stone")) { - return ""; - } - return stone; - } - } - - return ""; - } - - public boolean isStoneStrataTag(UnifyTag tag) { - return stoneStrataTagCache.computeIfAbsent(tag, t -> tagMatcher.matcher(t.location().toString()).matches()); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/constant/ModConstants.java b/Common/src/main/java/com/almostreliable/unified/api/constant/ModConstants.java new file mode 100644 index 0000000..c066a3b --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/constant/ModConstants.java @@ -0,0 +1,31 @@ +package com.almostreliable.unified.api.constant; + +import com.almostreliable.unified.BuildConfig; + +@SuppressWarnings({ "SpellCheckingInspection", "StaticMethodOnlyUsedInOneClass" }) +public interface ModConstants { + + String ALMOST_UNIFIED = BuildConfig.MOD_ID; + + // recipe viewer mods + String JEI = "jei"; + String REI = "roughlyenoughitems"; + String EMI = "emi"; + + // unify handler mods + String AMETHYST_IMBUEMENT = "amethyst_imbuement"; + String ARS_CREO = "ars_creo"; + String ARS_ELEMENTAL = "ars_elemental"; + String ARS_NOUVEAU = "ars_nouveau"; + String ARS_SCALAES = "ars_scalaes"; + String CYCLIC = "cyclic"; + String ENDER_IO = "enderio"; + String GREGTECH_MODERN = "gtceu"; + String IMMERSIVE_ENGINEERING = "immersiveengineering"; + String INTEGRATED_DYNAMICS = "integrateddynamics"; + String MEKANISM = "mekanism"; + String MODERN_INDUSTRIALIZATION = "modern_industrialization"; + String OCCULTISM = "occultism"; + String PRODUCTIVE_TREES = "productivetrees"; + String THEURGY = "theurgy"; +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/constant/RecipeConstants.java b/Common/src/main/java/com/almostreliable/unified/api/constant/RecipeConstants.java new file mode 100644 index 0000000..e7da3d9 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/constant/RecipeConstants.java @@ -0,0 +1,52 @@ +package com.almostreliable.unified.api.constant; + +@SuppressWarnings("StaticMethodOnlyUsedInOneClass") +public interface RecipeConstants { + + // inputs + String ITEM = "item"; + String TAG = "tag"; + String INPUT = "input"; + String INPUTS = "inputs"; + String INGREDIENT = "ingredient"; + String INGREDIENTS = "ingredients"; + String INPUT_ITEMS = "inputItems"; + String CATALYST = "catalyst"; + + // outputs + String OUTPUT = "output"; + String OUTPUTS = "outputs"; + String RESULT = "result"; + String RESULTS = "results"; + String OUTPUT_ITEMS = "outputItems"; + + // inner keys + String VALUE = "value"; + String BASE = "base"; + String ID = "id"; + + // defaults + String[] DEFAULT_INPUT_KEYS = { + INPUT, + INPUTS, + INGREDIENT, + INGREDIENTS, + INPUT_ITEMS + }; + String[] DEFAULT_INPUT_INNER_KEYS = { + VALUE, + BASE, + INGREDIENT + }; + String[] DEFAULT_OUTPUT_KEYS = { + OUTPUT, + OUTPUTS, + RESULT, + RESULTS, + OUTPUT_ITEMS + }; + String[] DEFAULT_OUTPUT_INNER_KEYS = { + ITEM, + INGREDIENT + }; +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/unifier/package-info.java b/Common/src/main/java/com/almostreliable/unified/api/constant/package-info.java similarity index 77% rename from Common/src/main/java/com/almostreliable/unified/mixin/unifier/package-info.java rename to Common/src/main/java/com/almostreliable/unified/api/constant/package-info.java index 8372ae3..8aa6793 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/unifier/package-info.java +++ b/Common/src/main/java/com/almostreliable/unified/api/constant/package-info.java @@ -1,5 +1,5 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package com.almostreliable.unified.mixin.unifier; +package com.almostreliable.unified.api.constant; import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedNeoPlugin.java b/Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedNeoPlugin.java new file mode 100644 index 0000000..8fd0782 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedNeoPlugin.java @@ -0,0 +1,8 @@ +package com.almostreliable.unified.api.plugin; + +/** + * Annotation to use with {@link AlmostUnifiedPlugin} for NeoForge class discovery. + * + * @since 1.0.0 + */ +public @interface AlmostUnifiedNeoPlugin {} diff --git a/Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedPlugin.java b/Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedPlugin.java new file mode 100644 index 0000000..02c928b --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/plugin/AlmostUnifiedPlugin.java @@ -0,0 +1,36 @@ +package com.almostreliable.unified.api.plugin; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import net.minecraft.resources.ResourceLocation; + +/** + * Implemented by plugins that wish to register their own {@link RecipeUnifier}s. + *

+ * NeoForge plugins should attach the {@link AlmostUnifiedNeoPlugin} annotation for discovery.
+ * Fabric plugins should use the {@code almostunified} entrypoint. + * + * @since 1.0.0 + */ +public interface AlmostUnifiedPlugin { + + /** + * Returns the identifier of the plugin. + *

+ * If your mod has multiple plugins for different modules, make + * sure they are unique. + *

+ * If you register a recipe unifier although Almost Unified already + * ships a recipe unifier for your recipes, yours will take precedence. + * + * @return the plugin id + */ + ResourceLocation getPluginId(); + + /** + * Allows registration of custom {@link RecipeUnifier}s. + * + * @param registry the {@link RecipeUnifierRegistry} to register with + */ + default void registerRecipeUnifiers(RecipeUnifierRegistry registry) {} +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/recipe/package-info.java b/Common/src/main/java/com/almostreliable/unified/api/plugin/package-info.java similarity index 78% rename from Common/src/main/java/com/almostreliable/unified/api/recipe/package-info.java rename to Common/src/main/java/com/almostreliable/unified/api/plugin/package-info.java index 31d273a..9385e19 100644 --- a/Common/src/main/java/com/almostreliable/unified/api/recipe/package-info.java +++ b/Common/src/main/java/com/almostreliable/unified/api/plugin/package-info.java @@ -1,5 +1,5 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package com.almostreliable.unified.api.recipe; +package com.almostreliable.unified.api.plugin; import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java b/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java deleted file mode 100644 index a2c9500..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeConstants.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.almostreliable.unified.api.recipe; - -public final class RecipeConstants { - // common inputs - public static final String ITEM = "item"; - public static final String TAG = "tag"; - public static final String INPUT = "input"; - public static final String INPUTS = "inputs"; - public static final String INGREDIENT = "ingredient"; - public static final String INGREDIENTS = "ingredients"; - public static final String INPUT_ITEMS = "inputItems"; - public static final String CATALYST = "catalyst"; - - // common outputs - public static final String OUTPUT = "output"; - public static final String OUTPUTS = "outputs"; - public static final String RESULT = "result"; - public static final String RESULTS = "results"; - public static final String OUTPUT_ITEMS = "outputItems"; - - // inner keys - public static final String VALUE = "value"; - public static final String BASE = "base"; - - // ars nouveau - public static final String PEDESTAL_ITEMS = "pedestalItems"; - public static final String REAGENT = "reagent"; - - // gregtech modern - public static final String TICK_INPUTS = "tickInputs"; - public static final String TICK_OUTPUTS = "tickOutputs"; - - // immersive engineering - public static final String INPUT_0 = "input0"; - public static final String INPUT_1 = "input1"; - public static final String ADDITIVES = "additives"; - public static final String SECONDARIES = "secondaries"; - public static final String SLAG = "slag"; - - // mekanism - public static final String MAIN_INPUT = "mainInput"; - public static final String MAIN_OUTPUT = "mainOutput"; - public static final String ITEM_INPUT = "itemInput"; - public static final String ITEM_OUTPUT = "itemOutput"; - public static final String SECONDARY_OUTPUT = "secondaryOutput"; - - // modern industrialization - public static final String ITEM_INPUTS = "item_inputs"; - public static final String ITEM_OUTPUTS = "item_outputs"; - - // cyclic - public static final String BONUS = "bonus"; - - private RecipeConstants() {} -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeContext.java b/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeContext.java deleted file mode 100644 index 754abe2..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeContext.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.almostreliable.unified.api.recipe; - -import com.almostreliable.unified.utils.UnifyTag; -import com.google.gson.JsonElement; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import javax.annotation.Nullable; -import java.util.function.Predicate; - -public interface RecipeContext { - - @Nullable - ResourceLocation getReplacementForItem(@Nullable ResourceLocation item); - - @Nullable - ResourceLocation getPreferredItemForTag(@Nullable UnifyTag tag, Predicate filter); - - @Nullable - UnifyTag getPreferredTagForItem(@Nullable ResourceLocation item); - - @Nullable - JsonElement createIngredientReplacement(@Nullable JsonElement element); - - @Nullable - JsonElement createResultReplacement(@Nullable JsonElement element); - - @Nullable - JsonElement createResultReplacement(@Nullable JsonElement element, boolean includeTagCheck, String... lookupKeys); - - ResourceLocation getType(); - - boolean hasProperty(String property); - - default String getModId() { - return getType().getNamespace(); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifier.java deleted file mode 100644 index 0b672b6..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifier.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.almostreliable.unified.api.recipe; - -public interface RecipeUnifier { - void collectUnifier(RecipeUnifierBuilder builder); -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifierBuilder.java b/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifierBuilder.java deleted file mode 100644 index 6827de0..0000000 --- a/Common/src/main/java/com/almostreliable/unified/api/recipe/RecipeUnifierBuilder.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.almostreliable.unified.api.recipe; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import java.util.function.BiFunction; - -public interface RecipeUnifierBuilder { - - void forEachObject(String property, BiFunction consumer); - - void put(String property, BiFunction consumer); - - void put(String property, Class type, BiFunction consumer); -} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/ModPriorities.java b/Common/src/main/java/com/almostreliable/unified/api/unification/ModPriorities.java new file mode 100644 index 0000000..5577e0f --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/ModPriorities.java @@ -0,0 +1,70 @@ +package com.almostreliable.unified.api.unification; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Helper for handling mod priorities. + *

+ * Mod priorities are used to choose the target items in the unification process.
+ * If a tag contains multiple items from different mods, the priority defines which item is chosen first. Priority + * is sorted from highest to lowest. All unlisted mods have less priority than all listed mods. + *

+ * Priority overrides allow overriding the priority mod for specific tags.
+ * When a priority override is specified for a tag, the mod priorities will be ignored. + * + * @since 1.0.0 + */ +public interface ModPriorities extends Iterable { + + /** + * Returns the priority override of the given tag. + *

+ * This method returns the mod id if a priority override is configured for the given tag. If you want to resolve + * the tag to an item, use {@link #findPriorityOverrideItem(TagKey, List)} or + * {@link #findTargetItem(TagKey, List)} instead. + * + * @param tag the tag to get the priority override for + * @return the priority override, or null if no override exists + */ + @Nullable + String getPriorityOverride(TagKey tag); + + /** + * Returns the priority override item of the given tag contained in the list of potential items. + *

+ * This method returns the item if a priority override is configured for the given tag. If you want to resolve the + * tag to an item by also using the mod priorities, use {@link #findTargetItem(TagKey, List)} instead. + * + * @param tag the tag to get the priority override item for + * @param items the list of potential items, sorted from shortest to longest id + * @return the priority override item, or null if no override exists + */ + @Nullable + UnificationEntry findPriorityOverrideItem(TagKey tag, List> items); + + /** + * Returns the target item of the given tag contained in the list of potential items. + *

+ * The item is chosen according to the priority overrides first if available. If no priority override is configured, + * the item is chosen according to the mod priorities. + *

+ * This method can return null if no override exists, and the potential items only include items with namespaces + * that are not part of the mod priorities. + * + * @param tag the tag to get the target item for + * @param items the list of potential items, sorted from shortest to longest id + * @return the target item of the given tag, or null if no target item could be found + */ + @Nullable + UnificationEntry findTargetItem(TagKey tag, List> items); + + default Stream stream() { + return StreamSupport.stream(spliterator(), false); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/Placeholders.java b/Common/src/main/java/com/almostreliable/unified/api/unification/Placeholders.java new file mode 100644 index 0000000..ba75f38 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/Placeholders.java @@ -0,0 +1,52 @@ +package com.almostreliable.unified.api.unification; + +import java.util.Collection; +import java.util.function.BiConsumer; + +/** + * Helper for handling placeholders in configs. + *

+ * Placeholders are used to replace specific patterns in config values with a set of values to easily cover all possible + * combinations of all values. Placeholders are in the format of {@code {placeholder}}. + * + * @since 1.0.0 + */ +public interface Placeholders { + + /** + * Applies the placeholders to given string. + *

+ * The given string is expected to contain an arbitrary number of placeholders or no placeholders at all. + *

+ * This method replaces all contained placeholders with all combinations of possible values. If string doesn't + * contain any placeholders, the same string will be returned as the only element in the returned collection. + * + * @param str the string to apply the placeholders to + * @return a collection containing all combinations of applied placeholder values + */ + Collection apply(String str); + + /** + * Returns all placeholders as a collection. + * + * @return a collection containing all placeholders + */ + Collection getPlaceholders(); + + /** + * Returns all possible replacements for given placeholder. + *

+ * The possible replacement values are ensured to be unique. + * + * @param placeholder the placeholder to get replacements for + * @return a collection containing all possible replacements for the given placeholder + */ + Collection getReplacements(String placeholder); + + /** + * Passes each placeholder and its possible replacements to the given consumer. + * + * @param consumer the consumer to pass each placeholder and its possible replacements to + */ + void forEach(BiConsumer> consumer); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/StoneVariants.java b/Common/src/main/java/com/almostreliable/unified/api/unification/StoneVariants.java new file mode 100644 index 0000000..5a2e00d --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/StoneVariants.java @@ -0,0 +1,40 @@ +package com.almostreliable.unified.api.unification; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +/** + * Helper to get the stone variant of an item. + *

+ * Upon creation, this lookup will try to fetch the stone variant from the + * {@code c:ores_in_ground} tag. If the tag is present, it will always take priority. + *

+ * As a fallback, it will lazily try to fetch the stone variant from the item or + * the respective block id. + * + * @since 1.0.0 + */ +public interface StoneVariants { + + /** + * Returns the stone variant for the given item. + *

+ * This assumes that the item has a valid ore tag.
+ * Use {@link #isOreTag(TagKey)} to ensure this requirement. + *

+ * If the detected variant is stone, an empty string will be returned. + * + * @param item the item to get the stone variant from + * @return the stone variant of the item + */ + String getStoneVariant(ResourceLocation item); + + /** + * Checks if the given tag is an ore tag. + * + * @param tag the tag to check + * @return true if the tag is an ore tag, false otherwise + */ + boolean isOreTag(TagKey tag); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/TagSubstitutions.java b/Common/src/main/java/com/almostreliable/unified/api/unification/TagSubstitutions.java new file mode 100644 index 0000000..527a3c5 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/TagSubstitutions.java @@ -0,0 +1,51 @@ +package com.almostreliable.unified.api.unification; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Set; + +/** + * Helper for tracking tag substitutions. + *

+ * The tag substitutions system allows converting tags (replaced tags) to other tags (substitute tags).
+ * The system copies all entries of the replaced tags to their respective substitute tags. After that, it replaces + * all occurrences of the replaced tags with their substitute tags in all recipes. + *

+ * Example:
+ * If we define the following entry {@code minecraft:logs -> minecraft:planks}, any recipes making use of the tag + * {@code minecraft:logs} will use the {@code minecraft:planks} tag instead. + *

+ * This can be useful when mods use different tag conventions like {@code c:ingots/iron} and {@code c:iron_ingots}. + * + * @since 1.0.0 + */ +public interface TagSubstitutions { + + /** + * Returns the substitute tag for the provided replaced tag. + * + * @param replacedTag the replaced tag to get the substitute for + * @return the substitute tag or null if the provided tag is not a valid configured replaced tag + */ + @Nullable + TagKey getSubstituteTag(TagKey replacedTag); + + /** + * Returns all replaced tags for the provided substitute tag. + * + * @param substituteTag the substitute tag to get the replaced tags for + * @return a collection of all replaced tags for the provided substitute tag or an empty collection if the + * provided tag is not a valid configured substitute tag + */ + Collection> getReplacedTags(TagKey substituteTag); + + /** + * Returns all valid configured replaced tags for all substitute tags. + * + * @return a collection of all valid configured replaced tags + */ + Set> getReplacedTags(); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationEntry.java b/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationEntry.java new file mode 100644 index 0000000..8281727 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationEntry.java @@ -0,0 +1,57 @@ +package com.almostreliable.unified.api.unification; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; + +/** + * Helper to abstract a single entry used in unification. + *

+ * This helper allows easy access to registry information while also offering utility methods. + * + * @param the type of the entry + * @since 1.0.0 + */ +public interface UnificationEntry { + + /** + * Returns the {@link ResourceKey} this entry is bound to in the {@link Registry}. + * + * @return the {@link ResourceKey} this entry is bound to + */ + ResourceKey key(); + + /** + * Returns the id of this entry. + *

+ * The id is the {@link ResourceLocation} of the entry in the {@link Registry}. + * + * @return the id of this entry + */ + ResourceLocation id(); + + /** + * Returns the raw value of this entry. + * + * @return the raw value + */ + T value(); + + /** + * Returns the tag of this entry. + *

+ * The tag represents the relevant tag used for the unification. Each entry can only have a single unification tag. + * + * @return the tag + */ + TagKey tag(); + + /** + * Returns the value as a {@link Holder.Reference}. + * + * @return the value holder + */ + Holder.Reference asHolderOrThrow(); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationLookup.java b/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationLookup.java new file mode 100644 index 0000000..48c8035 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationLookup.java @@ -0,0 +1,242 @@ +package com.almostreliable.unified.api.unification; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.function.Predicate; + +/** + * Interface exposing composable unification information for a single unification config. + *

+ * There exists one instance for each config.
+ * A unification lookup only exposes composable information. The composition of all lookups is used to check global + * unification information. For example, when retrieving the replacement of a specific item. The composition can't + * expose internal information such as mod priorities. + *

+ * If a lookup with exposed configuration is required, use {@link UnificationSettings} instead. + * + * @since 1.0.0 + */ +public interface UnificationLookup { + + /** + * Returns all {@link TagKey}s used for the unification process of the config this lookup is for. + *

+ * The returned collection will only contain tags that have their {@link Placeholders} replaced and have been + * validated. All tags will be unique. + * + * @return the {@link TagKey}s used for the unification, or empty if no tags are used + */ + Collection> getTags(); + + /** + * Returns all {@link UnificationEntry}s for the given {@link TagKey}. + *

+ * The returned collection will only contain entries if the provided {@link TagKey} is part of the config this + * lookup is for. + * + * @param tag the {@link TagKey} to get the entries for + * @return the {@link UnificationEntry}s for the {@link TagKey}, or empty if no {@link UnificationEntry}s are found + */ + Collection> getTagEntries(TagKey tag); + + /** + * Returns the {@link UnificationEntry} for the given item id. + *

+ * If the config this lookup is for doesn't cover the {@link Item}, null is returned. + * + * @param item the item id to get the {@link UnificationEntry} for + * @return the {@link UnificationEntry} for the item id, or null if no {@link UnificationEntry} is found + */ + @Nullable + UnificationEntry getItemEntry(ResourceLocation item); + + /** + * Returns the {@link UnificationEntry} for the given {@link Item}. + *

+ * If the config this lookup is for doesn't cover the {@link Item}, null is returned. + * + * @param item the {@link Item} to get the {@link UnificationEntry} for + * @return the {@link UnificationEntry} for the {@link Item}, or null if no {@link UnificationEntry} is found + */ + @Nullable + default UnificationEntry getItemEntry(Item item) { + return getItemEntry(BuiltInRegistries.ITEM.getKey(item)); + } + + /** + * Returns the {@link UnificationEntry} for the given item {@link Holder}. + *

+ * If the config this lookup is for doesn't cover the {@link Item}, null is returned. + * + * @param item the item {@link Holder} to get the {@link UnificationEntry} for + * @return the {@link UnificationEntry} for the item {@link Holder}, or null if no {@link UnificationEntry} is found + */ + @Nullable + default UnificationEntry getItemEntry(Holder item) { + return getItemEntry(item.value()); + } + + /** + * Returns the relevant {@link TagKey} for the given item id. + *

+ * Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as + * long as the config this lookup is for covers the {@link Item}. + * + * @param item the item id to get the relevant {@link TagKey} for + * @return the relevant {@link TagKey} for the item id, or null if no relevant {@link TagKey} is found + */ + @Nullable + TagKey getRelevantItemTag(ResourceLocation item); + + /** + * Returns the relevant {@link TagKey} for the given {@link Item}. + *

+ * Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as + * long as the config this lookup is for covers the {@link Item}. + * + * @param item the {@link Item} to get the relevant {@link TagKey} for + * @return the relevant {@link TagKey} for the {@link Item}, or null if no relevant {@link TagKey} is found + */ + @Nullable + default TagKey getRelevantItemTag(Item item) { + return getRelevantItemTag(BuiltInRegistries.ITEM.getKey(item)); + } + + /** + * Returns the relevant {@link TagKey} for the given item {@link Holder}. + *

+ * Since an item can only have a single relevant tag, this method is guaranteed to return a single {@link TagKey} as + * long as the config this lookup is for covers the {@link Item}. + * + * @param item the item {@link Holder} to get the relevant {@link TagKey} for + * @return the relevant {@link TagKey} for the item {@link Holder}, or null if no relevant {@link TagKey} is found + */ + @Nullable + default TagKey getRelevantItemTag(Holder item) { + return getRelevantItemTag(item.value()); + } + + /** + * Returns the target item {@link UnificationEntry} for the given variant item id. + *

+ * The target item describes the item with the highest priority among all variant items within a tag. It is used + * to replace the variant items in the unification process.
+ * This method will return null if the config this lookup is for doesn't cover a unification tag that includes + * the given item. + *

+ * If the item is part of a stone variant, it will only check items within the same stone variant. + * + * @param item the variant item id to get the target {@link UnificationEntry} for + * @return the target {@link UnificationEntry} for the variant item id, or null if no target + * {@link UnificationEntry} is found + */ + @Nullable + UnificationEntry getVariantItemTarget(ResourceLocation item); + + /** + * Returns the target item {@link UnificationEntry} for the given variant {@link Item}. + *

+ * The target item describes the item with the highest priority among all variant items within a tag. It is used + * to replace the variant items in the unification process.
+ * This method will return null if the config this lookup is for doesn't cover a unification tag that includes + * the given item. + *

+ * If the item is part of a stone variant, it will only check items within the same stone variant. + * + * @param item the variant {@link Item} to get the target {@link UnificationEntry} for + * @return the target {@link UnificationEntry} for the variant {@link Item}, or null if no target + * {@link UnificationEntry} is found + */ + @Nullable + default UnificationEntry getVariantItemTarget(Item item) { + return getVariantItemTarget(BuiltInRegistries.ITEM.getKey(item)); + } + + /** + * Returns the target {@link UnificationEntry} for the given variant item {@link Holder}. + *

+ * The target item describes the item with the highest priority among all variant items within a tag. It is used + * to replace the variant items in the unification process.
+ * This method will return null if the config this lookup is for doesn't cover a unification tag that includes + * the given item. + *

+ * If the item is part of a stone variant, it will only check items within the same stone variant. + * + * @param item the variant item {@link Holder} to get the target {@link UnificationEntry} for + * @return the target {@link UnificationEntry} for the variant item {@link Holder}, or null if no target + * {@link UnificationEntry} is found + */ + @Nullable + default UnificationEntry getVariantItemTarget(Holder item) { + return getVariantItemTarget(item.value()); + } + + /** + * Returns the target {@link UnificationEntry} for the given variant {@link UnificationEntry}. + *

+ * The target item describes the item with the highest priority among all variant items within a {@link TagKey}. + * It is used to replace the variant items in the unification process.
+ * This method will return null if the config this lookup is for doesn't cover a unification tag that includes + * the given item. + *

+ * If the item is part of a stone variant, it will only check items within the same stone variant. + * + * @param item the variant {@link UnificationEntry} to get the target {@link UnificationEntry} for + * @return the target {@link UnificationEntry} for the variant {@link UnificationEntry}, or null if no target + * {@link UnificationEntry} is found + */ + @Nullable + default UnificationEntry getVariantItemTarget(UnificationEntry item) { + return getVariantItemTarget(item.asHolderOrThrow()); + } + + /** + * Returns the target {@link UnificationEntry} for the given {@link TagKey} that matches the given filter. + *

+ * The target item describes the item with the highest priority among all variant items within a {@link TagKey}. + * It is used to replace the variant items in the unification process.
+ * If the config this lookup is for doesn't cover the {@link TagKey}, null is returned. + * + * @param tag the {@link TagKey} to get the target {@link UnificationEntry} for + * @return the target {@link UnificationEntry} for the {@link TagKey}, or null if no target + * {@link UnificationEntry} is found + */ + @Nullable + UnificationEntry getTagTargetItem(TagKey tag, Predicate itemFilter); + + /** + * Returns the target {@link UnificationEntry} for the given {@link TagKey}. + *

+ * The target item describes the item with the highest priority among all variant items within a {@link TagKey}. + * It is used to replace the variant items in the unification process.
+ * If the config this lookup is for doesn't cover the {@link TagKey}, null is returned. + * + * @param tag the {@link TagKey} to get the target {@link UnificationEntry} for + * @return the target {@link UnificationEntry} for the {@link TagKey}, or null if no target + * {@link UnificationEntry} is found + */ + @Nullable + default UnificationEntry getTagTargetItem(TagKey tag) { + return getTagTargetItem(tag, $ -> true); + } + + /** + * Returns whether the given {@link ItemStack} is part of any tags the given {@link Ingredient} points to. + *

+ * To check this, this method fetches all unification tags of the {@link Item}s within the given {@link Ingredient} + * and checks whether the given {@link ItemStack} is part of them. + * + * @param ingredient the {@link Ingredient} to check to get the relevant tags for + * @param item the {@link ItemStack} to check + * @return whether the given {@link ItemStack} is part of any tags the given {@link Ingredient} points to + */ + boolean isUnifiedIngredientItem(Ingredient ingredient, ItemStack item); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationSettings.java b/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationSettings.java new file mode 100644 index 0000000..b62f5bb --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/UnificationSettings.java @@ -0,0 +1,110 @@ +package com.almostreliable.unified.api.unification; + +import com.almostreliable.unified.api.unification.recipe.RecipeData; +import net.minecraft.resources.ResourceLocation; + +/** + * Interface exposing unification information for a single unification config. + *

+ * There exists one instance for each config.
+ * Because {@link UnificationLookup}s are not composable, this interface should only be used when specific settings + * need to be checked. + * + * @since 1.0.0 + */ +public interface UnificationSettings extends UnificationLookup { + + /** + * Returns the name of the unification config these settings are for. + *

+ * The name of the config is the file name of the JSON file within the unification subfolder without the file + * extension. + * + * @return the name of the config + */ + String getName(); + + /** + * Returns the instance of the {@link ModPriorities} these settings are based on. + * + * @return the {@link ModPriorities} + */ + ModPriorities getModPriorities(); + + /** + * Returns the instance of the {@link StoneVariants} these settings are based on. + * + * @return the {@link StoneVariants} + */ + StoneVariants getStoneVariants(); + + /** + * Returns whether the given {@link RecipeData} should be included in the unification process. + *

+ * This method is a quick way to check the recipe id and type. + * + * @param recipe the recipe to check + * @return true if the recipe should be included, false otherwise + */ + default boolean shouldIncludeRecipe(RecipeData recipe) { + return shouldIncludeRecipeType(recipe) && shouldIncludeRecipeId(recipe); + } + + /** + * Returns whether the given recipe type should be included in the unification process. + * + * @param type the recipe type to check + * @return true if the recipe type should be included, false otherwise + */ + boolean shouldIncludeRecipeType(ResourceLocation type); + + /** + * Returns whether the recipe type of the given {@link RecipeData} should be included in the unification process. + * + * @param recipe the recipe to check + * @return true if the recipe type should be included, false otherwise + */ + default boolean shouldIncludeRecipeType(RecipeData recipe) { + return shouldIncludeRecipeType(recipe.getType()); + } + + /** + * Returns whether the given recipe id should be included in the unification process. + * + * @param id the recipe id to check + * @return true if the recipe id should be included, false otherwise + */ + boolean shouldIncludeRecipeId(ResourceLocation id); + + /** + * Returns whether the recipe id of the given {@link RecipeData} should be included in the unification process. + * + * @param recipe the recipe to check + * @return true if the recipe id should be included, false otherwise + */ + default boolean shouldIncludeRecipeId(RecipeData recipe) { + return shouldIncludeRecipeId(recipe.getId()); + } + + /** + * Returns whether variant items of this config should be hidden in recipe viewers. + * + * @return true if variant items should be hidden, false otherwise + */ + boolean shouldHideVariantItems(); + + /** + * Returns whether loot tables should be unified with the unification of this config. + * + * @return true if loot tables should be unified, false otherwise + */ + boolean shouldUnifyLoot(); + + /** + * Returns whether the given loot table should be included in the unification process. + * + * @param table the loot table to check + * @return true if the loot table should be included, false otherwise + */ + boolean shouldIncludeLootTable(ResourceLocation table); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/GenericRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/GenericRecipeUnifier.java new file mode 100644 index 0000000..51ec081 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/GenericRecipeUnifier.java @@ -0,0 +1,41 @@ +package com.almostreliable.unified.api.unification.bundled; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +/** + * The most basic {@link RecipeUnifier} implementation. + *

+ * This {@link RecipeUnifier} will only be used if no other {@link RecipeUnifier} is registered for a recipe and more + * specific {@link RecipeUnifier}s such as the {@link ShapedRecipeUnifier} or {@link SmithingRecipeUnifier} can not be + * applied.
+ * It targets the most basic and commonly used keys and structures for inputs and outputs. + *

+ * Custom {@link RecipeUnifier}s can call {@link GenericRecipeUnifier#unify(UnificationHelper, RecipeJson)} on the + * {@link GenericRecipeUnifier#INSTANCE} to apply the defaults. + *

+ * For more specific {@link RecipeUnifier} implementations, see {@link ShapedRecipeUnifier} and + * {@link SmithingRecipeUnifier}. + * + * @since 1.0.0 + */ +public final class GenericRecipeUnifier implements RecipeUnifier { + + public static final GenericRecipeUnifier INSTANCE = new GenericRecipeUnifier(); + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + unifyInputs(helper, recipe); + unifyOutputs(helper, recipe); + } + + public void unifyInputs(UnificationHelper helper, RecipeJson recipe) { + helper.unifyInputs(recipe, RecipeConstants.DEFAULT_INPUT_KEYS); + } + + public void unifyOutputs(UnificationHelper helper, RecipeJson recipe) { + helper.unifyOutputs(recipe, RecipeConstants.DEFAULT_OUTPUT_KEYS); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/ShapedRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/ShapedRecipeUnifier.java new file mode 100644 index 0000000..085ad72 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/ShapedRecipeUnifier.java @@ -0,0 +1,63 @@ +package com.almostreliable.unified.api.unification.bundled; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.recipe.RecipeData; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; + +/** + * The {@link RecipeUnifier} implementation for shaped crafting recipes. + *

+ * This {@link RecipeUnifier} will only be used if no other {@link RecipeUnifier} is registered for a recipe. It targets + * vanilla shaped crafting recipes and custom recipe types that use common properties of shaped crafting recipes.
+ * If this {@link RecipeUnifier} can't be applied for a recipe, the {@link GenericRecipeUnifier} will be used as the + * last fallback. + *

+ * To check if a recipe is applicable for this {@link RecipeUnifier}, use + * {@link ShapedRecipeUnifier#isApplicable(RecipeData)}. Custom {@link RecipeUnifier}s can call + * {@link ShapedRecipeUnifier#unify(UnificationHelper, RecipeJson)} on the {@link ShapedRecipeUnifier#INSTANCE} to apply + * the defaults. + * + * @since 1.0.0 + */ +public final class ShapedRecipeUnifier implements RecipeUnifier { + + public static final RecipeUnifier INSTANCE = new ShapedRecipeUnifier(); + public static final ResourceLocation SHAPED_TYPE = ResourceLocation.withDefaultNamespace("crafting_shaped"); + public static final String KEY_PROPERTY = "key"; + public static final String PATTERN_PROPERTY = "pattern"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + + if (recipe.getProperty(KEY_PROPERTY) instanceof JsonObject json) { + for (var e : json.entrySet()) { + helper.unifyInputElement(e.getValue()); + } + } + } + + /** + * Checks if this {@link RecipeUnifier} can be applied for the given {@link RecipeData}. + *

+ * This method checks for the vanilla shaped crafting recipe type. If it's a custom recipe type, it tries to find + * common keys for the shaped crafting recipe. + * + * @param recipe the recipe to check + * @return true if the {@link RecipeUnifier} can be applied, false otherwise + */ + public static boolean isApplicable(RecipeData recipe) { + return recipe.getType().equals(SHAPED_TYPE) || hasShapedCraftingLikeStructure(recipe); + } + + @SuppressWarnings("FoldExpressionIntoStream") + private static boolean hasShapedCraftingLikeStructure(RecipeData recipe) { + return recipe.hasProperty(KEY_PROPERTY) && + recipe.hasProperty(PATTERN_PROPERTY) && + recipe.hasProperty(RecipeConstants.RESULT); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/SmithingRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/SmithingRecipeUnifier.java new file mode 100644 index 0000000..3e5d4a9 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/SmithingRecipeUnifier.java @@ -0,0 +1,61 @@ +package com.almostreliable.unified.api.unification.bundled; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.recipe.RecipeData; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import net.minecraft.resources.ResourceLocation; + +/** + * The {@link RecipeUnifier} implementation for smithing recipes. + *

+ * This {@link RecipeUnifier} will only be used if no other {@link RecipeUnifier} is registered for a recipe. It targets + * vanilla smithing recipes and custom recipe types that use common properties of smithing recipes.
+ * If this {@link RecipeUnifier} can't be applied for a recipe, the {@link GenericRecipeUnifier} will be used as the + * last fallback. + *

+ * To check if a recipe is applicable for this {@link RecipeUnifier}, use + * {@link SmithingRecipeUnifier#isApplicable(RecipeData)}. Custom {@link RecipeUnifier}s can call + * {@link SmithingRecipeUnifier#unify(UnificationHelper, RecipeJson)} on the {@link SmithingRecipeUnifier#INSTANCE} to + * apply the defaults. + * + * @since 1.0.0 + */ +public final class SmithingRecipeUnifier implements RecipeUnifier { + + public static final SmithingRecipeUnifier INSTANCE = new SmithingRecipeUnifier(); + public static final ResourceLocation TRANSFORM_TYPE = ResourceLocation.withDefaultNamespace("smithing_transform"); + public static final ResourceLocation TRIM_TYPE = ResourceLocation.withDefaultNamespace("smithing_trim"); + public static final String ADDITION_PROPERTY = "addition"; + public static final String BASE_PROPERTY = "base"; + public static final String TEMPLATE_PROPERTY = "template"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + helper.unifyInputs(recipe, ADDITION_PROPERTY, BASE_PROPERTY, TEMPLATE_PROPERTY); + } + + /** + * Checks if this {@link RecipeUnifier} can be applied for the given {@link RecipeData}. + *

+ * This method checks for the vanilla smithing recipe type. If it's a custom recipe type, it tries to find common + * keys for the smithing recipe. + * + * @param recipe the recipe to check + * @return true if the {@link RecipeUnifier} can be applied, false otherwise + */ + public static boolean isApplicable(RecipeData recipe) { + return recipe.getType().equals(TRANSFORM_TYPE) || + recipe.getType().equals(TRIM_TYPE) || + hasSmithingLikeStructure(recipe); + } + + @SuppressWarnings("FoldExpressionIntoStream") + private static boolean hasSmithingLikeStructure(RecipeData recipe) { + return recipe.hasProperty(ADDITION_PROPERTY) && + recipe.hasProperty(BASE_PROPERTY) && + recipe.hasProperty(RecipeConstants.RESULT); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/package-info.java b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/package-info.java new file mode 100644 index 0000000..3bfa9fd --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/bundled/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.api.unification.bundled; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/package-info.java b/Common/src/main/java/com/almostreliable/unified/api/unification/package-info.java new file mode 100644 index 0000000..9fe30f8 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.api.unification; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeData.java b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeData.java new file mode 100644 index 0000000..1b23fdb --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeData.java @@ -0,0 +1,33 @@ +package com.almostreliable.unified.api.unification.recipe; + +import net.minecraft.resources.ResourceLocation; + +/** + * Basic information about a recipe used for determination of the correct {@link RecipeUnifier}. + * + * @since 1.0.0 + */ +public interface RecipeData { + + /** + * Returns the recipe id as a {@link ResourceLocation}. + * + * @return the id + */ + ResourceLocation getId(); + + /** + * Returns the recipe type as a {@link ResourceLocation}. + * + * @return the recipe type + */ + ResourceLocation getType(); + + /** + * Checks if the current recipe contains the property with the given key. + * + * @param key the key of the property to check for + * @return true if the recipe contains the property, false otherwise + */ + boolean hasProperty(String key); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeJson.java b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeJson.java new file mode 100644 index 0000000..a8b62c4 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeJson.java @@ -0,0 +1,30 @@ +package com.almostreliable.unified.api.unification.recipe; + +import com.google.gson.JsonElement; + +import javax.annotation.Nullable; + +/** + * Abstraction of a recipe JSON to access and override properties. + * + * @since 1.0.0 + */ +public interface RecipeJson extends RecipeData { + + /** + * Returns the value of the property with the given key. + * + * @param key the key to retrieve the property for + * @return the property value or null if not present + */ + @Nullable + JsonElement getProperty(String key); + + /** + * Sets the property with the given key to the given value. + * + * @param key the key to set the property for + * @param value the value to set + */ + void setProperty(String key, JsonElement value); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifier.java new file mode 100644 index 0000000..e77849a --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifier.java @@ -0,0 +1,34 @@ +package com.almostreliable.unified.api.unification.recipe; + +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; + +/** + * Implemented on custom recipe unifiers. + *

+ * Custom unifiers will tell Almost Unified how to handle specific recipes.
+ * It can provide information about custom recipe keys not covered by the default unifiers and how to + * treat them. Whether they support ingredient replacements or just items.
+ * Recipes will be marked as modified automatically through comparison with the original JSON. + *

+ * Unifiers can either be registered per recipe type or per mod id. Registering a custom unifier will + * disable the default unifiers such as the {@link GenericRecipeUnifier}. + *

+ * Registration is handled in {@link RecipeUnifierRegistry} which can be obtained in + * {@link AlmostUnifiedPlugin#registerRecipeUnifiers(RecipeUnifierRegistry)}. + * + * @since 1.0.0 + */ +public interface RecipeUnifier { + + /** + * Uses of the given {@link UnificationHelper} to unify the given {@link RecipeJson}. + *

+ * {@link RecipeJson} is a utility wrapper that allows to easily access recipe information such as the recipe id, + * the recipe type and provides methods to check or modify the raw JSON. + * + * @param helper the helper to aid in the unification + * @param recipe the recipe to unify as a {@link RecipeJson} + */ + void unify(UnificationHelper helper, RecipeJson recipe); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifierRegistry.java b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifierRegistry.java new file mode 100644 index 0000000..8b2e270 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/RecipeUnifierRegistry.java @@ -0,0 +1,47 @@ +package com.almostreliable.unified.api.unification.recipe; + +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import net.minecraft.resources.ResourceLocation; + +/** + * The registry holding all {@link RecipeUnifier}s. + *

+ * {@link RecipeUnifier}s can be registered per recipe type or per mod id. + * + * @since 1.0.0 + */ +public interface RecipeUnifierRegistry { + + /** + * Registers a {@link RecipeUnifier} for a specific recipe type. + *

+ * Recipe-type-based recipe unifiers override mod-id-based recipe unifiers.
+ * Registering a custom recipe unifier will always disable the bundled recipe unifiers + * like the {@link GenericRecipeUnifier}. + * + * @param recipeType the recipe type to register the recipe unifier for + * @param recipeUnifier the recipe unifier + */ + void registerForRecipeType(ResourceLocation recipeType, RecipeUnifier recipeUnifier); + + /** + * Registers a {@link RecipeUnifier} for a specific mod id. + *

+ * Mod-id-based recipe unifiers will only apply if no recipe-type-based recipe unifiers + * are registered for the respective recipe.
+ * Registering a custom recipe unifier will always disable the bundled recipe unifiers + * like the {@link GenericRecipeUnifier}. + * + * @param modId the mod id to register the recipe unifier for + * @param recipeUnifier the recipe unifier + */ + void registerForModId(String modId, RecipeUnifier recipeUnifier); + + /** + * Retrieves the respective {@link RecipeUnifier} for the given {@link RecipeData}. + * + * @param recipeData the recipe data + * @return the recipe unifier for the given recipe data + */ + RecipeUnifier getRecipeUnifier(RecipeData recipeData); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/UnificationHelper.java b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/UnificationHelper.java new file mode 100644 index 0000000..f36bee3 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/UnificationHelper.java @@ -0,0 +1,247 @@ +package com.almostreliable.unified.api.unification.recipe; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.TagSubstitutions; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import javax.annotation.Nullable; + +/** + * Helper interface to aid in the unification of recipes. + *

+ * This interface provides methods to unify elements within recipes. Unification involves converting elements to tags + * or target items
+ * An instance of this interface is passed to {@link RecipeUnifier#unify(UnificationHelper, RecipeJson)} to assist in + * the unification process of the given {@link RecipeJson}. + *

+ * Implementations of this interface are expected to provide the logic for unification, typically based on predefined + * lookup tables or constants. The methods provided by this interface handle various JSON structures, including + * {@link JsonObject}s, {@link JsonArray}s, and individual {@link JsonElement}s. + * + * @since 1.0.0 + */ +public interface UnificationHelper { + + /** + * Returns the instance of the {@link UnificationLookup} this helper is based on. + * + * @return the {@link UnificationLookup} this helper is based on + */ + UnificationLookup getUnificationLookup(); + + /** + * Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as inputs.
+ * Entries treated as inputs will be converted to tags if possible. + *

+ * The keys refer to top-level entries in the {@link RecipeJson}. This method requires at least one key to be + * provided.
+ * To use default keys, refer to {@link RecipeConstants#DEFAULT_INPUT_KEYS} or see {@link GenericRecipeUnifier}. + * + * @param recipe the {@link RecipeJson} to fetch the input entries from + * @param keys the keys of the input entries to unify + * @return true if any element was changed, false otherwise + */ + boolean unifyInputs(RecipeJson recipe, String... keys); + + /** + * Unifies a {@link JsonElement} as an input.
+ * Elements treated as inputs will be converted to tags if possible. + *

+ * This method can unify {@link JsonObject}s and {@link JsonArray}s.
+ * The keys will be used for each nested element. If no keys are provided, it falls back to + * {@link RecipeConstants#DEFAULT_INPUT_INNER_KEYS}. + * + * @param jsonElement the {@link JsonElement} to unify + * @param keys the keys to use + * @return true if the {@link JsonElement} was changed, false otherwise + */ + boolean unifyInputElement(@Nullable JsonElement jsonElement, String... keys); + + /** + * Unifies a {@link JsonArray} as an input.
+ * Elements treated as inputs will be converted to tags if possible. + *

+ * The keys will be used for each nested element. If no keys are provided, it falls back to + * {@link RecipeConstants#DEFAULT_INPUT_INNER_KEYS}. + * + * @param jsonArray the {@link JsonArray} to unify + * @param keys the keys to use + * @return true if any element of the {@link JsonArray} was changed, false otherwise + */ + boolean unifyInputArray(JsonArray jsonArray, String... keys); + + /** + * Unifies a {@link JsonObject} as an input.
+ * Elements treated as inputs will be converted to tags if possible. + *

+ * The keys will be used for each nested element. If no keys are provided, it falls back to + * {@link RecipeConstants#DEFAULT_INPUT_INNER_KEYS}. + * + * @param jsonObject the {@link JsonObject} to unify + * @param keys the keys to use + * @return true if any element of the {@link JsonObject} was changed, false otherwise + */ + boolean unifyInputObject(JsonObject jsonObject, String... keys); + + /** + * Unifies a {@link JsonObject} as a tag input.
+ * Tag inputs are only changed if they have an associated {@link TagSubstitutions} entry. + * + * @param jsonObject the {@link JsonObject} to unify + * @return true if the tag input was changed, false otherwise + */ + boolean unifyInputTag(JsonObject jsonObject); + + /** + * Unifies a {@link JsonObject} as an item input.
+ * The item will be converted to a tag if possible. + * + * @param jsonObject the {@link JsonObject} to unify + * @return true if the item input was changed, false otherwise + */ + boolean unifyInputItem(JsonObject jsonObject); + + /** + * Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as + * outputs.
+ * Entries treated as outputs will be converted to target items. If the entry is a tag, it will be converted + * to the target item of the tag. + *

+ * The keys refer to top-level entries in the {@link RecipeJson}. This method requires at least one key to be + * provided.
+ * To use default keys, refer to {@link RecipeConstants#DEFAULT_OUTPUT_KEYS} or see {@link GenericRecipeUnifier}. + * + * @param recipe the {@link RecipeJson} to fetch the output entries from + * @param keys the keys of the output entries to unify + * @return true if any element was changed, false otherwise + */ + boolean unifyOutputs(RecipeJson recipe, String... keys); + + /** + * Fetches all entries of the given {@link RecipeJson} under the specified keys and unifies them as + * outputs.
+ * Entries treated as outputs will be converted to target items. If the entry is a tag and tagsToItems is true, + * it will be converted to the target item of the tag. + *

+ * The keys refer to top-level entries in the {@link RecipeJson}. This method requires at least one key to be + * provided.
+ * To use default keys, refer to {@link RecipeConstants#DEFAULT_OUTPUT_KEYS} or see {@link GenericRecipeUnifier}. + * + * @param recipe the {@link RecipeJson} to fetch the output entries from + * @param tagsToItems if true, tags will be converted to target items + * @param keys the keys of the output entries to unify + * @return true if any element was changed, false otherwise + */ + boolean unifyOutputs(RecipeJson recipe, boolean tagsToItems, String... keys); + + /** + * Fetches the entry of the given {@link RecipeJson} under the specified key and unifies it as output by using the + * given inner keys.
+ * Entries treated as outputs will be converted to target items. If the entry is a tag and tagsToItems is true, + * it will be converted to the target item of the tag. + *

+ * The key refers to a top-level entry in the {@link RecipeJson} while the inner keys refer to nested entries within + * the resulting {@link JsonElement}. + * + * @param recipe the {@link RecipeJson} to fetch the output entry from + * @param key the key of the output entry to unify + * @param tagsToItems if true, tags will be converted to target items + * @param innerKeys the inner keys of the output entry to unify + * @return true if any element was changed, false otherwise + */ + boolean unifyOutputs(RecipeJson recipe, String key, boolean tagsToItems, String... innerKeys); + + /** + * Unifies a {@link JsonElement} as an output.
+ * Elements treated as outputs will be converted to target items. If the element is a tag and tagsToItems is true, + * it will be converted to the target item of the tag. + *

+ * This method can unify {@link JsonObject}s and {@link JsonArray}s.
+ * The keys will be used for each nested element. If no keys are provided, it falls back to + * {@link RecipeConstants#DEFAULT_OUTPUT_INNER_KEYS}. + * + * @param jsonElement the {@link JsonElement} to unify + * @param tagsToItems if true, tags will be converted to target items + * @param keys the keys to use + * @return true if the {@link JsonElement} was changed, false otherwise + */ + boolean unifyOutputElement(@Nullable JsonElement jsonElement, boolean tagsToItems, String... keys); + + /** + * Unifies a {@link JsonArray} as an output.
+ * Elements treated as outputs will be converted to target items. If the element is a tag and tagsToItems is true, + * it will be converted to the target item of the tag. + *

+ * The keys will be used for each nested element. If no keys are provided, it falls back to + * {@link RecipeConstants#DEFAULT_OUTPUT_INNER_KEYS}. + * + * @param jsonArray the {@link JsonArray} to unify + * @param tagsToItems if true, tags will be converted to target items + * @param keys the keys to use + * @return true if the {@link JsonArray} was changed, false otherwise + */ + boolean unifyOutputArray(JsonArray jsonArray, boolean tagsToItems, String... keys); + + /** + * Unifies a {@link JsonObject} as an output.
+ * Elements treated as outputs will be converted to target items. If the element is a tag and tagsToItems is true, + * it will be converted to the target item of the tag. + *

+ * The keys will be used for each nested element. If no keys are provided, it falls back to + * {@link RecipeConstants#DEFAULT_OUTPUT_INNER_KEYS}. + * + * @param jsonObject the {@link JsonObject} to unify + * @param tagsToItems if true, tags will be converted to target items + * @param keys the keys to use + * @return true if the {@link JsonObject} was changed, false otherwise + */ + boolean unifyOutputObject(JsonObject jsonObject, boolean tagsToItems, String... keys); + + /** + * Unifies a {@link JsonObject} as a tag output.
+ * If tagsToItems is true, it will be converted to the target item of the tag. If tagsToItems is false, it + * will only be changed if the tag has an associated {@link TagSubstitutions} entry. + * + * @param jsonObject the {@link JsonObject} to unify + * @param tagsToItems if true, the tag will be converted to the target item + * @return true if the tag was changed, false otherwise + */ + boolean unifyOutputTag(JsonObject jsonObject, boolean tagsToItems); + + /** + * Unifies a {@link JsonObject} as an item output.
+ * The item will be converted to the target item of the tag if possible. + *

+ * This uses the default keys {@link RecipeConstants#ITEM} and {@link RecipeConstants#ID}. + * + * @param jsonObject the {@link JsonObject} to unify + * @return true if the item output was changed, false otherwise + */ + boolean unifyOutputItem(JsonObject jsonObject); + + /** + * Unifies a {@link JsonObject} as an item output.
+ * The item will be converted to the target item of the tag if possible. + * + * @param jsonObject the {@link JsonObject} to unify + * @param key the key of the output entry to unify + * @return true if the item output was changed, false otherwise + */ + boolean unifyOutputItem(JsonObject jsonObject, String key); + + /** + * Handles the output item replacement. + *

+ * It needs to be ensured that the passed {@link JsonPrimitive} is an item. + * + * @param jsonPrimitive the {@link JsonPrimitive} to handle + * @return the replacement {@link JsonPrimitive} or null if no replacement was found + */ + @Nullable + JsonPrimitive handleOutputItemReplacement(JsonPrimitive jsonPrimitive); +} diff --git a/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/package-info.java b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/package-info.java new file mode 100644 index 0000000..e2e83d5 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/api/unification/recipe/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.api.unification.recipe; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/compat/AdAstraRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/compat/AdAstraRecipeUnifier.java deleted file mode 100644 index 2144347..0000000 --- a/Common/src/main/java/com/almostreliable/unified/compat/AdAstraRecipeUnifier.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -public class AdAstraRecipeUnifier implements RecipeUnifier { - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put("output", (json, ctx) -> { - return ctx.createResultReplacement(json, false, RecipeConstants.ITEM, "id"); - }); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/AlmostKube.java b/Common/src/main/java/com/almostreliable/unified/compat/AlmostKube.java deleted file mode 100644 index 5dd588c..0000000 --- a/Common/src/main/java/com/almostreliable/unified/compat/AlmostKube.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.utils.UnifyTag; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; - -import javax.annotation.Nullable; -import java.util.Set; -import java.util.stream.Collectors; - -@SuppressWarnings("unused") -public final class AlmostKube { - - private AlmostKube() {} - - @Nullable - public static String getPreferredTagForItem(ItemStack stack) { - UnifyTag tag = AlmostUnified - .getRuntime() - .getReplacementMap() - .orElseThrow(AlmostKube::notLoadedException) - .getPreferredTagForItem(getId(stack)); - return tag == null ? null : tag.location().toString(); - } - - public static ItemStack getReplacementForItem(ItemStack stack) { - ResourceLocation replacement = AlmostUnified - .getRuntime() - .getReplacementMap() - .orElseThrow(AlmostKube::notLoadedException) - .getReplacementForItem(getId(stack)); - return BuiltInRegistries.ITEM.get(replacement).getDefaultInstance(); - } - - public static ItemStack getPreferredItemForTag(ResourceLocation tag) { - UnifyTag asUnifyTag = UnifyTag.item(tag); - ResourceLocation item = AlmostUnified - .getRuntime() - .getReplacementMap() - .orElseThrow(AlmostKube::notLoadedException) - .getPreferredItemForTag(asUnifyTag, $ -> true); - return BuiltInRegistries.ITEM.get(item).getDefaultInstance(); - } - - public static Set getTags() { - return AlmostUnified - .getRuntime() - .getFilteredTagMap() - .orElseThrow(AlmostKube::notLoadedException) - .getTags() - .stream() - .map(tag -> tag.location().toString()) - .collect(Collectors.toSet()); - } - - public static Set getItemIds(ResourceLocation tag) { - UnifyTag asUnifyTag = UnifyTag.item(tag); - return AlmostUnified - .getRuntime() - .getFilteredTagMap() - .orElseThrow(AlmostKube::notLoadedException) - .getEntriesByTag(asUnifyTag) - .stream() - .map(ResourceLocation::toString) - .collect(Collectors.toSet()); - } - - public static UnifyConfig getUnifyConfig() { - return AlmostUnified.getRuntime().getUnifyConfig().orElseThrow(AlmostKube::notLoadedException); - } - - private static ResourceLocation getId(ItemStack stack) { - return BuiltInRegistries.ITEM - .getResourceKey(stack.getItem()) - .map(ResourceKey::location) - .orElseThrow(() -> new IllegalArgumentException("Item not found in registry")); - } - - private static IllegalStateException notLoadedException() { - return new IllegalStateException( - "AlmostUnifiedRuntime is unavailable in KubeJS! Possible reasons: calling runtime too early, not in a server environment" - ); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/GregTechModernRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/compat/GregTechModernRecipeUnifier.java deleted file mode 100644 index ae32de2..0000000 --- a/Common/src/main/java/com/almostreliable/unified/compat/GregTechModernRecipeUnifier.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import javax.annotation.Nullable; -import java.util.List; -import java.util.function.Function; - -public class GregTechModernRecipeUnifier implements RecipeUnifier { - - private static final String CONTENT = "content"; - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - List.of( - RecipeConstants.INPUTS, - RecipeConstants.TICK_INPUTS - ).forEach(key -> - builder.put(key, (json, ctx) -> createContentReplacement(json, ctx, ctx::createIngredientReplacement)) - ); - - List.of( - RecipeConstants.OUTPUTS, - RecipeConstants.TICK_OUTPUTS - ).forEach(key -> - builder.put( - key, - (json, ctx) -> createContentReplacement( - json, - ctx, - element -> ctx.createResultReplacement( - element, - true, - RecipeConstants.ITEM, - RecipeConstants.INGREDIENT - ) - ) - ) - ); - } - - @Nullable - private JsonElement createContentReplacement(@Nullable JsonElement json, RecipeContext ctx, Function elementTransformer) { - if (json instanceof JsonObject jsonObject && - jsonObject.get(RecipeConstants.ITEM) instanceof JsonArray jsonArray) { - JsonArray result = new JsonArray(); - boolean changed = false; - - for (JsonElement element : jsonArray) { - if (element instanceof JsonObject elementObject) { - JsonElement replacement = elementTransformer.apply(elementObject.get(CONTENT)); - if (replacement != null) { - elementObject.add(CONTENT, replacement); - changed = true; - } - result.add(elementObject); - } - } - - if (changed) { - jsonObject.add(RecipeConstants.ITEM, result); - return jsonObject; - } - } - - return null; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java b/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java deleted file mode 100644 index ba8318a..0000000 --- a/Common/src/main/java/com/almostreliable/unified/compat/HideHelper.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.AlmostUnifiedRuntime; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.TagOwnerships; -import com.almostreliable.unified.utils.Utils; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - -public final class HideHelper { - - private HideHelper() {} - - public static Collection createHidingList(AlmostUnifiedRuntime runtime) { - ReplacementMap repMap = runtime.getReplacementMap().orElse(null); - var tagMap = runtime.getFilteredTagMap().orElse(null); - - if (repMap == null || tagMap == null) return new ArrayList<>(); - - Set hidingList = new HashSet<>(); - - for (var unifyTag : tagMap.getTags()) { - var itemsByTag = tagMap.getEntriesByTag(unifyTag); - - // avoid handling single entries and tags that only contain the same namespace for all items - if (Utils.allSameNamespace(itemsByTag)) continue; - - Set replacements = new HashSet<>(); - for (ResourceLocation item : itemsByTag) { - replacements.add(getReplacementForItem(repMap, item)); - } - - Set toHide = new HashSet<>(); - for (ResourceLocation item : itemsByTag) { - if (!replacements.contains(item)) { - toHide.add(item); - } - } - - if (toHide.isEmpty()) continue; - - AlmostUnified.LOG.info( - "[AutoHiding] Hiding {}/{} items for tag '#{}' -> {}", - toHide.size(), - itemsByTag.size(), - unifyTag.location(), - toHide - ); - - hidingList.addAll(toHide); - } - - hidingList.addAll(getRefItems(repMap)); - - return hidingList - .stream() - .flatMap(rl -> BuiltInRegistries.ITEM.getOptional(rl).stream()) - .map(ItemStack::new) - .collect(Collectors.toList()); - } - - /** - * Returns the replacement for the given item, or the item itself if no replacement is found. - *

- * Returning the item itself is important for stone strata detection. - * - * @param repMap The replacement map. - * @param item The item to get the replacement for. - * @return The replacement for the given item, or the item itself if no replacement is found. - */ - private static ResourceLocation getReplacementForItem(ReplacementMap repMap, ResourceLocation item) { - var replacement = repMap.getReplacementForItem(item); - if (replacement == null) return item; - return replacement; - } - - /** - * Returns a set of all items that are contained in the reference tags. - * - * @return A set of all items that are contained in the reference tags. - */ - private static Set getRefItems(ReplacementMap repMap) { - Set hidingList = new HashSet<>(); - TagOwnerships ownerships = repMap.getTagOwnerships(); - - ownerships.getRefs().forEach(ref -> { - var owner = ownerships.getOwnerByTag(ref); - assert owner != null; - - var dominantItem = repMap.getPreferredItemForTag(owner, $ -> true); - - TagKey asTagKey = TagKey.create(Registries.ITEM, ref.location()); - Set refItems = new HashSet<>(); - BuiltInRegistries.ITEM.getTagOrEmpty(asTagKey).forEach(holder -> { - ResourceLocation item = BuiltInRegistries.ITEM.getKey(holder.value()); - if (item.equals(dominantItem)) return; // don't hide if the item is a dominant one - refItems.add(item); - }); - - if (refItems.isEmpty()) return; - - AlmostUnified.LOG.info( - "[AutoHiding] Hiding reference tag '#{}' of owner tag '#{}' -> {}", - ref.location(), - owner.location(), - refItems - ); - - hidingList.addAll(refItems); - }); - - return hidingList; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/PluginManager.java b/Common/src/main/java/com/almostreliable/unified/compat/PluginManager.java new file mode 100644 index 0000000..ea8c7f8 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/PluginManager.java @@ -0,0 +1,78 @@ +package com.almostreliable.unified.compat; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import net.minecraft.resources.ResourceLocation; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public final class PluginManager { + + @Nullable private static PluginManager INSTANCE; + private final List plugins; + + private PluginManager(List plugins) { + this.plugins = plugins; + } + + @SuppressWarnings("StaticVariableUsedBeforeInitialization") + public static PluginManager instance() { + if (INSTANCE == null) { + throw new IllegalStateException("PluginManager is not initialized"); + } + + return INSTANCE; + } + + public static void init(Collection plugins) { + if (INSTANCE != null) { + throw new IllegalStateException("PluginManager is already initialized"); + } + + var sortedPlugins = new ArrayList<>(plugins); + sortedPlugins.sort((a, b) -> { + if (a.getPluginId().getNamespace().equals(ModConstants.ALMOST_UNIFIED)) { + return -1; + } + + if (b.getPluginId().getNamespace().equals(ModConstants.ALMOST_UNIFIED)) { + return 1; + } + + return a.getPluginId().compareTo(b.getPluginId()); + }); + + String ids = sortedPlugins + .stream() + .map(AlmostUnifiedPlugin::getPluginId) + .map(ResourceLocation::toString) + .collect(Collectors.joining(", ")); + AlmostUnifiedCommon.LOGGER.info("Loaded plugins: {}", ids); + + INSTANCE = new PluginManager(sortedPlugins); + } + + public void registerRecipeUnifiers(RecipeUnifierRegistry registry) { + forEachPlugin(plugin -> plugin.registerRecipeUnifiers(registry)); + } + + public void forEachPlugin(Consumer consumer) { + var it = plugins.listIterator(); + while (it.hasNext()) { + AlmostUnifiedPlugin plugin = it.next(); + try { + consumer.accept(plugin); + } catch (Exception e) { + it.remove(); + AlmostUnifiedCommon.LOGGER.error("Failed to process plugin {}, removing it.", plugin.getPluginId(), e); + } + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/kube/AlmostKube.java b/Common/src/main/java/com/almostreliable/unified/compat/kube/AlmostKube.java new file mode 100644 index 0000000..a548fb3 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/kube/AlmostKube.java @@ -0,0 +1,71 @@ +package com.almostreliable.unified.compat.kube; + +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.AlmostUnifiedRuntime; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nullable; +import java.util.Set; +import java.util.stream.Collectors; + +@SuppressWarnings("unused") +public final class AlmostKube { + + private AlmostKube() {} + + private static AlmostUnifiedRuntime getRuntime() { + return AlmostUnified.INSTANCE.getRuntimeOrThrow(); + } + + public static Set getTags() { + return getRuntime() + .getUnificationLookup() + .getTags() + .stream() + .map(tag -> tag.location().toString()) + .collect(Collectors.toSet()); + } + + public static Set getTagEntries(ResourceLocation tag) { + var tagKey = TagKey.create(Registries.ITEM, tag); + return getRuntime() + .getUnificationLookup() + .getTagEntries(tagKey) + .stream() + .map(holder -> holder.id().toString()) + .collect(Collectors.toSet()); + } + + @Nullable + public static String getRelevantItemTag(ItemStack stack) { + var tag = getRuntime().getUnificationLookup().getRelevantItemTag(getId(stack)); + return tag == null ? null : tag.location().toString(); + } + + public static ItemStack getVariantItemTarget(ItemStack stack) { + var entry = getRuntime().getUnificationLookup().getVariantItemTarget(getId(stack)); + if (entry == null) return ItemStack.EMPTY; + + return entry.value().getDefaultInstance(); + } + + public static ItemStack getTagTargetItem(ResourceLocation tag) { + var tagKey = TagKey.create(Registries.ITEM, tag); + var entry = getRuntime().getUnificationLookup().getTagTargetItem(tagKey); + if (entry == null) return ItemStack.EMPTY; + + return entry.value().getDefaultInstance(); + } + + private static ResourceLocation getId(ItemStack stack) { + return BuiltInRegistries.ITEM + .getResourceKey(stack.getItem()) + .map(ResourceKey::location) + .orElseThrow(() -> new IllegalArgumentException("Item not found in registry")); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/kube/package-info.java b/Common/src/main/java/com/almostreliable/unified/compat/kube/package-info.java new file mode 100644 index 0000000..61d4d6a --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/kube/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.compat.kube; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/compat/unification/GregTechModernRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/compat/unification/GregTechModernRecipeUnifier.java new file mode 100644 index 0000000..2b7722c --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/unification/GregTechModernRecipeUnifier.java @@ -0,0 +1,51 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.function.Consumer; + +public class GregTechModernRecipeUnifier implements RecipeUnifier { + + private static final String TICK_INPUTS = "tickInputs"; + private static final String TICK_OUTPUTS = "tickOutputs"; + private static final String CONTENT = "content"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + + doUnify(recipe, RecipeConstants.INPUTS, helper::unifyInputElement); + doUnify(recipe, TICK_INPUTS, helper::unifyInputElement); + + doUnify(recipe, + RecipeConstants.OUTPUTS, + json -> helper.unifyOutputObject(json, true, RecipeConstants.ITEM, RecipeConstants.INGREDIENT)); + doUnify(recipe, + TICK_OUTPUTS, + json -> helper.unifyOutputObject(json, true, RecipeConstants.ITEM, RecipeConstants.INGREDIENT)); + } + + private void doUnify(RecipeJson recipe, String key, Consumer callback) { + JsonElement property = recipe.getProperty(key); + if (property == null) { + return; + } + + if (!(property.getAsJsonObject().get(RecipeConstants.ITEM) instanceof JsonArray arr)) { + return; + } + + for (JsonElement element : arr) { + if (element.getAsJsonObject().get(CONTENT) instanceof JsonObject content) { + callback.accept(content); + } + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/unification/package-info.java b/Common/src/main/java/com/almostreliable/unified/compat/unification/package-info.java new file mode 100644 index 0000000..23aeb55 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/unification/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.compat.unification; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostEMI.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostEMI.java new file mode 100644 index 0000000..f1b6487 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostEMI.java @@ -0,0 +1,57 @@ +package com.almostreliable.unified.compat.viewer; + +import dev.emi.emi.api.EmiEntrypoint; +import dev.emi.emi.api.EmiInitRegistry; +import dev.emi.emi.api.EmiPlugin; +import dev.emi.emi.api.EmiRegistry; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipeDecorator; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.WidgetHolder; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; + +@SuppressWarnings("UnstableApiUsage") +@EmiEntrypoint +public class AlmostEMI implements EmiPlugin { + + @Override + public void initialize(EmiInitRegistry registry) { + if (!BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.EMI_STRICT_TAG).iterator().hasNext()) return; + for (Holder itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) { + registry.disableStack(EmiStack.of(new ItemStack(itemHolder))); + } + } + + @Override + public void register(EmiRegistry registry) { + registry.addRecipeDecorator(new IndicatorDecorator()); + + if (BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.EMI_STRICT_TAG).iterator().hasNext()) return; + for (Holder itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) { + registry.removeEmiStacks(EmiStack.of(new ItemStack(itemHolder))); + } + } + + private static class IndicatorDecorator implements EmiRecipeDecorator { + + @Override + public void decorateRecipe(EmiRecipe recipe, WidgetHolder widgets) { + var recipeId = recipe.getId(); + if (recipeId == null) return; + + var link = CRTLookup.getLink(recipeId); + if (link == null) return; + + int pX = recipe.getDisplayWidth() - 5; + int pY = recipe.getDisplayHeight() - 3; + int size = RecipeIndicator.RENDER_SIZE - 1; + + widgets.addDrawable(0, 0, 0, 0, (guiGraphics, mX, mY, delta) -> + RecipeIndicator.renderIndicator(guiGraphics, pX, pY, size)); + widgets.addTooltipText(RecipeIndicator.constructTooltip(link), pX, pY, size, size); + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostJEI.java similarity index 78% rename from Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java rename to Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostJEI.java index 1d004f0..754443f 100644 --- a/Common/src/main/java/com/almostreliable/unified/compat/AlmostJEI.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostJEI.java @@ -1,11 +1,7 @@ -package com.almostreliable.unified.compat; +package com.almostreliable.unified.compat.viewer; -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.AlmostUnifiedFallbackRuntime; -import com.almostreliable.unified.api.ModConstants; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.recipe.CRTLookup; -import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink; +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.compat.viewer.ClientRecipeTracker.ClientRecipeLink; import com.almostreliable.unified.utils.Utils; import me.shedaniel.rei.plugincompatibilities.api.REIPluginCompatIgnore; import mezz.jei.api.IModPlugin; @@ -17,10 +13,14 @@ import mezz.jei.api.recipe.category.extensions.IRecipeCategoryDecorator; import mezz.jei.api.registration.IAdvancedRegistration; import mezz.jei.api.runtime.IJeiRuntime; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.Collection; @REIPluginCompatIgnore @@ -34,15 +34,11 @@ public class AlmostJEI implements IModPlugin { @Override public void onRuntimeAvailable(IJeiRuntime jei) { - AlmostUnifiedFallbackRuntime.getInstance().reload(); + Collection items = new ArrayList<>(); + for (Holder itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) { + items.add(new ItemStack(itemHolder)); + } - Boolean jeiDisabled = AlmostUnified.getRuntime() - .getUnifyConfig() - .map(UnifyConfig::reiOrJeiDisabled) - .orElse(false); - if (jeiDisabled) return; - - Collection items = HideHelper.createHidingList(AlmostUnified.getRuntime()); if (!items.isEmpty()) { jei.getIngredientManager().removeIngredientsAtRuntime(VanillaTypes.ITEM_STACK, items); } diff --git a/Common/src/main/java/com/almostreliable/unified/compat/AlmostREI.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostREI.java similarity index 83% rename from Common/src/main/java/com/almostreliable/unified/compat/AlmostREI.java rename to Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostREI.java index 1cd158e..8a93893 100644 --- a/Common/src/main/java/com/almostreliable/unified/compat/AlmostREI.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/AlmostREI.java @@ -1,12 +1,8 @@ -package com.almostreliable.unified.compat; +package com.almostreliable.unified.compat.viewer; -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.AlmostUnifiedFallbackRuntime; -import com.almostreliable.unified.ClientTagUpdateEvent; -import com.almostreliable.unified.api.ModConstants; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.recipe.CRTLookup; -import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink; +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.compat.viewer.ClientRecipeTracker.ClientRecipeLink; +import com.almostreliable.unified.utils.ClientTagUpdateEvent; import com.almostreliable.unified.utils.Utils; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.entry.filtering.base.BasicFilteringRule; @@ -23,8 +19,14 @@ import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.plugins.PluginManager; import me.shedaniel.rei.api.common.registry.ReloadStage; import me.shedaniel.rei.api.common.util.EntryIngredients; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; @SuppressWarnings("UnstableApiUsage") @@ -46,16 +48,12 @@ public class AlmostREI implements REIClientPlugin { @Override public void registerBasicEntryFiltering(BasicFilteringRule rule) { filterUpdate = rule.hide(() -> { - AlmostUnifiedFallbackRuntime.getInstance().reload(); + Collection items = new ArrayList<>(); + for (Holder itemHolder : BuiltInRegistries.ITEM.getTagOrEmpty(ItemHider.HIDE_TAG)) { + items.add(new ItemStack(itemHolder)); + } - var reiDisabled = AlmostUnified - .getRuntime() - .getUnifyConfig() - .map(UnifyConfig::reiOrJeiDisabled) - .orElse(false); - if (reiDisabled) return List.of(); - - return EntryIngredients.ofItemStacks(HideHelper.createHidingList(AlmostUnified.getRuntime())); + return EntryIngredients.ofItemStacks(items); }); } diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/CRTLookup.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/CRTLookup.java similarity index 69% rename from Common/src/main/java/com/almostreliable/unified/recipe/CRTLookup.java rename to Common/src/main/java/com/almostreliable/unified/compat/viewer/CRTLookup.java index 1c28ae2..5fdd659 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/CRTLookup.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/CRTLookup.java @@ -1,8 +1,9 @@ -package com.almostreliable.unified.recipe; +package com.almostreliable.unified.compat.viewer; -import com.almostreliable.unified.BuildConfig; +import com.almostreliable.unified.utils.Utils; import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.RecipeHolder; import javax.annotation.Nullable; @@ -12,14 +13,15 @@ public final class CRTLookup { @Nullable public static ClientRecipeTracker.ClientRecipeLink getLink(ResourceLocation recipeId) { - ResourceLocation linkRecipe = new ResourceLocation(BuildConfig.MOD_ID, recipeId.getNamespace()); + ResourceLocation link = Utils.getRL(recipeId.getNamespace()); if (Minecraft.getInstance().level == null) { return null; } return Minecraft.getInstance().level .getRecipeManager() - .byKey(linkRecipe) + .byKey(link) + .map(RecipeHolder::value) .filter(ClientRecipeTracker.class::isInstance) .map(ClientRecipeTracker.class::cast) .map(tracker -> tracker.getLink(recipeId)) diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/ClientRecipeTracker.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/ClientRecipeTracker.java similarity index 51% rename from Common/src/main/java/com/almostreliable/unified/recipe/ClientRecipeTracker.java rename to Common/src/main/java/com/almostreliable/unified/compat/viewer/ClientRecipeTracker.java index cf675e7..ac9060e 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/ClientRecipeTracker.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/ClientRecipeTracker.java @@ -1,29 +1,36 @@ -package com.almostreliable.unified.recipe; +package com.almostreliable.unified.compat.viewer; -import com.almostreliable.unified.BuildConfig; +import com.almostreliable.unified.unification.recipe.RecipeLink; import com.almostreliable.unified.utils.Utils; +import com.google.common.collect.ImmutableMap; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.minecraft.core.RegistryAccess; -import net.minecraft.network.FriendlyByteBuf; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.Container; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeInput; import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.Level; import javax.annotation.Nullable; import java.util.HashMap; +import java.util.List; import java.util.Map; /** * This recipe is used to track which recipes were unified. It is NOT used for crafting. * Each tracker will hold one namespace with a list of recipes that were unified for it. */ -public class ClientRecipeTracker implements Recipe { +public record ClientRecipeTracker(String namespace, Map recipes) + implements Recipe { + public static final ResourceLocation ID = Utils.getRL("client_recipe_tracker"); public static final String RECIPES = "recipes"; public static final String NAMESPACE = "namespace"; @@ -37,14 +44,6 @@ public class ClientRecipeTracker implements Recipe { } }; - private final ResourceLocation id; - private final Map recipes = new HashMap<>(); - private final String namespace; - - protected ClientRecipeTracker(ResourceLocation id, String namespace) { - this.id = id; - this.namespace = namespace; - } /** * Creates a raw string representation. @@ -63,12 +62,12 @@ public class ClientRecipeTracker implements Recipe { // @Override - public boolean matches(Container container, Level level) { + public boolean matches(RecipeInput recipeInput, Level level) { return false; } @Override - public ItemStack assemble(Container container, RegistryAccess registryAccess) { + public ItemStack assemble(RecipeInput recipeInput, HolderLookup.Provider provider) { return ItemStack.EMPTY; } @@ -78,14 +77,9 @@ public class ClientRecipeTracker implements Recipe { } @Override - public ItemStack getResultItem(RegistryAccess registryAccess) { + public ItemStack getResultItem(HolderLookup.Provider provider) { return ItemStack.EMPTY; } - - @Override - public ResourceLocation getId() { - return id; - } // @Override @@ -109,10 +103,15 @@ public class ClientRecipeTracker implements Recipe { public record ClientRecipeLink(ResourceLocation id, boolean isUnified, boolean isDuplicate) {} + + public List getLinkStrings() { + return recipes.values().stream().map(l -> createRaw(l.isUnified, l.isDuplicate, l.id.getPath())).toList(); + } + public static class Serializer implements RecipeSerializer { /** - * Reads a recipe from a json file. Recipe will look like this: + * Codec for the recipe tracker. The recipe will look like this: *

          * {@code
          * {
@@ -127,62 +126,78 @@ public class ClientRecipeTracker implements Recipe {
          * }
          * }
          * 
- * - * @param recipeId The id of the recipe for the tracker. - * @param json The json object. - * @return The recipe tracker. */ - @Override - public ClientRecipeTracker fromJson(ResourceLocation recipeId, JsonObject json) { - String namespace = json.get(NAMESPACE).getAsString(); - JsonArray recipes = json.get(RECIPES).getAsJsonArray(); - ClientRecipeTracker tracker = new ClientRecipeTracker(recipeId, namespace); - for (JsonElement element : recipes) { - ClientRecipeLink clientRecipeLink = parseRaw(namespace, element.getAsString()); - tracker.add(clientRecipeLink); + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance + .group( + Codec.STRING.fieldOf("namespace").forGetter(ClientRecipeTracker::namespace), + Codec.list(Codec.STRING).fieldOf("recipes").forGetter(ClientRecipeTracker::getLinkStrings) + ) + .apply(instance, Serializer::of)); + + + public static final StreamCodec STREAM_CODEC = new StreamCodec<>() { + @Override + public ClientRecipeTracker decode(RegistryFriendlyByteBuf buffer) { + int size = buffer.readInt(); + String namespace = buffer.readUtf(); + + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (int i = 0; i < size; i++) { + String raw = buffer.readUtf(); + ClientRecipeLink clientRecipeLink = parseRaw(namespace, raw); + builder.put(clientRecipeLink.id(), clientRecipeLink); + } + + return new ClientRecipeTracker(namespace, builder.build()); } - return tracker; + + /** + * Writes the tracker to the buffer. The namespace is written separately to save some bytes. + * Buffer output will look like: + *
+             *     size
+             *     namespace
+             *     flag$recipePath
+             *     flag$recipe2Path
+             *     ...
+             *     flag$recipeNPath
+             * 
+ * + * @param buffer The buffer to write to + * @param recipe The recipe to write + */ + @Override + public void encode(RegistryFriendlyByteBuf buffer, ClientRecipeTracker recipe) { + buffer.writeInt(recipe.recipes.size()); + buffer.writeUtf(recipe.namespace); + for (ClientRecipeLink clientRecipeLink : recipe.recipes.values()) { + String raw = createRaw(clientRecipeLink.isUnified(), + clientRecipeLink.isDuplicate(), + clientRecipeLink.id().getPath()); + buffer.writeUtf(raw); + } + } + }; + + @Override + public MapCodec codec() { + return CODEC; } @Override - public ClientRecipeTracker fromNetwork(ResourceLocation recipeId, FriendlyByteBuf buffer) { - int size = buffer.readInt(); - String namespace = buffer.readUtf(); - - ClientRecipeTracker recipe = new ClientRecipeTracker(recipeId, namespace); - for (int i = 0; i < size; i++) { - String raw = buffer.readUtf(); - ClientRecipeLink clientRecipeLink = parseRaw(namespace, raw); - recipe.add(clientRecipeLink); - } - return recipe; + public StreamCodec streamCodec() { + return STREAM_CODEC; } - /** - * Writes the tracker to the buffer. The namespace is written separately to save some bytes. - * Buffer output will look like: - *
-         *     size
-         *     namespace
-         *     flag$recipePath
-         *     flag$recipe2Path
-         *     ...
-         *     flag$recipeNPath
-         * 
- * - * @param buffer The buffer to write to - * @param recipe The recipe to write - */ - @Override - public void toNetwork(FriendlyByteBuf buffer, ClientRecipeTracker recipe) { - buffer.writeInt(recipe.recipes.size()); - buffer.writeUtf(recipe.namespace); - for (ClientRecipeLink clientRecipeLink : recipe.recipes.values()) { - String raw = createRaw(clientRecipeLink.isUnified(), - clientRecipeLink.isDuplicate(), - clientRecipeLink.id().getPath()); - buffer.writeUtf(raw); + private static ClientRecipeTracker of(String namespace, List recipes) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (String recipe : recipes) { + ClientRecipeLink link = parseRaw(namespace, recipe); + builder.put(link.id(), link); } + + return new ClientRecipeTracker(namespace, builder.build()); } /** @@ -192,12 +207,16 @@ public class ClientRecipeTracker implements Recipe { * @param raw The raw string. * @return The client sided recipe link. */ - private static ClientRecipeLink parseRaw(String namespace, String raw) { + public static ClientRecipeLink parseRaw(String namespace, String raw) { String[] split = raw.split("\\$", 2); int flag = Integer.parseInt(split[0]); boolean isUnified = (flag & UNIFIED_FLAG) != 0; boolean isDuplicate = (flag & DUPLICATE_FLAG) != 0; - return new ClientRecipeLink(new ResourceLocation(namespace, split[1]), isUnified, isDuplicate); + return new ClientRecipeLink( + ResourceLocation.fromNamespaceAndPath(namespace, split[1]), + isUnified, + isDuplicate + ); } } @@ -212,10 +231,10 @@ public class ClientRecipeTracker implements Recipe { } /** - * Creates a map with the namespace as key and the json recipe. - * These recipes are used later in {@link Serializer#fromJson(ResourceLocation, JsonObject)} + * Creates a map with the namespace as key and the JSON recipe. + * These recipes are used later in {@link Serializer} * - * @return The map with the namespace as key and the json recipe. + * @return The map with the namespace as key and the JSON recipe. */ public Map compute() { Map result = new HashMap<>(); @@ -224,7 +243,7 @@ public class ClientRecipeTracker implements Recipe { json.addProperty("type", ID.toString()); json.addProperty(NAMESPACE, namespace); json.add(RECIPES, recipes); - result.put(new ResourceLocation(BuildConfig.MOD_ID, namespace), json); + result.put(Utils.getRL(namespace), json); }); return result; } diff --git a/Common/src/main/java/com/almostreliable/unified/compat/viewer/ItemHider.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/ItemHider.java new file mode 100644 index 0000000..3231f7a --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/ItemHider.java @@ -0,0 +1,99 @@ +package com.almostreliable.unified.compat.viewer; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.unification.UnificationEntry; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.api.unification.UnificationSettings; +import com.almostreliable.unified.utils.Utils; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public final class ItemHider { + + public static final TagKey HIDE_TAG = TagKey.create(Registries.ITEM, Utils.getRL("hide")); + public static final TagKey EMI_STRICT_TAG = TagKey.create(Registries.ITEM, Utils.getRL("emi_strict")); + + private ItemHider() {} + + public static void applyHideTags(VanillaTagWrapper tags, Collection handlers, boolean emiHidingStrict) { + for (var handler : handlers) { + if (handler.shouldHideVariantItems()) { + applyHideTags(tags, handler); + } + } + + if (emiHidingStrict) { + tags.add(EMI_STRICT_TAG.location(), BuiltInRegistries.ITEM.wrapAsHolder(Items.DEBUG_STICK)); + } + } + + public static void applyHideTags(VanillaTagWrapper tags, UnificationSettings handler) { + var holdersToHide = createHidingItems(handler); + for (Holder holder : holdersToHide) { + tags.add(HIDE_TAG.location(), holder); + } + } + + public static Set> createHidingItems(UnificationSettings handler) { + Set> hidings = new HashSet<>(); + + for (TagKey tag : handler.getTags()) { + var entriesByTag = handler.getTagEntries(tag); + + // avoid handling single entries and tags that only contain the same namespace for all items + if (Utils.allSameNamespace(entriesByTag)) continue; + + Set> replacements = new HashSet<>(); + for (var holder : entriesByTag) { + replacements.add(getReplacementForItem(handler, holder)); + } + + Set> toHide = new HashSet<>(); + Set toHideIds = new HashSet<>(); + for (var entry : entriesByTag) { + if (!replacements.contains(entry)) { + toHide.add(entry.asHolderOrThrow()); + toHideIds.add(entry.id().toString()); + } + } + + if (toHide.isEmpty()) continue; + + AlmostUnifiedCommon.LOGGER.info( + "[AutoHiding] Hiding {}/{} items for tag '#{}' -> {}", + toHide.size(), + entriesByTag.size(), + tag.location(), + toHideIds + ); + + hidings.addAll(toHide); + } + + return hidings; + } + + /** + * Returns the replacement for the given item, or the item itself if no replacement is found. + *

+ * Returning the item itself is important for stone variant detection. + * + * @param repMap The replacement map. + * @param entry The holder to get the replacement for. + * @return The replacement for the given item, or the item itself if no replacement is found. + */ + private static UnificationEntry getReplacementForItem(UnificationLookup repMap, UnificationEntry entry) { + var replacement = repMap.getVariantItemTarget(entry); + if (replacement == null) return entry; + return replacement; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/RecipeIndicator.java similarity index 95% rename from Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java rename to Common/src/main/java/com/almostreliable/unified/compat/viewer/RecipeIndicator.java index 6862046..b8d8e4b 100644 --- a/Common/src/main/java/com/almostreliable/unified/compat/RecipeIndicator.java +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/RecipeIndicator.java @@ -1,6 +1,6 @@ -package com.almostreliable.unified.compat; +package com.almostreliable.unified.compat.viewer; -import com.almostreliable.unified.recipe.ClientRecipeTracker.ClientRecipeLink; +import com.almostreliable.unified.compat.viewer.ClientRecipeTracker.ClientRecipeLink; import com.almostreliable.unified.utils.Utils; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; diff --git a/Common/src/main/java/com/almostreliable/unified/compat/viewer/package-info.java b/Common/src/main/java/com/almostreliable/unified/compat/viewer/package-info.java new file mode 100644 index 0000000..facf769 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/compat/viewer/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.compat.viewer; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/config/Config.java b/Common/src/main/java/com/almostreliable/unified/config/Config.java index 05f740d..43e9268 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/Config.java +++ b/Common/src/main/java/com/almostreliable/unified/config/Config.java @@ -1,13 +1,13 @@ package com.almostreliable.unified.config; -import com.almostreliable.unified.AlmostUnified; +import com.almostreliable.unified.AlmostUnifiedCommon; import com.almostreliable.unified.AlmostUnifiedPlatform; +import com.almostreliable.unified.api.constant.ModConstants; import com.almostreliable.unified.utils.JsonUtils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; -import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -21,107 +21,128 @@ import java.util.stream.Collectors; public class Config { + private static final String CONFIG_DIR_PROPERTY = ModConstants.ALMOST_UNIFIED + ".configDir"; + private final String name; + + Config(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @SuppressWarnings("StaticMethodOnlyUsedInOneClass") public static T load(String name, Serializer serializer) { - JsonObject json = safeLoadJson(name); + AlmostUnifiedCommon.LOGGER.info("Loading config '{}.json'.", name); + + JsonObject json = JsonUtils.safeReadFromFile(buildPath(createConfigDir(), name), new JsonObject()); T config = serializer.deserialize(json); + if (serializer.isInvalid()) { - Path filePath = buildPath(createConfigDir(), name); - if (Files.exists(filePath)) { - backupConfig(name, filePath); - } - AlmostUnified.LOG.warn("Creating config: {}", name); - save(filePath, config, serializer); + save(buildPath(createConfigDir(), config.getName()), config, serializer); } + return config; } - private static void backupConfig(String name, Path p) { - AlmostUnified.LOG.warn("Config {} is invalid. Backing up and recreating.", name); - Path backupPath = p.resolveSibling(p.getFileName() + ".bak"); - try { - Files.deleteIfExists(backupPath); - Files.move(p, backupPath); - } catch (IOException e) { - AlmostUnified.LOG.error("Could not backup config file", e); + static Path createConfigDir() { + Path path = AlmostUnifiedPlatform.INSTANCE.getConfigPath(); + String property = System.getProperty(CONFIG_DIR_PROPERTY); + if (property != null) { + path = Path.of(property); } + + try { + Files.createDirectories(path); + } catch (IOException e) { + AlmostUnifiedCommon.LOGGER.error("Failed to create config directory.", e); + } + + return path; } - public static void save(Path p, T config, Serializer serializer) { + static void save(Path path, T config, Serializer serializer) { + if (Files.exists(path)) { + backupConfig(path); + } else { + AlmostUnifiedCommon.LOGGER.warn("Config '{}.json' not found. Creating default config.", config.getName()); + } + JsonObject json = serializer.serialize(config); Gson gson = new GsonBuilder().setPrettyPrinting().create(); String jsonString = gson.toJson(json); try { - Files.writeString(p, + Files.writeString( + path, jsonString, StandardOpenOption.CREATE, - StandardOpenOption.WRITE); + StandardOpenOption.WRITE + ); } catch (IOException e) { - AlmostUnified.LOG.error(e); + AlmostUnifiedCommon.LOGGER.error("Failed to save config '{}'.", config.getName(), e); } } - private static JsonObject safeLoadJson(String file) { - Path p = createConfigDir(); - try (BufferedReader reader = Files.newBufferedReader(buildPath(p, file))) { - return new Gson().fromJson(reader, JsonObject.class); - } catch (Exception ignored) { - } - return new JsonObject(); - } + private static void backupConfig(Path path) { + AlmostUnifiedCommon.LOGGER.warn("Config '{}' is invalid. Backing up and recreating.", path.getFileName()); - private static Path createConfigDir() { - Path p = AlmostUnifiedPlatform.INSTANCE.getConfigPath(); + Path backupPath = path.resolveSibling(path.getFileName() + ".bak"); try { - Files.createDirectories(p); + Files.deleteIfExists(backupPath); + Files.move(path, backupPath); } catch (IOException e) { - AlmostUnified.LOG.error("Failed to create config directory", e); + AlmostUnifiedCommon.LOGGER.error("Config '{}' could not be backed up.", path.getFileName(), e); } - return p; } - private static Path buildPath(Path p, String name) { - return p.resolve(name + ".json"); + private static Path buildPath(Path path, String name) { + return path.resolve(name + ".json"); } public abstract static class Serializer { - private boolean valid = true; - protected void setInvalid() { - this.valid = false; + private boolean valid; + + T deserialize(JsonObject json) { + valid = true; + return handleDeserialization(json); } - public boolean isInvalid() { - return !valid; - } + abstract T handleDeserialization(JsonObject json); - public V safeGet(Supplier supplier, V defaultValue) { + abstract JsonObject serialize(T config); + + V safeGet(Supplier supplier, V defaultValue) { try { return supplier.get(); } catch (Exception e) { setInvalid(); + return defaultValue; } - return defaultValue; } - protected Set deserializePatterns(JsonObject json, String configKey, List defaultValue) { - return safeGet(() -> JsonUtils + void setInvalid() { + this.valid = false; + } + + boolean isInvalid() { + return !valid; + } + + Set deserializePatterns(JsonObject json, String configKey, List defaultValue) { + return safeGet( + () -> JsonUtils .toList(json.getAsJsonArray(configKey)) .stream() .map(Pattern::compile) .collect(Collectors.toSet()), - new HashSet<>(defaultValue.stream().map(Pattern::compile).toList())); + new HashSet<>(defaultValue.stream().map(Pattern::compile).toList()) + ); } - protected void serializePatterns(JsonObject json, String configKey, Set patterns) { - json.add(configKey, - JsonUtils.toArray(patterns - .stream() - .map(Pattern::pattern) - .toList())); + void serializePatterns(JsonObject json, String configKey, Set patterns) { + json.add(configKey, JsonUtils.toArray(patterns.stream().map(Pattern::pattern).toList())); } - - public abstract T deserialize(JsonObject json); - - public abstract JsonObject serialize(T src); } } diff --git a/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java b/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java index 4e2e6e7..859d3e9 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java +++ b/Common/src/main/java/com/almostreliable/unified/config/DebugConfig.java @@ -1,96 +1,76 @@ package com.almostreliable.unified.config; -import com.almostreliable.unified.AlmostUnifiedPlatform; -import com.almostreliable.unified.utils.FileUtils; -import com.almostreliable.unified.utils.TagMap; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import org.apache.commons.lang3.StringUtils; -import java.util.Comparator; -import java.util.Map; -import java.util.stream.Collectors; +public final class DebugConfig extends Config { -public class DebugConfig extends Config { public static final String NAME = "debug"; + public static final DebugSerializer SERIALIZER = new DebugSerializer(); - public final boolean dumpTagMap; - public final boolean dumpDuplicates; - public final boolean dumpUnification; - public final boolean dumpOverview; - public final boolean dumpRecipes; + private final boolean dumpDuplicates; + private final boolean dumpOverview; + private final boolean dumpRecipes; + private final boolean dumpTags; + private final boolean dumpUnification; - public DebugConfig(boolean dumpTagMap, boolean dumpDuplicates, boolean dumpUnification, boolean dumpOverview, boolean dumpRecipes) { - this.dumpTagMap = dumpTagMap; + private DebugConfig(boolean dumpDuplicates, boolean dumpOverview, boolean dumpRecipes, boolean dumpTags, boolean dumpUnification) { + super(NAME); this.dumpDuplicates = dumpDuplicates; - this.dumpUnification = dumpUnification; this.dumpOverview = dumpOverview; this.dumpRecipes = dumpRecipes; + this.dumpTags = dumpTags; + this.dumpUnification = dumpUnification; } - public void logUnifyTagDump(TagMap tagMap) { - if (!dumpTagMap) { - return; - } - - FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "unify_tag_dump.txt", sb -> { - sb.append(tagMap - .getTags() - .stream() - .sorted(Comparator.comparing(t -> t.location().toString())) - .map(t -> StringUtils.rightPad(t.location().toString(), 40) + " => " + tagMap - .getEntriesByTag(t) - .stream() - .map(ResourceLocation::toString) - .sorted() - .collect(Collectors.joining(", ")) + "\n") - .collect(Collectors.joining())); - }); + public boolean shouldDumpDuplicates() { + return dumpDuplicates; } - public void logRecipes(Map recipes, String filename) { - if (!dumpRecipes) { - return; - } - - FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), - filename, - sb -> recipes.forEach((key, value) -> sb - .append(key.toString()) - .append(" [JSON]:") - .append(value.toString()) - .append("\n"))); + public boolean shouldDumpOverview() { + return dumpOverview; } - public static class Serializer extends Config.Serializer { + public boolean shouldDumpRecipes() { + return dumpRecipes; + } - public static final String DUMP_TAG_MAP = "dumpTagMap"; - public static final String DUMP_DUPLICATES = "dumpDuplicates"; - public static final String DUMP_UNIFICATION = "dumpUnification"; - public static final String DUMP_OVERVIEW = "dumpOverview"; - public static final String DUMP_RECIPES = "dumpRecipes"; + public boolean shouldDumpTags() { + return dumpTags; + } + + public boolean shouldDumpUnification() { + return dumpUnification; + } + + public static final class DebugSerializer extends Config.Serializer { + + private static final String DUMP_DUPLICATES = "dump_duplicates"; + private static final String DUMP_OVERVIEW = "dump_overview"; + private static final String DUMP_RECIPES = "dump_recipes"; + private static final String DUMP_TAGS = "dump_tags"; + private static final String DUMP_UNIFICATION = "dump_unification"; + + private DebugSerializer() {} @Override - public DebugConfig deserialize(JsonObject json) { + public DebugConfig handleDeserialization(JsonObject json) { return new DebugConfig( - safeGet(() -> json.get(DUMP_TAG_MAP).getAsBoolean(), false), safeGet(() -> json.get(DUMP_DUPLICATES).getAsBoolean(), false), - safeGet(() -> json.get(DUMP_UNIFICATION).getAsBoolean(), false), safeGet(() -> json.get(DUMP_OVERVIEW).getAsBoolean(), false), - safeGet(() -> json.get(DUMP_RECIPES).getAsBoolean(), false) + safeGet(() -> json.get(DUMP_RECIPES).getAsBoolean(), false), + safeGet(() -> json.get(DUMP_TAGS).getAsBoolean(), false), + safeGet(() -> json.get(DUMP_UNIFICATION).getAsBoolean(), false) ); } @Override - public JsonObject serialize(DebugConfig src) { + public JsonObject serialize(DebugConfig config) { JsonObject json = new JsonObject(); - json.addProperty(DUMP_TAG_MAP, src.dumpTagMap); - json.addProperty(DUMP_DUPLICATES, src.dumpDuplicates); - json.addProperty(DUMP_UNIFICATION, src.dumpUnification); - json.addProperty(DUMP_OVERVIEW, src.dumpOverview); - json.addProperty(DUMP_RECIPES, src.dumpRecipes); + json.addProperty(DUMP_DUPLICATES, config.dumpDuplicates); + json.addProperty(DUMP_OVERVIEW, config.dumpOverview); + json.addProperty(DUMP_RECIPES, config.dumpRecipes); + json.addProperty(DUMP_TAGS, config.dumpTags); + json.addProperty(DUMP_UNIFICATION, config.dumpUnification); return json; } } diff --git a/Common/src/main/java/com/almostreliable/unified/config/Defaults.java b/Common/src/main/java/com/almostreliable/unified/config/Defaults.java index 6eaeb2a..a7f60ff 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/Defaults.java +++ b/Common/src/main/java/com/almostreliable/unified/config/Defaults.java @@ -2,146 +2,115 @@ package com.almostreliable.unified.config; import com.almostreliable.unified.AlmostUnifiedPlatform; import com.almostreliable.unified.utils.JsonCompare; +import com.google.common.collect.ImmutableMap; +import net.minecraft.Util; import net.minecraft.resources.ResourceLocation; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; @SuppressWarnings("SpellCheckingInspection") public final class Defaults { - public static final List STONE_STRATA = List.of( - "stone", - "nether", - "deepslate", - "granite", - "diorite", - "andesite" - ); - public static final List MATERIALS = List.of( - "aeternium", - "aluminum", - "amber", - "apatite", - "bitumen", - "brass", - "bronze", - "charcoal", - "chrome", - "cinnabar", - "coal", - "coal_coke", - "cobalt", - "constantan", - "copper", - "diamond", - "electrum", - "elementium", - "emerald", - "enderium", - "fluorite", - "gold", - "graphite", - "invar", - "iridium", - "iron", - "lapis", - "lead", - "lumium", - "mithril", - "netherite", - "nickel", - "obsidian", - "osmium", - "peridot", - "platinum", - "potassium_nitrate", - "ruby", - "sapphire", - "signalum", - "silver", - "steel", - "sulfur", - "tin", - "tungsten", - "uranium", - "zinc" - ); - private Defaults() {} - public static List getModPriorities(AlmostUnifiedPlatform.Platform platform) { - return switch (platform) { - case FORGE -> List.of( - "minecraft", - "kubejs", - "crafttweaker", - "create", - "thermal", - "immersiveengineering", - "mekanism" - ); - case FABRIC -> List.of( - "minecraft", - "kubejs", - "crafttweaker", - "create", - "techreborn", - "modern_industrialization", - "indrev" - ); - }; - } + public static final List STONE_VARIANTS = List.of( + "stone", + "andesite", + "deepslate", + "diorite", + "granite", + "nether" + ); - public static List getTags(AlmostUnifiedPlatform.Platform platform) { - return switch (platform) { - case FORGE -> List.of( - "forge:nuggets/{material}", - "forge:dusts/{material}", - "forge:gears/{material}", - "forge:gems/{material}", - "forge:ingots/{material}", - "forge:raw_materials/{material}", - "forge:ores/{material}", - "forge:plates/{material}", - "forge:rods/{material}", - "forge:wires/{material}", - "forge:storage_blocks/{material}", - "forge:storage_blocks/raw_{material}" - ); - case FABRIC -> List.of( - "c:{material}_nuggets", - "c:{material}_dusts", - "c:{material}_gears", - "c:{material}_gems", - "c:{material}_ingots", - "c:{material}_raw_materials", - "c:{material}_ores", - "c:{material}_plates", - "c:{material}_rods", - "c:{material}_blocks", - "c:{material}_wires", - "c:{material}_storage_blocks", - "c:raw_{material}_ores", - "c:raw_{material}_blocks", - "c:raw_{material}_storage_blocks" - ); - }; - } + public static final Map> PLACEHOLDERS = Util.make(() -> { + ImmutableMap.Builder> builder = ImmutableMap.builder(); - public static List getIgnoredRecipeTypes(AlmostUnifiedPlatform.Platform platform) { - return switch (platform) { - default -> List.of("cucumber:shaped_tag"); - }; - } + builder.put("material", List.of( + "aeternium", + "aluminum", + "amber", + "apatite", + "bitumen", + "brass", + "bronze", + "chrome", + "cinnabar", + "cobalt", + "constantan", + "copper", + "diamond", + "electrum", + "elementium", + "emerald", + "enderium", + "fluorite", + "gold", + "graphite", + "invar", + "iridium", + "iron", + "lapis", + "lead", + "lumium", + "mithril", + "netherite", + "nickel", + "obsidian", + "osmium", + "peridot", + "platinum", + "potassium_nitrate", + "ruby", + "sapphire", + "signalum", + "silver", + "steel", + "sulfur", + "tin", + "tungsten", + "uranium", + "zinc" + )); + + return builder.build(); + }); + + public static final List MOD_PRIORITIES = Stream.of( + "minecraft", + "kubejs", + "crafttweaker", + "create", + "thermal", + "immersiveengineering", + "mekanism", + "techreborn", + "modern_industrialization", + "indrev" + ).filter(AlmostUnifiedPlatform.INSTANCE::isModLoaded).toList(); + + public static final List TAGS = List.of( + "c:dusts/{material}", + "c:gears/{material}", + "c:gems/{material}", + "c:ingots/{material}", + "c:nuggets/{material}", + "c:ores/{material}", + "c:plates/{material}", + "c:raw_materials/{material}", + "c:rods/{material}", + "c:storage_blocks/raw_{material}", + "c:storage_blocks/{material}", + "c:wires/{material}" + ); + + public static final List IGNORED_RECIPE_TYPES = List.of("cucumber:shaped_tag"); public static JsonCompare.CompareSettings getDefaultDuplicateRules(AlmostUnifiedPlatform.Platform platform) { - JsonCompare.CompareSettings result = new JsonCompare.CompareSettings(); - result.ignoreField(switch (platform) { - case FORGE -> "conditions"; - case FABRIC -> "fabric:load_conditions"; - }); - result.ignoreField("group"); + JsonCompare.CompareSettings result = getDefaultCompareSettings(platform); result.addRule("cookingtime", new JsonCompare.HigherRule()); result.addRule("energy", new JsonCompare.HigherRule()); result.addRule("experience", new JsonCompare.HigherRule()); @@ -149,16 +118,24 @@ public final class Defaults { } public static LinkedHashMap getDefaultDuplicateOverrides(AlmostUnifiedPlatform.Platform platform) { + JsonCompare.CompareSettings result = getDefaultCompareSettings(platform); + result.ignoreField("pattern"); + result.ignoreField("key"); + + LinkedHashMap resultMap = new LinkedHashMap<>(); + resultMap.put(ResourceLocation.withDefaultNamespace("crafting_shaped"), result); + return resultMap; + } + + public static JsonCompare.CompareSettings getDefaultCompareSettings(AlmostUnifiedPlatform.Platform platform) { JsonCompare.CompareSettings result = new JsonCompare.CompareSettings(); result.ignoreField(switch (platform) { - case FORGE -> "conditions"; + case NEO_FORGE -> "neoforge:conditions"; case FABRIC -> "fabric:load_conditions"; }); result.ignoreField("group"); - result.ignoreField("pattern"); - result.ignoreField("key"); - LinkedHashMap resultMap = new LinkedHashMap<>(); - resultMap.put(new ResourceLocation("minecraft", "crafting_shaped"), result); - return resultMap; + result.ignoreField("category"); + result.ignoreField("show_notification"); + return result; } } diff --git a/Common/src/main/java/com/almostreliable/unified/config/DuplicationConfig.java b/Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java similarity index 60% rename from Common/src/main/java/com/almostreliable/unified/config/DuplicationConfig.java rename to Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java index a1c39b5..37be58d 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/DuplicationConfig.java +++ b/Common/src/main/java/com/almostreliable/unified/config/DuplicateConfig.java @@ -1,7 +1,7 @@ package com.almostreliable.unified.config; import com.almostreliable.unified.AlmostUnifiedPlatform; -import com.almostreliable.unified.recipe.RecipeLink; +import com.almostreliable.unified.unification.recipe.RecipeLink; import com.almostreliable.unified.utils.JsonCompare; import com.google.gson.JsonObject; import net.minecraft.resources.ResourceLocation; @@ -10,21 +10,25 @@ import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; -public class DuplicationConfig extends Config { +public final class DuplicateConfig extends Config { + public static final String NAME = "duplicates"; + public static final DuplicateSerializer SERIALIZER = new DuplicateSerializer(); private final JsonCompare.CompareSettings defaultRules; private final LinkedHashMap overrideRules; private final Set ignoreRecipeTypes; - private final Set ignoreRecipes; + private final Set ignoreRecipeIds; private final boolean strictMode; + private final Map ignoredRecipeTypesCache; - public DuplicationConfig(JsonCompare.CompareSettings defaultRules, LinkedHashMap overrideRules, Set ignoreRecipeTypes, Set ignoreRecipes, boolean strictMode) { + private DuplicateConfig(JsonCompare.CompareSettings defaultRules, LinkedHashMap overrideRules, Set ignoreRecipeTypes, Set ignoreRecipeIds, boolean strictMode) { + super(NAME); this.defaultRules = defaultRules; this.overrideRules = overrideRules; this.ignoreRecipeTypes = ignoreRecipeTypes; - this.ignoreRecipes = ignoreRecipes; + this.ignoreRecipeIds = ignoreRecipeIds; this.strictMode = strictMode; this.ignoredRecipeTypesCache = new HashMap<>(); } @@ -34,7 +38,7 @@ public class DuplicationConfig extends Config { return true; } - for (Pattern ignoreRecipePattern : ignoreRecipes) { + for (Pattern ignoreRecipePattern : ignoreRecipeIds) { if (ignoreRecipePattern.matcher(recipe.getId().toString()).matches()) { return true; } @@ -72,35 +76,53 @@ public class DuplicationConfig extends Config { ignoredRecipeTypesCache.clear(); } - public static class Serializer extends Config.Serializer { - public static final String DEFAULT_DUPLICATE_RULES = "defaultDuplicateRules"; - public static final String OVERRIDE_DUPLICATE_RULES = "overrideDuplicateRules"; - public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes"; - public static final String IGNORED_RECIPES = "ignoredRecipes"; - public static final String STRICT_MODE = "strictMode"; + public static final class DuplicateSerializer extends Config.Serializer { + + private static final String DEFAULT_DUPLICATE_RULES = "default_duplicate_rules"; + private static final String OVERRIDE_DUPLICATE_RULES = "override_duplicate_rules"; + private static final String IGNORED_RECIPE_TYPES = "ignored_recipe_types"; + private static final String IGNORED_RECIPE_IDS = "ignored_recipe_ids"; + private static final String STRICT_MODE = "strict_mode"; + + private DuplicateSerializer() {} @Override - public DuplicationConfig deserialize(JsonObject json) { + public DuplicateConfig handleDeserialization(JsonObject json) { var platform = AlmostUnifiedPlatform.INSTANCE.getPlatform(); - Set ignoreRecipeTypes = deserializePatterns(json, + Set ignoreRecipeTypes = deserializePatterns( + json, IGNORED_RECIPE_TYPES, - Defaults.getIgnoredRecipeTypes(platform)); - Set ignoreRecipes = deserializePatterns(json, IGNORED_RECIPES, List.of()); + Defaults.IGNORED_RECIPE_TYPES + ); + Set ignoreRecipeIds = deserializePatterns(json, IGNORED_RECIPE_IDS, List.of()); JsonCompare.CompareSettings defaultRules = safeGet(() -> createCompareSet(json.getAsJsonObject( DEFAULT_DUPLICATE_RULES)), Defaults.getDefaultDuplicateRules(platform)); - LinkedHashMap overrideRules = safeGet(() -> json + LinkedHashMap overrideRules = safeGet(() -> getOverrideRules( + json), Defaults.getDefaultDuplicateOverrides(platform)); + boolean strictMode = safeGet(() -> json.get(STRICT_MODE).getAsBoolean(), false); + + return new DuplicateConfig( + defaultRules, + overrideRules, + ignoreRecipeTypes, + ignoreRecipeIds, + strictMode + ); + } + + // Extracted as method because `safeGet` couldn't cast the type... Seems to be an old SDK bug :-) + // https://bugs.openjdk.org/browse/JDK-8324860 + private LinkedHashMap getOverrideRules(JsonObject json) { + return json .getAsJsonObject(OVERRIDE_DUPLICATE_RULES) .entrySet() .stream() - .collect(Collectors.toMap(entry -> new ResourceLocation(entry.getKey()), + .collect(Collectors.toMap(entry -> ResourceLocation.parse(entry.getKey()), entry -> createCompareSet(entry.getValue().getAsJsonObject()), (a, b) -> b, - LinkedHashMap::new)), Defaults.getDefaultDuplicateOverrides(platform)); - boolean strictMode = safeGet(() -> json.get(STRICT_MODE).getAsBoolean(), false); - - return new DuplicationConfig(defaultRules, overrideRules, ignoreRecipeTypes, ignoreRecipes, strictMode); + LinkedHashMap::new)); } private JsonCompare.CompareSettings createCompareSet(JsonObject rules) { @@ -110,16 +132,15 @@ public class DuplicationConfig extends Config { } @Override - public JsonObject serialize(DuplicationConfig config) { + public JsonObject serialize(DuplicateConfig config) { JsonObject json = new JsonObject(); serializePatterns(json, IGNORED_RECIPE_TYPES, config.ignoreRecipeTypes); - serializePatterns(json, IGNORED_RECIPES, config.ignoreRecipes); + serializePatterns(json, IGNORED_RECIPE_IDS, config.ignoreRecipeIds); json.add(DEFAULT_DUPLICATE_RULES, config.defaultRules.serialize()); JsonObject overrides = new JsonObject(); - config.overrideRules.forEach((rl, compareSettings) -> { - overrides.add(rl.toString(), compareSettings.serialize()); - }); + config.overrideRules.forEach((rl, compareSettings) -> + overrides.add(rl.toString(), compareSettings.serialize())); json.add(OVERRIDE_DUPLICATE_RULES, overrides); json.addProperty(STRICT_MODE, false); diff --git a/Common/src/main/java/com/almostreliable/unified/config/PlaceholderConfig.java b/Common/src/main/java/com/almostreliable/unified/config/PlaceholderConfig.java new file mode 100644 index 0000000..d64826c --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/config/PlaceholderConfig.java @@ -0,0 +1,103 @@ +package com.almostreliable.unified.config; + +import com.almostreliable.unified.api.unification.Placeholders; +import com.almostreliable.unified.utils.JsonUtils; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; + +public final class PlaceholderConfig extends Config implements Placeholders { + + public static final String NAME = "placeholders"; + public static final PlaceholderSerializer SERIALIZER = new PlaceholderSerializer(); + + private final Map> placeholders; + + private PlaceholderConfig(Map> placeholders) { + super(NAME); + this.placeholders = placeholders; + } + + @Override + public Collection apply(String str) { + AtomicReference> inflated = new AtomicReference<>(new HashSet<>()); + inflated.get().add(str); + forEach((placeholder, replacements) -> inflated.set(inflate(inflated.get(), placeholder, replacements))); + return inflated.get(); + } + + @Override + public Collection getPlaceholders() { + return Collections.unmodifiableCollection(placeholders.keySet()); + } + + @Override + public Collection getReplacements(String placeholder) { + return placeholders.getOrDefault(placeholder, Collections.emptyList()); + } + + @Override + public void forEach(BiConsumer> consumer) { + placeholders.forEach(consumer); + } + + private static Collection inflate(Collection values, String placeholder, Collection replacements) { + String formattedPlaceholder = "{" + placeholder + "}"; + Set result = new HashSet<>(); + + for (String value : values) { + for (String replacement : replacements) { + result.add(value.replace(formattedPlaceholder, replacement)); + } + } + + return result; + } + + public static final class PlaceholderSerializer extends Config.Serializer { + + private PlaceholderSerializer() {} + + @Override + public PlaceholderConfig handleDeserialization(JsonObject json) { + // noinspection SizeReplaceableByIsEmpty + if (json.size() == 0) { // json.isEmpty crashes in prod... + setInvalid(); + return new PlaceholderConfig(Defaults.PLACEHOLDERS); + } + + Map> replacements = safeGet(() -> { + ImmutableMap.Builder> builder = ImmutableMap.builder(); + for (var entry : json.entrySet()) { + ImmutableSet.Builder placeholders = ImmutableSet.builder(); + for (JsonElement element : entry.getValue().getAsJsonArray()) { + if (element.isJsonPrimitive()) { + placeholders.add(element.getAsString().trim()); + } + } + + builder.put(entry.getKey().trim(), placeholders.build()); + } + + return builder.build(); + }, Defaults.PLACEHOLDERS); + + return new PlaceholderConfig(replacements); + } + + @Override + public JsonObject serialize(PlaceholderConfig config) { + JsonObject json = new JsonObject(); + for (var entry : config.placeholders.entrySet()) { + json.add(entry.getKey(), JsonUtils.toArray(entry.getValue())); + } + + return json; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java b/Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java deleted file mode 100644 index 42ba7f7..0000000 --- a/Common/src/main/java/com/almostreliable/unified/config/ServerConfigs.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.almostreliable.unified.config; - -import com.almostreliable.unified.AlmostUnifiedPlatform; -import com.almostreliable.unified.utils.FileUtils; - -import java.nio.file.Files; -import java.nio.file.Path; - -public class ServerConfigs { - - private final UnifyConfig unifyConfig; - private final DuplicationConfig dupConfig; - private final DebugConfig debugConfig; - - public static ServerConfigs load() { - createGitIgnoreIfNotExists(); - UnifyConfig unifyConfig = Config.load(UnifyConfig.NAME, new UnifyConfig.Serializer()); - DuplicationConfig dupConfig = Config.load(DuplicationConfig.NAME, new DuplicationConfig.Serializer()); - DebugConfig debugConfig = Config.load(DebugConfig.NAME, new DebugConfig.Serializer()); - return new ServerConfigs(unifyConfig, dupConfig, debugConfig); - } - - private ServerConfigs(UnifyConfig unifyConfig, DuplicationConfig dupConfig, DebugConfig debugConfig) { - this.unifyConfig = unifyConfig; - this.dupConfig = dupConfig; - this.debugConfig = debugConfig; - } - - private static void createGitIgnoreIfNotExists() { - Path path = AlmostUnifiedPlatform.INSTANCE.getConfigPath(); - if (!(Files.exists(path) && Files.isDirectory(path))) { - FileUtils.write( - AlmostUnifiedPlatform.INSTANCE.getConfigPath(), - ".gitignore", - sb -> sb.append(DebugConfig.NAME).append(".json").append("\n") - ); - } - } - - public UnifyConfig getUnifyConfig() { - return unifyConfig; - } - - public DuplicationConfig getDupConfig() { - return dupConfig; - } - - public DebugConfig getDebugConfig() { - return debugConfig; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/config/StartupConfig.java b/Common/src/main/java/com/almostreliable/unified/config/StartupConfig.java index 60fdc68..19cee4c 100644 --- a/Common/src/main/java/com/almostreliable/unified/config/StartupConfig.java +++ b/Common/src/main/java/com/almostreliable/unified/config/StartupConfig.java @@ -1,32 +1,55 @@ package com.almostreliable.unified.config; +import com.almostreliable.unified.AlmostUnifiedPlatform; import com.google.gson.JsonObject; -public class StartupConfig extends Config { +public final class StartupConfig extends Config { public static final String NAME = "startup"; - private final boolean serverOnly; + public static final StartupSerializer SERIALIZER = new StartupSerializer(); - public StartupConfig(boolean serverOnly) { + private final boolean serverOnly; + private final Boolean worldGenUnification; + + private StartupConfig(boolean serverOnly, boolean worldGenUnification) { + super(NAME); this.serverOnly = serverOnly; + this.worldGenUnification = worldGenUnification; } public boolean isServerOnly() { return serverOnly; } - public static class Serializer extends Config.Serializer { - public static final String SERVER_ONLY = "serverOnly"; + public boolean allowWorldGenUnification() { + return worldGenUnification; + } + + public static final class StartupSerializer extends Config.Serializer { + + private static final String SERVER_ONLY = "server_only"; + private static final String WORLD_GEN_UNIFICATION = "world_gen_unification"; + + private StartupSerializer() {} @Override - public StartupConfig deserialize(JsonObject json) { - return new StartupConfig(safeGet(() -> json.get(SERVER_ONLY).getAsBoolean(), false)); + public StartupConfig handleDeserialization(JsonObject json) { + boolean serverOnly = safeGet(() -> json.get(SERVER_ONLY).getAsBoolean(), false); + boolean worldGenUnification = switch (AlmostUnifiedPlatform.INSTANCE.getPlatform()) { + case NEO_FORGE -> safeGet(() -> json.get(WORLD_GEN_UNIFICATION).getAsBoolean(), false); + case FABRIC -> false; + }; + return new StartupConfig(serverOnly, worldGenUnification); } @Override - public JsonObject serialize(StartupConfig src) { + public JsonObject serialize(StartupConfig config) { JsonObject json = new JsonObject(); - json.addProperty(SERVER_ONLY, src.serverOnly); + json.addProperty(SERVER_ONLY, config.serverOnly); + if (AlmostUnifiedPlatform.INSTANCE.getPlatform() == AlmostUnifiedPlatform.Platform.NEO_FORGE) { + json.addProperty(WORLD_GEN_UNIFICATION, config.worldGenUnification); + } + return json; } } diff --git a/Common/src/main/java/com/almostreliable/unified/config/TagConfig.java b/Common/src/main/java/com/almostreliable/unified/config/TagConfig.java new file mode 100644 index 0000000..2017da0 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/config/TagConfig.java @@ -0,0 +1,193 @@ +package com.almostreliable.unified.config; + +import com.almostreliable.unified.AlmostUnifiedPlatform; +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.unification.TagInheritance; +import com.almostreliable.unified.utils.JsonUtils; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +public final class TagConfig extends Config { + + public static final String NAME = "tags"; + public static final TagSerializer SERIALIZER = new TagSerializer(); + + private final Map> customTags; + private final Map> tagSubstitutions; + private final TagInheritance.Mode itemTagInheritanceMode; + private final Map, Set> itemTagInheritance; + private final TagInheritance.Mode blockTagInheritanceMode; + private final Map, Set> blockTagInheritance; + private final boolean emiStrictHiding; + + private TagConfig(Map> customTags, Map> tagSubstitutions, TagInheritance.Mode itemTagInheritanceMode, Map, Set> itemTagInheritance, TagInheritance.Mode blockTagInheritanceMode, Map, Set> blockTagInheritance, boolean emiStrictHiding) { + super(NAME); + this.customTags = customTags; + this.tagSubstitutions = tagSubstitutions; + this.itemTagInheritanceMode = itemTagInheritanceMode; + this.itemTagInheritance = itemTagInheritance; + this.blockTagInheritanceMode = blockTagInheritanceMode; + this.blockTagInheritance = blockTagInheritance; + this.emiStrictHiding = emiStrictHiding; + } + + public TagInheritance getTagInheritance() { + return new TagInheritance(itemTagInheritanceMode, + itemTagInheritance, + blockTagInheritanceMode, + blockTagInheritance); + } + + public Map> getCustomTags() { + return Collections.unmodifiableMap(customTags); + } + + public Map> getTagSubstitutions() { + return Collections.unmodifiableMap(tagSubstitutions); + } + + public boolean isEmiHidingStrict() { + return emiStrictHiding; + } + + public static final class TagSerializer extends Config.Serializer { + + private static final String CUSTOM_TAGS = "custom_tags"; + private static final String TAG_SUBSTITUTIONS = "tag_substitutions"; + private static final String ITEM_TAG_INHERITANCE_MODE = "item_tag_inheritance_mode"; + private static final String ITEM_TAG_INHERITANCE = "item_tag_inheritance"; + private static final String BLOCK_TAG_INHERITANCE_MODE = "block_tag_inheritance_mode"; + private static final String BLOCK_TAG_INHERITANCE = "block_tag_inheritance"; + private static final String EMI_STRICT_HIDING = "emi_strict_hiding"; + + private TagSerializer() {} + + @Override + public TagConfig handleDeserialization(JsonObject json) { + Map> customTags = safeGet(() -> JsonUtils.deserializeMapSet(json, + CUSTOM_TAGS, + e -> ResourceLocation.parse(e.getKey()), + ResourceLocation::parse), new HashMap<>()); + + Map> tagSubstitutions = safeGet(() -> JsonUtils.deserializeMapSet( + json, + TAG_SUBSTITUTIONS, + e -> ResourceLocation.parse(e.getKey()), + ResourceLocation::parse), new HashMap<>()); + + TagInheritance.Mode itemTagInheritanceMode = deserializeTagInheritanceMode(json, + ITEM_TAG_INHERITANCE_MODE); + Map, Set> itemTagInheritance = deserializePatternsForLocations(Registries.ITEM, + json, + ITEM_TAG_INHERITANCE); + TagInheritance.Mode blockTagInheritanceMode = deserializeTagInheritanceMode(json, + BLOCK_TAG_INHERITANCE_MODE); + Map, Set> blockTagInheritance = deserializePatternsForLocations(Registries.BLOCK, + json, + BLOCK_TAG_INHERITANCE); + + boolean emiStrictHiding = AlmostUnifiedPlatform.INSTANCE.isModLoaded(ModConstants.EMI) ? + safeGet(() -> json.get(EMI_STRICT_HIDING).getAsBoolean(), true) : + false; + + return new TagConfig( + customTags, + tagSubstitutions, + itemTagInheritanceMode, + itemTagInheritance, + blockTagInheritanceMode, + blockTagInheritance, + emiStrictHiding + ); + } + + @Override + public JsonObject serialize(TagConfig config) { + JsonObject json = new JsonObject(); + + JsonObject customTags = new JsonObject(); + config.customTags.forEach((parent, child) -> { + customTags.add(parent.toString(), + JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList())); + }); + json.add(CUSTOM_TAGS, customTags); + + JsonObject tagSubstitutions = new JsonObject(); + config.tagSubstitutions.forEach((parent, child) -> { + tagSubstitutions.add(parent.toString(), + JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList())); + }); + json.add(TAG_SUBSTITUTIONS, tagSubstitutions); + + JsonObject itemTagInheritance = new JsonObject(); + config.itemTagInheritance.forEach((tag, patterns) -> { + itemTagInheritance.add(tag.toString(), + JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList())); + }); + json.add(ITEM_TAG_INHERITANCE_MODE, new JsonPrimitive(config.itemTagInheritanceMode.toString())); + json.add(ITEM_TAG_INHERITANCE, itemTagInheritance); + + JsonObject blockTagInheritance = new JsonObject(); + config.blockTagInheritance.forEach((tag, patterns) -> { + blockTagInheritance.add(tag.toString(), + JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList())); + }); + json.add(BLOCK_TAG_INHERITANCE_MODE, new JsonPrimitive(config.blockTagInheritanceMode.toString())); + json.add(BLOCK_TAG_INHERITANCE, blockTagInheritance); + + if (AlmostUnifiedPlatform.INSTANCE.isModLoaded(ModConstants.EMI)) { + json.addProperty(EMI_STRICT_HIDING, config.emiStrictHiding); + } + + return json; + } + + /** + * Deserializes a list of patterns from a json object with a base key. Example json: + *

+         * {
+         *   "baseKey": {
+         *     "location1": [ pattern1, pattern2 ],
+         *     "location2": [ pattern3, pattern4 ]
+         *   }
+         * }
+         * 
+ * + * @param rawConfigJson The raw config json + * @param baseKey The base key + * @return The deserialized patterns separated by location + */ + private Map, Set> unsafeDeserializePatternsForLocations(ResourceKey> registry, JsonObject rawConfigJson, String baseKey) { + return JsonUtils.deserializeMapSet(rawConfigJson, + baseKey, + e -> TagKey.create(registry, ResourceLocation.parse(e.getKey())), + Pattern::compile); + } + + private Map, Set> deserializePatternsForLocations(ResourceKey> registry, JsonObject rawConfigJson, String baseKey) { + return safeGet(() -> unsafeDeserializePatternsForLocations(registry, rawConfigJson, baseKey), + new HashMap<>()); + } + + + private TagInheritance.Mode deserializeTagInheritanceMode(JsonObject json, String key) { + return safeGet(() -> TagInheritance.Mode.valueOf(json + .getAsJsonPrimitive(key) + .getAsString() + .toUpperCase()), TagInheritance.Mode.ALLOW); + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/config/UnificationConfig.java b/Common/src/main/java/com/almostreliable/unified/config/UnificationConfig.java new file mode 100644 index 0000000..c8dd95b --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/config/UnificationConfig.java @@ -0,0 +1,354 @@ +package com.almostreliable.unified.config; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.AlmostUnifiedPlatform; +import com.almostreliable.unified.api.unification.ModPriorities; +import com.almostreliable.unified.api.unification.Placeholders; +import com.almostreliable.unified.unification.ModPrioritiesImpl; +import com.almostreliable.unified.utils.JsonUtils; +import com.google.gson.JsonObject; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public final class UnificationConfig extends Config { + + private static final String SUB_FOLDER = "unification"; + + private final List modPriorities; + private final Map, String> priorityOverrides; + private final List stoneVariants; + private final List tags; + private final Set> ignoredTags; + private final Set ignoredItems; + private final Set ignoredRecipeTypes; + private final Set ignoredRecipeIds; + private final boolean recipeViewerHiding; + private final boolean lootUnification; + private final Set ignoredLootTables; + + private final Map ignoredItemsCache = new HashMap<>(); + private final Map ignoredRecipeTypesCache = new HashMap<>(); + private final Map ignoredRecipeIdsCache = new HashMap<>(); + private final Map ignoredLootTablesCache = new HashMap<>(); + @Nullable private Set> bakedTags; + + private UnificationConfig(String name, List modPriorities, Map, String> priorityOverrides, List stoneVariants, List tags, Set> ignoredTags, Set ignoredItems, Set ignoredRecipeTypes, Set ignoredRecipeIds, boolean recipeViewerHiding, boolean lootUnification, Set ignoredLootTables) { + super(name); + this.modPriorities = modPriorities; + this.priorityOverrides = priorityOverrides; + this.stoneVariants = stoneVariants; + this.tags = tags; + this.ignoredTags = ignoredTags; + this.ignoredItems = ignoredItems; + this.ignoredRecipeTypes = ignoredRecipeTypes; + this.ignoredRecipeIds = ignoredRecipeIds; + this.recipeViewerHiding = recipeViewerHiding; + this.lootUnification = lootUnification; + this.ignoredLootTables = ignoredLootTables; + } + + @SuppressWarnings("StaticMethodOnlyUsedInOneClass") + public static Collection safeLoadConfigs() { + try { + return loadConfigs(); + } catch (Exception e) { + AlmostUnifiedCommon.LOGGER.error("Could not load unify configs.", e); + return List.of(); + } + } + + private static Collection loadConfigs() throws IOException { + Path subFolder = createConfigDir().resolve(SUB_FOLDER); + + var jsons = readJsons(subFolder); + if (jsons.isEmpty()) { + String name = "materials"; + UnifySerializer serializer = new UnifySerializer(name); + UnificationConfig defaultConfig = serializer.deserialize(new JsonObject()); + save(subFolder.resolve(name + ".json"), defaultConfig, serializer); + return List.of(defaultConfig); + } + + Collection configs = new ArrayList<>(); + for (var entry : jsons.entrySet()) { + String name = entry.getKey(); + JsonObject json = entry.getValue(); + + UnifySerializer serializer = new UnifySerializer(name); + var config = serializer.deserialize(json); + if (serializer.isInvalid()) { + save(subFolder.resolve(name + ".json"), config, serializer); + } + + configs.add(config); + } + + logMissingPriorityMods(configs); + return configs; + } + + private static Map readJsons(Path subFolder) throws IOException { + Files.createDirectories(subFolder); + var files = FileUtils.listFiles(subFolder.toFile(), new String[]{ "json" }, false); + + Map jsons = new HashMap<>(); + for (var file : files) { + String fileName = FilenameUtils.getBaseName(file.getName()); + try { + jsons.put(fileName, JsonUtils.readFromFile(file.toPath(), JsonObject.class)); + } catch (Throwable e) { + AlmostUnifiedCommon.LOGGER.error("Unify config '{}.json' could not be loaded.", fileName, e); + } + } + + return jsons; + } + + private static void logMissingPriorityMods(Collection unificationConfigs) { + Set mods = unificationConfigs + .stream() + .map(UnificationConfig::getModPriorities) + .flatMap(ModPriorities::stream) + .filter(m -> !AlmostUnifiedPlatform.INSTANCE.isModLoaded(m)) + .collect(Collectors.toSet()); + + if (mods.isEmpty()) return; + AlmostUnifiedCommon.LOGGER.warn( + "The following mods are used in unification settings, but are not loaded: {}", + mods + ); + } + + public ModPriorities getModPriorities() { + return new ModPrioritiesImpl(modPriorities, priorityOverrides); + } + + public List getStoneVariants() { + return stoneVariants; + } + + public Set> getTags() { + if (bakedTags == null) { + throw new IllegalStateException("unification tags are not baked yet"); + } + + return bakedTags; + } + + public Set> bakeTags(Predicate> tagValidator, Placeholders placeholders) { + if (bakedTags != null) return bakedTags; + + bakedTags = new HashSet<>(); + for (var tag : tags) { + var replacedTags = placeholders.apply(tag); + for (var replacedTag : replacedTags) { + ResourceLocation parsedTag = ResourceLocation.tryParse(replacedTag); + if (parsedTag == null) continue; + + var tagKey = TagKey.create(Registries.ITEM, parsedTag); + if (ignoredTags.contains(tagKey)) continue; + if (!tagValidator.test(tagKey)) continue; + + bakedTags.add(tagKey); + } + } + + return bakedTags; + } + + public boolean shouldIncludeItem(ResourceLocation item) { + return ignoredItemsCache.computeIfAbsent(item, i -> { + for (Pattern pattern : ignoredItems) { + if (pattern.matcher(i.toString()).matches()) { + return false; + } + } + + return true; + }); + } + + public boolean shouldIncludeRecipeType(ResourceLocation type) { + return ignoredRecipeTypesCache.computeIfAbsent(type, t -> { + for (Pattern pattern : ignoredRecipeTypes) { + if (pattern.matcher(t.toString()).matches()) { + return false; + } + } + + return true; + }); + } + + public boolean shouldIncludeRecipeId(ResourceLocation id) { + return ignoredRecipeIdsCache.computeIfAbsent(id, i -> { + for (Pattern pattern : ignoredRecipeIds) { + if (pattern.matcher(i.toString()).matches()) { + return false; + } + } + + return true; + }); + } + + public boolean shouldHideVariantItems() { + return recipeViewerHiding; + } + + public boolean shouldUnifyLoot() { + return lootUnification; + } + + public boolean shouldIncludeLootTable(ResourceLocation table) { + return ignoredLootTablesCache.computeIfAbsent(table, t -> { + for (Pattern pattern : ignoredRecipeIds) { + if (pattern.matcher(t.toString()).matches()) { + return false; + } + } + + return true; + }); + } + + public void clearCaches() { + ignoredItemsCache.clear(); + ignoredRecipeTypesCache.clear(); + ignoredRecipeIdsCache.clear(); + ignoredLootTablesCache.clear(); + } + + public static final class UnifySerializer extends Config.Serializer { + + private static final String MOD_PRIORITIES = "mod_priorities"; + private static final String PRIORITY_OVERRIDES = "priority_overrides"; + private static final String STONE_VARIANTS = "stone_variants"; + private static final String TAGS = "tags"; + private static final String IGNORED_TAGS = "ignored_tags"; + private static final String IGNORED_ITEMS = "ignored_items"; + private static final String IGNORED_RECIPE_TYPES = "ignored_recipe_types"; + private static final String IGNORED_RECIPES_IDS = "ignored_recipe_ids"; + private static final String RECIPE_VIEWER_HIDING = "recipe_viewer_hiding"; + private static final String LOOT_UNIFICATION = "loot_unification"; + private static final String IGNORED_LOOT_TABLES = "ignored_loot_tables"; + + private final String name; + + private UnifySerializer(String name) { + this.name = name; + } + + @Override + public UnificationConfig handleDeserialization(JsonObject json) { + List modPriorities = safeGet( + () -> JsonUtils.toList(json.getAsJsonArray(MOD_PRIORITIES)), + Defaults.MOD_PRIORITIES + ); + Map, String> priorityOverrides = safeGet( + () -> JsonUtils.deserializeMap( + json, + PRIORITY_OVERRIDES, + e -> TagKey.create(Registries.ITEM, ResourceLocation.parse(e.getKey())), + e -> e.getValue().getAsString() + ), + new HashMap<>() + ); + + List stoneVariants = safeGet( + () -> JsonUtils.toList(json.getAsJsonArray(STONE_VARIANTS)), + Defaults.STONE_VARIANTS + ); + + List tags = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(TAGS)), Defaults.TAGS); + Set> ignoredTags = safeGet( + () -> JsonUtils + .toList(json.getAsJsonArray(IGNORED_TAGS)) + .stream() + .map(s -> TagKey.create(Registries.ITEM, ResourceLocation.parse(s))) + .collect(Collectors.toSet()), + new HashSet<>() + ); + Set ignoredItems = deserializePatterns(json, IGNORED_ITEMS, List.of()); + Set ignoredRecipeTypes = deserializePatterns( + json, + IGNORED_RECIPE_TYPES, + Defaults.IGNORED_RECIPE_TYPES + ); + Set ignoredRecipeIds = deserializePatterns(json, IGNORED_RECIPES_IDS, List.of()); + + boolean recipeViewerHiding = safeGet( + () -> json.getAsJsonPrimitive(RECIPE_VIEWER_HIDING).getAsBoolean(), + true + ); + + boolean lootUnification = safeGet( + () -> json.getAsJsonPrimitive(LOOT_UNIFICATION).getAsBoolean(), + false + ); + Set ignoredLootTables = deserializePatterns(json, IGNORED_LOOT_TABLES, List.of()); + + return new UnificationConfig( + name, + modPriorities, + priorityOverrides, + stoneVariants, + tags, + ignoredTags, + ignoredItems, + ignoredRecipeTypes, + ignoredRecipeIds, + recipeViewerHiding, + lootUnification, + ignoredLootTables + ); + } + + @Override + public JsonObject serialize(UnificationConfig config) { + JsonObject json = new JsonObject(); + + json.add(MOD_PRIORITIES, JsonUtils.toArray(config.modPriorities)); + JsonObject priorityOverrides = new JsonObject(); + config.priorityOverrides.forEach( + (tag, mod) -> priorityOverrides.addProperty(tag.location().toString(), mod) + ); + json.add(PRIORITY_OVERRIDES, priorityOverrides); + + json.add(STONE_VARIANTS, JsonUtils.toArray(config.stoneVariants)); + + json.add(TAGS, JsonUtils.toArray(config.tags)); + json.add( + IGNORED_TAGS, + JsonUtils.toArray(config.ignoredTags + .stream() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList()) + ); + serializePatterns(json, IGNORED_ITEMS, config.ignoredItems); + serializePatterns(json, IGNORED_RECIPE_TYPES, config.ignoredRecipeTypes); + serializePatterns(json, IGNORED_RECIPES_IDS, config.ignoredRecipeIds); + + json.addProperty(RECIPE_VIEWER_HIDING, config.recipeViewerHiding); + + json.addProperty(LOOT_UNIFICATION, config.lootUnification); + serializePatterns(json, IGNORED_LOOT_TABLES, config.ignoredLootTables); + + return json; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java b/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java deleted file mode 100644 index 4fbd047..0000000 --- a/Common/src/main/java/com/almostreliable/unified/config/UnifyConfig.java +++ /dev/null @@ -1,420 +0,0 @@ -package com.almostreliable.unified.config; - -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.AlmostUnifiedPlatform; -import com.almostreliable.unified.utils.JsonUtils; -import com.almostreliable.unified.utils.UnifyTag; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import net.minecraft.core.Holder; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.Block; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class UnifyConfig extends Config { - public static final String NAME = "unify"; - - private final List modPriorities; - private final List stoneStrata; - private final List unbakedTags; - private final List materials; - private final Map priorityOverrides; - private final Map> customTags; - private final Map> tagOwnerships; - private final Enum itemTagInheritanceMode; - private final Map> itemTagInheritance; - private final Enum blockTagInheritanceMode; - private final Map> blockTagInheritance; - private final Set> ignoredTags; - private final Set ignoredItems; - private final Set ignoredRecipeTypes; - private final Set ignoredRecipes; - private final boolean hideJeiRei; - - private final Map ignoredRecipeTypesCache; - @Nullable private Set> bakedTagsCache; - - public UnifyConfig( - List modPriorities, - List stoneStrata, - List unbakedTags, - List materials, - Map priorityOverrides, - Map> customTags, - Map> tagOwnerships, - Enum itemTagInheritanceMode, - Map> itemTagInheritance, - Enum blockTagInheritanceMode, - Map> blockTagInheritance, - Set> ignoredTags, - Set ignoredItems, - Set ignoredRecipeTypes, - Set ignoredRecipes, - boolean hideJeiRei - ) { - this.modPriorities = modPriorities; - this.stoneStrata = stoneStrata; - this.unbakedTags = unbakedTags; - this.materials = materials; - this.priorityOverrides = priorityOverrides; - this.customTags = customTags; - this.tagOwnerships = tagOwnerships; - this.itemTagInheritanceMode = itemTagInheritanceMode; - this.itemTagInheritance = itemTagInheritance; - this.blockTagInheritanceMode = blockTagInheritanceMode; - this.blockTagInheritance = blockTagInheritance; - this.ignoredTags = ignoredTags; - this.ignoredItems = ignoredItems; - this.ignoredRecipeTypes = ignoredRecipeTypes; - this.ignoredRecipes = ignoredRecipes; - this.hideJeiRei = hideJeiRei; - this.ignoredRecipeTypesCache = new HashMap<>(); - } - - public List getModPriorities() { - return Collections.unmodifiableList(modPriorities); - } - - public List getStoneStrata() { - return Collections.unmodifiableList(stoneStrata); - } - - public Set> bakeTags() { - return bakeTags($ -> true); - } - - public Set> bakeAndValidateTags(Map>> tags) { - return bakeTags(tags::containsKey); - } - - private Set> bakeTags(Predicate tagValidator) { - if (bakedTagsCache != null) { - return bakedTagsCache; - } - - Set> result = new HashSet<>(); - Set> wrongTags = new HashSet<>(); - - for (String tag : unbakedTags) { - for (String material : materials) { - String replace = tag.replace("{material}", material); - ResourceLocation asRL = ResourceLocation.tryParse(replace); - if (asRL == null) { - AlmostUnified.LOG.warn("Could not bake tag <{}> with material <{}>", tag, material); - continue; - } - - UnifyTag t = UnifyTag.item(asRL); - if (ignoredTags.contains(t)) continue; - - if (!tagValidator.test(asRL)) { - wrongTags.add(t); - continue; - } - - result.add(t); - } - } - - if (!wrongTags.isEmpty()) { - AlmostUnified.LOG.warn( - "The following tags are invalid and will be ignored: {}", - wrongTags.stream().map(UnifyTag::location).collect(Collectors.toList()) - ); - } - - bakedTagsCache = result; - return result; - } - - // exposed for KubeJS binding - @SuppressWarnings("unused") - public List getMaterials() { - return Collections.unmodifiableList(materials); - } - - public Map getPriorityOverrides() { - return Collections.unmodifiableMap(priorityOverrides); - } - - public Map> getCustomTags() { - return Collections.unmodifiableMap(customTags); - } - - public Map> getTagOwnerships() { - return Collections.unmodifiableMap(tagOwnerships); - } - - public boolean shouldInheritItemTag(UnifyTag itemTag, Set> dominantTags) { - var patterns = itemTagInheritance.get(itemTag.location()); - boolean result = checkPatterns(dominantTags, patterns); - // noinspection SimplifiableConditionalExpression - return itemTagInheritanceMode == TagInheritanceMode.ALLOW ? result : !result; - } - - public boolean shouldInheritBlockTag(UnifyTag itemTag, Set> dominantTags) { - var patterns = blockTagInheritance.get(itemTag.location()); - boolean result = checkPatterns(dominantTags, patterns); - // noinspection SimplifiableConditionalExpression - return blockTagInheritanceMode == TagInheritanceMode.ALLOW ? result : !result; - } - - /** - * Checks all patterns against all dominant tags. - *

- * This implementation works based on the assumption that the mode is {@link TagInheritanceMode#ALLOW}. - * Flip the result if the mode is {@link TagInheritanceMode#DENY}. - * - * @param dominantTags The tags of the dominant item to check. - * @param patterns The patterns to check against. - * @param The type of the dominant tags. - * @return Whether the dominant tags match any of the patterns. - */ - private static boolean checkPatterns(Set> dominantTags, @Nullable Set patterns) { - if (patterns == null) return false; - - for (var pattern : patterns) { - for (var dominantTag : dominantTags) { - if (pattern.matcher(dominantTag.location().toString()).matches()) { - return true; - } - } - } - - return false; - } - - public boolean includeItem(ResourceLocation item) { - for (Pattern pattern : ignoredItems) { - if (pattern.matcher(item.toString()).matches()) { - return false; - } - } - return true; - } - - public boolean includeRecipe(ResourceLocation recipe) { - for (Pattern pattern : ignoredRecipes) { - if (pattern.matcher(recipe.toString()).matches()) { - return false; - } - } - return true; - } - - public boolean includeRecipeType(ResourceLocation type) { - return ignoredRecipeTypesCache.computeIfAbsent(type, t -> { - for (Pattern pattern : ignoredRecipeTypes) { - if (pattern.matcher(t.toString()).matches()) { - return false; - } - } - return true; - }); - } - - public boolean reiOrJeiDisabled() { - return !hideJeiRei; - } - - public void clearCache() { - ignoredRecipeTypesCache.clear(); - } - - public static class Serializer extends Config.Serializer { - - public static final String MOD_PRIORITIES = "modPriorities"; - public static final String STONE_STRATA = "stoneStrata"; - public static final String TAGS = "tags"; - public static final String MATERIALS = "materials"; - public static final String PRIORITY_OVERRIDES = "priorityOverrides"; - public static final String CUSTOM_TAGS = "customTags"; - public static final String TAG_OWNERSHIPS = "tagOwnerships"; - public static final String ITEM_TAG_INHERITANCE_MODE = "itemTagInheritanceMode"; - public static final String ITEM_TAG_INHERITANCE = "itemTagInheritance"; - public static final String BLOCK_TAG_INHERITANCE_MODE = "blockTagInheritanceMode"; - public static final String BLOCK_TAG_INHERITANCE = "blockTagInheritance"; - public static final String IGNORED_TAGS = "ignoredTags"; - public static final String IGNORED_ITEMS = "ignoredItems"; - public static final String IGNORED_RECIPE_TYPES = "ignoredRecipeTypes"; - public static final String IGNORED_RECIPES = "ignoredRecipes"; - public static final String HIDE_JEI_REI = "itemsHidingJeiRei"; - - @Override - public UnifyConfig deserialize(JsonObject json) { - var platform = AlmostUnifiedPlatform.INSTANCE.getPlatform(); - List modPriorities = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(MOD_PRIORITIES)), - Defaults.getModPriorities(platform)); - List stoneStrata = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(STONE_STRATA)), - Defaults.STONE_STRATA); - List tags = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(TAGS)), Defaults.getTags(platform)); - List materials = safeGet(() -> JsonUtils.toList(json.getAsJsonArray(MATERIALS)), - Defaults.MATERIALS); - - Map priorityOverrides = safeGet( - () -> JsonUtils.deserializeMap( - json, - PRIORITY_OVERRIDES, - e -> new ResourceLocation(e.getKey()), - e -> e.getValue().getAsString() - ), - new HashMap<>() - ); - - Map> customTags = safeGet( - () -> JsonUtils.deserializeMapSet( - json, - CUSTOM_TAGS, - e -> new ResourceLocation(e.getKey()), - ResourceLocation::new - ), - new HashMap<>() - ); - - Map> tagOwnerships = safeGet( - () -> JsonUtils.deserializeMapSet( - json, - TAG_OWNERSHIPS, - e -> new ResourceLocation(e.getKey()), - ResourceLocation::new - ), - new HashMap<>() - ); - - Enum itemTagInheritanceMode = deserializeTagInheritanceMode(json, - ITEM_TAG_INHERITANCE_MODE); - Map> itemTagInheritance = deserializePatternsForLocations(json, - ITEM_TAG_INHERITANCE); - Enum blockTagInheritanceMode = deserializeTagInheritanceMode(json, - BLOCK_TAG_INHERITANCE_MODE); - Map> blockTagInheritance = deserializePatternsForLocations(json, - BLOCK_TAG_INHERITANCE); - - Set> ignoredTags = safeGet(() -> JsonUtils - .toList(json.getAsJsonArray(IGNORED_TAGS)) - .stream() - .map(s -> UnifyTag.item(new ResourceLocation(s))) - .collect(Collectors.toSet()), new HashSet<>()); - Set ignoredItems = deserializePatterns(json, IGNORED_ITEMS, List.of()); - Set ignoredRecipeTypes = deserializePatterns(json, IGNORED_RECIPE_TYPES, - Defaults.getIgnoredRecipeTypes(platform)); - Set ignoredRecipes = deserializePatterns(json, IGNORED_RECIPES, List.of()); - boolean hideJeiRei = safeGet(() -> json.getAsJsonPrimitive(HIDE_JEI_REI).getAsBoolean(), true); - - return new UnifyConfig( - modPriorities, - stoneStrata, - tags, - materials, - priorityOverrides, - customTags, - tagOwnerships, - itemTagInheritanceMode, - itemTagInheritance, - blockTagInheritanceMode, - blockTagInheritance, - ignoredTags, - ignoredItems, - ignoredRecipeTypes, - ignoredRecipes, - hideJeiRei - ); - } - - private TagInheritanceMode deserializeTagInheritanceMode(JsonObject json, String key) { - return safeGet(() -> TagInheritanceMode.valueOf(json - .getAsJsonPrimitive(key) - .getAsString().toUpperCase()), TagInheritanceMode.ALLOW); - } - - /** - * Deserializes a list of patterns from a json object with a base key. Example json: - *

-         * {
-         *   "baseKey": {
-         *     "location1": [ pattern1, pattern2 ],
-         *     "location2": [ pattern3, pattern4 ]
-         *   }
-         * }
-         * 
- * - * @param rawConfigJson The raw config json - * @param baseKey The base key - * @return The deserialized patterns separated by location - */ - private Map> unsafeDeserializePatternsForLocations(JsonObject rawConfigJson, String baseKey) { - return JsonUtils.deserializeMapSet( - rawConfigJson, - baseKey, - e -> new ResourceLocation(e.getKey()), - Pattern::compile - ); - } - - private Map> deserializePatternsForLocations(JsonObject rawConfigJson, String baseKey) { - return safeGet(() -> unsafeDeserializePatternsForLocations(rawConfigJson, baseKey), new HashMap<>()); - } - - @Override - public JsonObject serialize(UnifyConfig config) { - JsonObject json = new JsonObject(); - json.add(MOD_PRIORITIES, JsonUtils.toArray(config.modPriorities)); - json.add(STONE_STRATA, JsonUtils.toArray(config.stoneStrata)); - json.add(TAGS, JsonUtils.toArray(config.unbakedTags)); - json.add(MATERIALS, JsonUtils.toArray(config.materials)); - JsonObject priorityOverrides = new JsonObject(); - config.priorityOverrides.forEach((tag, mod) -> { - priorityOverrides.addProperty(tag.toString(), mod); - }); - json.add(PRIORITY_OVERRIDES, priorityOverrides); - JsonObject customTags = new JsonObject(); - config.customTags.forEach((parent, child) -> { - customTags.add(parent.toString(), - JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList())); - }); - json.add(CUSTOM_TAGS, customTags); - JsonObject tagOwnerships = new JsonObject(); - config.tagOwnerships.forEach((parent, child) -> { - tagOwnerships.add(parent.toString(), - JsonUtils.toArray(child.stream().map(ResourceLocation::toString).toList())); - }); - json.add(TAG_OWNERSHIPS, tagOwnerships); - JsonObject itemTagInheritance = new JsonObject(); - config.itemTagInheritance.forEach((tag, patterns) -> { - itemTagInheritance.add(tag.toString(), - JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList())); - }); - json.add(ITEM_TAG_INHERITANCE_MODE, new JsonPrimitive(config.itemTagInheritanceMode.toString())); - json.add(ITEM_TAG_INHERITANCE, itemTagInheritance); - JsonObject blockTagInheritance = new JsonObject(); - config.blockTagInheritance.forEach((tag, patterns) -> { - blockTagInheritance.add(tag.toString(), - JsonUtils.toArray(patterns.stream().map(Pattern::toString).toList())); - }); - json.add(BLOCK_TAG_INHERITANCE_MODE, new JsonPrimitive(config.blockTagInheritanceMode.toString())); - json.add(BLOCK_TAG_INHERITANCE, blockTagInheritance); - json.add(IGNORED_TAGS, - JsonUtils.toArray(config.ignoredTags - .stream() - .map(UnifyTag::location) - .map(ResourceLocation::toString) - .toList())); - serializePatterns(json, IGNORED_ITEMS, config.ignoredItems); - serializePatterns(json, IGNORED_RECIPE_TYPES, config.ignoredRecipeTypes); - serializePatterns(json, IGNORED_RECIPES, config.ignoredRecipes); - json.addProperty(HIDE_JEI_REI, config.hideJeiRei); - return json; - } - } - - public enum TagInheritanceMode { - ALLOW, - DENY - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedImpl.java b/Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedImpl.java new file mode 100644 index 0000000..28b3251 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedImpl.java @@ -0,0 +1,85 @@ +package com.almostreliable.unified.core; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.AlmostUnifiedRuntime; +import com.almostreliable.unified.api.unification.UnificationEntry; +import com.google.auto.service.AutoService; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +@AutoService(AlmostUnified.class) +public class AlmostUnifiedImpl implements AlmostUnified { + + @Override + public boolean isRuntimeLoaded() { + return getRuntime() != null; + } + + @Nullable + @Override + public AlmostUnifiedRuntime getRuntime() { + return AlmostUnifiedCommon.getRuntime(); + } + + @Override + public AlmostUnifiedRuntime getRuntimeOrThrow() { + AlmostUnifiedRuntime runtime = AlmostUnifiedCommon.getRuntime(); + if (runtime == null) { + throw new IllegalStateException("runtime is not loaded"); + } + + return runtime; + } + + @Override + public Collection> getTags() { + if (!isRuntimeLoaded()) return Set.of(); + return getRuntimeOrThrow().getUnificationLookup().getTags(); + } + + @Override + public Collection getTagEntries(TagKey tag) { + if (!isRuntimeLoaded()) return Set.of(); + return getRuntimeOrThrow() + .getUnificationLookup() + .getTagEntries(tag) + .stream() + .map(UnificationEntry::value) + .collect(Collectors.toSet()); + } + + @Nullable + @Override + public TagKey getRelevantItemTag(ItemLike itemLike) { + if (!isRuntimeLoaded()) return null; + return getRuntimeOrThrow().getUnificationLookup().getRelevantItemTag(itemLike.asItem()); + } + + @Nullable + @Override + public Item getVariantItemTarget(ItemLike itemLike) { + if (!isRuntimeLoaded()) return null; + + ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemLike.asItem()); + var replacement = getRuntimeOrThrow().getUnificationLookup().getVariantItemTarget(id); + return replacement == null ? null : replacement.value(); + } + + @Nullable + @Override + public Item getTagTargetItem(TagKey tag) { + if (!isRuntimeLoaded()) return null; + + var replacement = getRuntimeOrThrow().getUnificationLookup().getTagTargetItem(tag); + return replacement == null ? null : replacement.value(); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedRuntimeImpl.java b/Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedRuntimeImpl.java new file mode 100644 index 0000000..d9546b9 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/core/AlmostUnifiedRuntimeImpl.java @@ -0,0 +1,306 @@ +package com.almostreliable.unified.core; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.AlmostUnifiedRuntime; +import com.almostreliable.unified.api.unification.*; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import com.almostreliable.unified.compat.PluginManager; +import com.almostreliable.unified.compat.viewer.ItemHider; +import com.almostreliable.unified.config.Config; +import com.almostreliable.unified.config.PlaceholderConfig; +import com.almostreliable.unified.config.TagConfig; +import com.almostreliable.unified.config.UnificationConfig; +import com.almostreliable.unified.unification.TagInheritance; +import com.almostreliable.unified.unification.TagSubstitutionsImpl; +import com.almostreliable.unified.unification.UnificationSettingsImpl; +import com.almostreliable.unified.unification.recipe.RecipeTransformer; +import com.almostreliable.unified.unification.recipe.RecipeUnifierRegistryImpl; +import com.almostreliable.unified.utils.DebugHandler; +import com.almostreliable.unified.utils.FileUtils; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import com.google.gson.JsonElement; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.block.Block; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public final class AlmostUnifiedRuntimeImpl implements AlmostUnifiedRuntime { + + private final Collection unificationSettings; + private final RecipeUnifierRegistry recipeUnifierRegistry; + private final TagSubstitutions tagSubstitutions; + private final Placeholders placeholders; + private final UnificationLookup compositeUnificationLookup; + + private AlmostUnifiedRuntimeImpl(Collection unificationSettings, RecipeUnifierRegistry recipeUnifierRegistry, TagSubstitutions tagSubstitutions, Placeholders placeholders) { + this.unificationSettings = unificationSettings; + this.recipeUnifierRegistry = recipeUnifierRegistry; + this.tagSubstitutions = tagSubstitutions; + this.placeholders = placeholders; + this.compositeUnificationLookup = new CompositeUnificationLookup(unificationSettings); + } + + public static AlmostUnifiedRuntimeImpl create(VanillaTagWrapper itemTags, VanillaTagWrapper blockTags) { + AlmostUnifiedCommon.LOGGER.warn("Reload detected. Reconstructing runtime."); + + FileUtils.createGitIgnore(); + var tagConfig = Config.load(TagConfig.NAME, TagConfig.SERIALIZER); + var placeholderConfig = Config.load(PlaceholderConfig.NAME, PlaceholderConfig.SERIALIZER); + var unificationConfigs = UnificationConfig.safeLoadConfigs(); + + var unificationTags = bakeAndValidateTags(unificationConfigs, itemTags, placeholderConfig); + + RecipeUnifierRegistry recipeUnifierRegistry = new RecipeUnifierRegistryImpl(); + PluginManager.instance().registerRecipeUnifiers(recipeUnifierRegistry); + // TODO: add plugin support for registering config defaults + + TagReloadHandler.applyCustomTags(tagConfig.getCustomTags(), itemTags); + TagSubstitutionsImpl tagSubstitutions = TagSubstitutionsImpl.create( + itemTags::has, + unificationTags::contains, + tagConfig.getTagSubstitutions() + ); + tagSubstitutions.apply(itemTags); + + List unificationSettings = createUnificationLookups( + itemTags, + blockTags, + unificationConfigs, + tagSubstitutions, + tagConfig.getTagInheritance() + ); + ItemHider.applyHideTags(itemTags, unificationSettings, tagConfig.isEmiHidingStrict()); + + return new AlmostUnifiedRuntimeImpl( + unificationSettings, + recipeUnifierRegistry, + tagSubstitutions, + placeholderConfig + ); + } + + /** + * Bake all tags from unify configs and validate them. + * Validating contains: + *
    + *
  • Tag must exist in vanilla tags, which means that the tag is in used by either vanilla or any mods.
  • + *
  • Tag must not exist in another unify config. If found, the tag will be skipped.
  • + *
+ * + * @param unificationConfigs The unify configs + * @param itemTags The vanilla tags + * @param placeholders The replacements + * @return The baked tags combined from all unify configs + */ + private static Set> bakeAndValidateTags(Collection unificationConfigs, VanillaTagWrapper itemTags, Placeholders placeholders) { + Set> result = new HashSet<>(); + + Map, String> visitedTags = new HashMap<>(); + Set> wrongTags = new HashSet<>(); + + for (UnificationConfig config : unificationConfigs) { + Predicate> validator = tag -> { + if (!itemTags.has(tag)) { + wrongTags.add(tag); + return false; + } + + if (visitedTags.containsKey(tag)) { + AlmostUnifiedCommon.LOGGER.warn( + "Tag '{}' from unify config '{}' was already created in unify config '{}'", + config.getName(), + tag.location(), + visitedTags.get(tag)); + return false; + } + + visitedTags.put(tag, config.getName()); + return true; + }; + + Set> tags = config.bakeTags(validator, placeholders); + result.addAll(tags); + } + + if (!wrongTags.isEmpty()) { + AlmostUnifiedCommon.LOGGER.warn("The following tags are invalid or not in use and will be ignored: {}", + wrongTags.stream().map(TagKey::location).collect(Collectors.toList())); + } + + return result; + } + + /** + * Creates all unify handlers for further unification. + *

+ * This method also applies tag inheritance. If tag inheritance was applied, all handlers will be rebuilt due to tag inheritance modifications against vanilla tags. + * + * @param itemTags All existing item tags which are used ingame + * @param blockTags All existing block tags which are used ingame + * @param unificationConfigs All unify configs + * @param tagSubstitutions All tag substitutions + * @param tagInheritance Tag inheritance data + * @return All unify handlers + */ + private static List createUnificationLookups(VanillaTagWrapper itemTags, VanillaTagWrapper blockTags, Collection unificationConfigs, TagSubstitutionsImpl tagSubstitutions, TagInheritance tagInheritance) { + var unificationSettings = UnificationSettingsImpl.create(unificationConfigs, + itemTags, + blockTags, + tagSubstitutions); + + var needsRebuild = tagInheritance.apply(itemTags, blockTags, unificationSettings); + if (needsRebuild) { + return UnificationSettingsImpl.create(unificationConfigs, itemTags, blockTags, tagSubstitutions); + } + + return unificationSettings; + } + + public void run(Map recipes) { + DebugHandler debugHandler = DebugHandler.onRunStart(recipes, compositeUnificationLookup); + + debugHandler.measure(() -> { + var transformer = new RecipeTransformer(recipeUnifierRegistry, unificationSettings); + return transformer.transformRecipes(recipes); + }); + + debugHandler.onRunEnd(recipes); + } + + @Override + public UnificationLookup getUnificationLookup() { + return compositeUnificationLookup; + } + + @Override + public Collection getUnificationSettings() { + return Collections.unmodifiableCollection(unificationSettings); + } + + @Nullable + @Override + public UnificationSettings getUnificationSettings(String name) { + for (UnificationSettings settings : unificationSettings) { + if (settings.getName().equals(name)) { + return settings; + } + } + + return null; + } + + @Override + public TagSubstitutions getTagSubstitutions() { + return tagSubstitutions; + } + + @Override + public Placeholders getPlaceholders() { + return placeholders; + } + + private static final class CompositeUnificationLookup implements UnificationLookup { + + private final Iterable unificationLookups; + + @Nullable private Collection> unificationTagsView; + + private CompositeUnificationLookup(Iterable unificationLookups) { + this.unificationLookups = unificationLookups; + } + + @Override + public Collection> getTags() { + if (unificationTagsView == null) { + Set> tagView = new HashSet<>(); + for (var unificationLookup : unificationLookups) { + tagView.addAll(unificationLookup.getTags()); + } + + unificationTagsView = Collections.unmodifiableCollection(tagView); + } + + return unificationTagsView; + } + + @Override + public Collection> getTagEntries(TagKey tag) { + for (var unificationLookup : unificationLookups) { + var resultItems = unificationLookup.getTagEntries(tag); + if (!resultItems.isEmpty()) { + return resultItems; + } + } + + return Collections.emptyList(); + } + + @Nullable + @Override + public UnificationEntry getItemEntry(ResourceLocation item) { + for (var unificationLookup : unificationLookups) { + var resultItem = unificationLookup.getItemEntry(item); + if (resultItem != null) { + return resultItem; + } + } + + return null; + } + + @Nullable + @Override + public TagKey getRelevantItemTag(ResourceLocation item) { + for (var unificationLookup : unificationLookups) { + TagKey tag = unificationLookup.getRelevantItemTag(item); + if (tag != null) { + return tag; + } + } + + return null; + } + + @Override + public UnificationEntry getVariantItemTarget(ResourceLocation item) { + for (var unificationLookup : unificationLookups) { + var resultItem = unificationLookup.getVariantItemTarget(item); + if (resultItem != null) { + return resultItem; + } + } + + return null; + } + + @Override + public UnificationEntry getTagTargetItem(TagKey tag, Predicate itemFilter) { + for (var unificationLookup : unificationLookups) { + var result = unificationLookup.getTagTargetItem(tag, itemFilter); + if (result != null) { + return result; + } + } + + return null; + } + + @Override + public boolean isUnifiedIngredientItem(Ingredient ingredient, ItemStack item) { + for (var unificationLookup : unificationLookups) { + if (unificationLookup.isUnifiedIngredientItem(ingredient, item)) { + return true; + } + } + + return false; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/core/CommonPlugin.java b/Common/src/main/java/com/almostreliable/unified/core/CommonPlugin.java new file mode 100644 index 0000000..0daf41f --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/core/CommonPlugin.java @@ -0,0 +1,23 @@ +package com.almostreliable.unified.core; + +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.plugin.AlmostUnifiedNeoPlugin; +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import com.almostreliable.unified.compat.unification.GregTechModernRecipeUnifier; +import com.almostreliable.unified.utils.Utils; +import net.minecraft.resources.ResourceLocation; + +@AlmostUnifiedNeoPlugin +public class CommonPlugin implements AlmostUnifiedPlugin { + + @Override + public ResourceLocation getPluginId() { + return Utils.getRL("common"); + } + + @Override + public void registerRecipeUnifiers(RecipeUnifierRegistry registry) { + registry.registerForModId(ModConstants.GREGTECH_MODERN, new GregTechModernRecipeUnifier()); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/core/TagReloadHandler.java b/Common/src/main/java/com/almostreliable/unified/core/TagReloadHandler.java new file mode 100644 index 0000000..8678816 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/core/TagReloadHandler.java @@ -0,0 +1,99 @@ +package com.almostreliable.unified.core; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings({ "StaticVariableMayNotBeInitialized", "StaticVariableUsedBeforeInitialization" }) +public final class TagReloadHandler { + + private static final Object LOCK = new Object(); + @Nullable private static VanillaTagWrapper VANILLA_ITEM_TAGS; + @Nullable private static VanillaTagWrapper VANILLA_BLOCK_TAGS; + + private TagReloadHandler() {} + + public static void initItemTags(Map>> rawItemTags) { + synchronized (LOCK) { + VANILLA_ITEM_TAGS = VanillaTagWrapper.of(BuiltInRegistries.ITEM, rawItemTags); + } + } + + public static void initBlockTags(Map>> rawBlockTags) { + synchronized (LOCK) { + VANILLA_BLOCK_TAGS = VanillaTagWrapper.of(BuiltInRegistries.BLOCK, rawBlockTags); + } + } + + public static void run() { + if (VANILLA_ITEM_TAGS == null || VANILLA_BLOCK_TAGS == null) { + return; + } + + AlmostUnifiedCommon.onTagLoaderReload(VANILLA_ITEM_TAGS, VANILLA_BLOCK_TAGS); + + VANILLA_ITEM_TAGS.seal(); + VANILLA_BLOCK_TAGS.seal(); + VANILLA_ITEM_TAGS = null; + VANILLA_BLOCK_TAGS = null; + } + + public static void applyCustomTags(Map> customTags, VanillaTagWrapper itemTags) { + Multimap changedItemTags = HashMultimap.create(); + + for (var entry : customTags.entrySet()) { + ResourceLocation tag = entry.getKey(); + Set itemIds = entry.getValue(); + + for (ResourceLocation itemId : itemIds) { + if (!BuiltInRegistries.ITEM.containsKey(itemId)) { + AlmostUnifiedCommon.LOGGER.warn("[CustomTags] Custom tag '{}' contains invalid item '{}'", + tag, + itemId); + continue; + } + + ResourceKey itemKey = ResourceKey.create(Registries.ITEM, itemId); + Holder itemHolder = BuiltInRegistries.ITEM.getHolder(itemKey).orElse(null); + if (itemHolder == null) continue; + + var currentHolders = itemTags.get(tag); + + if (!currentHolders.isEmpty()) { + if (currentHolders.contains(itemHolder)) { + AlmostUnifiedCommon.LOGGER.warn("[CustomTags] Custom tag '{}' already contains item '{}'", + tag, + itemId); + continue; + } + } + + itemTags.add(tag, itemHolder); + changedItemTags.put(tag, itemId); + } + } + + if (!changedItemTags.isEmpty()) { + changedItemTags.asMap().forEach( + (tag, items) -> AlmostUnifiedCommon.LOGGER.info( + "[CustomTags] Modified tag '#{}', added {}", + tag, + items + ) + ); + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/package-info.java b/Common/src/main/java/com/almostreliable/unified/core/package-info.java similarity index 80% rename from Common/src/main/java/com/almostreliable/unified/mixin/package-info.java rename to Common/src/main/java/com/almostreliable/unified/core/package-info.java index b173bf2..4c7745c 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/package-info.java +++ b/Common/src/main/java/com/almostreliable/unified/core/package-info.java @@ -1,5 +1,5 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package com.almostreliable.unified.mixin; +package com.almostreliable.unified.core; import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/loot/CompositeEntryBaseMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/loot/CompositeEntryBaseMixin.java new file mode 100644 index 0000000..e923af7 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/loot/CompositeEntryBaseMixin.java @@ -0,0 +1,29 @@ +package com.almostreliable.unified.mixin.loot; + +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.unification.loot.LootUnificationHandler; +import net.minecraft.world.level.storage.loot.entries.CompositeEntryBase; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; + +@Mixin(CompositeEntryBase.class) +public class CompositeEntryBaseMixin implements LootUnificationHandler { + @Shadow @Final protected List children; + + @Override + public boolean almostunified$unify(UnificationLookup lookup) { + boolean unified = false; + + for (LootPoolEntryContainer child : children) { + if (child instanceof LootUnificationHandler handler) { + unified |= handler.almostunified$unify(lookup); + } + } + + return unified; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootItemMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootItemMixin.java new file mode 100644 index 0000000..378e1c8 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootItemMixin.java @@ -0,0 +1,27 @@ +package com.almostreliable.unified.mixin.loot; + +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.unification.loot.LootUnificationHandler; +import net.minecraft.core.Holder; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(LootItem.class) +public class LootItemMixin implements LootUnificationHandler { + @Shadow @Final @Mutable private Holder item; + + @Override + public boolean almostunified$unify(UnificationLookup lookup) { + var replacement = lookup.getVariantItemTarget(item); + if (replacement == null || item.value().equals(replacement.value())) { + return false; + } + + this.item = replacement.asHolderOrThrow(); + return true; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootPoolMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootPoolMixin.java new file mode 100644 index 0000000..a2b7bbc --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootPoolMixin.java @@ -0,0 +1,30 @@ +package com.almostreliable.unified.mixin.loot; + +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.unification.loot.LootUnificationHandler; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; + +@Mixin(LootPool.class) +public class LootPoolMixin implements LootUnificationHandler { + + @Shadow @Final private List entries; + + @Override + public boolean almostunified$unify(UnificationLookup lookup) { + boolean unified = false; + + for (LootPoolEntryContainer entry : this.entries) { + if (entry instanceof LootUnificationHandler handler) { + unified |= handler.almostunified$unify(lookup); + } + } + + return unified; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootTableMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootTableMixin.java new file mode 100644 index 0000000..f167fcc --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/loot/LootTableMixin.java @@ -0,0 +1,26 @@ +package com.almostreliable.unified.mixin.loot; + +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.unification.loot.LootUnificationHandler; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; + +@Mixin(LootTable.class) +public class LootTableMixin implements LootUnificationHandler { + @Shadow @Final private List pools; + + @Override + public boolean almostunified$unify(UnificationLookup lookup) { + boolean unified = false; + for (LootPool pool : pools) { + unified |= LootUnificationHandler.cast(pool).almostunified$unify(lookup); + } + + return unified; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/loot/package-info.java b/Common/src/main/java/com/almostreliable/unified/mixin/loot/package-info.java new file mode 100644 index 0000000..8ccc9ae --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/loot/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.mixin.loot; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/runtime/ClientPacketListenerMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/runtime/ClientPacketListenerMixin.java index dbb9aef..5d3908e 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/runtime/ClientPacketListenerMixin.java +++ b/Common/src/main/java/com/almostreliable/unified/mixin/runtime/ClientPacketListenerMixin.java @@ -1,8 +1,8 @@ package com.almostreliable.unified.mixin.runtime; -import com.almostreliable.unified.ClientTagUpdateEvent; +import com.almostreliable.unified.utils.ClientTagUpdateEvent; import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket; +import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/runtime/RecipeManagerMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/runtime/RecipeManagerMixin.java index 50c9138..9d95a0b 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/runtime/RecipeManagerMixin.java +++ b/Common/src/main/java/com/almostreliable/unified/mixin/runtime/RecipeManagerMixin.java @@ -1,12 +1,15 @@ package com.almostreliable.unified.mixin.runtime; -import com.almostreliable.unified.AlmostUnified; +import com.almostreliable.unified.AlmostUnifiedCommon; import com.google.gson.JsonElement; +import net.minecraft.core.HolderLookup; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.item.crafting.RecipeManager; +import org.spongepowered.asm.mixin.Final; 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; @@ -17,12 +20,14 @@ import java.util.Map; @Mixin(value = RecipeManager.class, priority = 1_099) public class RecipeManagerMixin { + @Shadow @Final private HolderLookup.Provider registries; + @Inject(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At("HEAD")) private void runTransformation(Map recipes, ResourceManager resourceManager, ProfilerFiller profiler, CallbackInfo ci) { try { - AlmostUnified.onRecipeManagerReload(recipes); + AlmostUnifiedCommon.onRecipeManagerReload(recipes, registries); } catch (Exception e) { - AlmostUnified.LOG.error(e.getMessage(), e); + AlmostUnifiedCommon.LOGGER.error(e.getMessage(), e); } } } diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/runtime/TagLoaderMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/runtime/TagLoaderMixin.java index 2a29334..032d712 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/runtime/TagLoaderMixin.java +++ b/Common/src/main/java/com/almostreliable/unified/mixin/runtime/TagLoaderMixin.java @@ -1,7 +1,7 @@ package com.almostreliable.unified.mixin.runtime; -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.utils.TagReloadHandler; +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.core.TagReloadHandler; import com.almostreliable.unified.utils.Utils; import net.minecraft.core.Holder; import net.minecraft.resources.ResourceLocation; @@ -26,22 +26,22 @@ public class TagLoaderMixin { @Inject(method = "build(Ljava/util/Map;)Ljava/util/Map;", at = @At("RETURN")) private void onCreateLoadResult(Map> map, CallbackInfoReturnable>> cir) { - if (directory.equals("tags/items")) { + if (directory.equals("tags/item")) { try { Map>> tags = Utils.cast(cir.getReturnValue()); TagReloadHandler.initItemTags(tags); TagReloadHandler.run(); } catch (Exception e) { - AlmostUnified.LOG.error(e.getMessage(), e); + AlmostUnifiedCommon.LOGGER.error(e.getMessage(), e); } } - if (directory.equals("tags/blocks")) { + if (directory.equals("tags/block")) { try { Map>> tags = Utils.cast(cir.getReturnValue()); TagReloadHandler.initBlockTags(tags); TagReloadHandler.run(); } catch (Exception e) { - AlmostUnified.LOG.error(e.getMessage(), e); + AlmostUnifiedCommon.LOGGER.error(e.getMessage(), e); } } } diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/unifier/ArmorItemMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/unification/ArmorItemMixin.java similarity index 51% rename from Common/src/main/java/com/almostreliable/unified/mixin/unifier/ArmorItemMixin.java rename to Common/src/main/java/com/almostreliable/unified/mixin/unification/ArmorItemMixin.java index 7873475..7bde4b7 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/unifier/ArmorItemMixin.java +++ b/Common/src/main/java/com/almostreliable/unified/mixin/unification/ArmorItemMixin.java @@ -1,9 +1,12 @@ -package com.almostreliable.unified.mixin.unifier; +package com.almostreliable.unified.mixin.unification; -import com.almostreliable.unified.AlmostUnified; +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.AlmostUnifiedRuntime; +import net.minecraft.core.Holder; import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.ArmorMaterial; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -14,14 +17,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ArmorItem.class) public class ArmorItemMixin { - @Shadow @Final protected ArmorMaterial material; + @Shadow @Final protected Holder material; @Inject(method = "isValidRepairItem", at = @At("HEAD"), cancellable = true) private void unified$repairUnification(ItemStack stack, ItemStack repairCandidate, CallbackInfoReturnable cir) { - AlmostUnified.getRuntime().getReplacementMap().ifPresent(replacementMap -> { - if (replacementMap.isItemInUnifiedIngredient(material.getRepairIngredient(), repairCandidate)) { - cir.setReturnValue(true); - } - }); + AlmostUnifiedRuntime runtime = AlmostUnified.INSTANCE.getRuntime(); + if (runtime == null) return; + + Ingredient repairIngredient = material.value().repairIngredient().get(); + if (runtime.getUnificationLookup().isUnifiedIngredientItem(repairIngredient, repairCandidate)) { + cir.setReturnValue(true); + } } } diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/unifier/TieredItemMixin.java b/Common/src/main/java/com/almostreliable/unified/mixin/unification/TieredItemMixin.java similarity index 57% rename from Common/src/main/java/com/almostreliable/unified/mixin/unifier/TieredItemMixin.java rename to Common/src/main/java/com/almostreliable/unified/mixin/unification/TieredItemMixin.java index 60dd436..946b594 100644 --- a/Common/src/main/java/com/almostreliable/unified/mixin/unifier/TieredItemMixin.java +++ b/Common/src/main/java/com/almostreliable/unified/mixin/unification/TieredItemMixin.java @@ -1,9 +1,11 @@ -package com.almostreliable.unified.mixin.unifier; +package com.almostreliable.unified.mixin.unification; -import com.almostreliable.unified.AlmostUnified; +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.AlmostUnifiedRuntime; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Tier; import net.minecraft.world.item.TieredItem; +import net.minecraft.world.item.crafting.Ingredient; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -18,10 +20,12 @@ public class TieredItemMixin { @Inject(method = "isValidRepairItem", at = @At("HEAD"), cancellable = true) private void unified$repairUnification(ItemStack stack, ItemStack repairCandidate, CallbackInfoReturnable cir) { - AlmostUnified.getRuntime().getReplacementMap().ifPresent(replacementMap -> { - if (replacementMap.isItemInUnifiedIngredient(tier.getRepairIngredient(), repairCandidate)) { - cir.setReturnValue(true); - } - }); + AlmostUnifiedRuntime runtime = AlmostUnified.INSTANCE.getRuntime(); + if (runtime == null) return; + + Ingredient repairIngredient = tier.getRepairIngredient(); + if (runtime.getUnificationLookup().isUnifiedIngredientItem(repairIngredient, repairCandidate)) { + cir.setReturnValue(true); + } } } diff --git a/Common/src/main/java/com/almostreliable/unified/mixin/unification/package-info.java b/Common/src/main/java/com/almostreliable/unified/mixin/unification/package-info.java new file mode 100644 index 0000000..f253a8c --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/mixin/unification/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.mixin.unification; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java deleted file mode 100644 index cee07eb..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeContextImpl.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.almostreliable.unified.recipe; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.utils.JsonUtils; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.UnifyTag; -import com.almostreliable.unified.utils.Utils; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import javax.annotation.Nullable; -import java.util.function.Predicate; - -@SuppressWarnings("SameParameterValue") -public class RecipeContextImpl implements RecipeContext { - - private final ReplacementMap replacementMap; - private final JsonObject originalRecipe; - - public RecipeContextImpl(JsonObject json, ReplacementMap replacementMap) { - this.originalRecipe = json; - this.replacementMap = replacementMap; - } - - @Nullable - @Override - public ResourceLocation getReplacementForItem(@Nullable ResourceLocation item) { - if (item == null) { - return null; - } - - return replacementMap.getReplacementForItem(item); - } - - @Nullable - @Override - public ResourceLocation getPreferredItemForTag(@Nullable UnifyTag tag, Predicate filter) { - if (tag == null) { - return null; - } - - return replacementMap.getPreferredItemForTag(tag, filter); - } - - @Nullable - @Override - public UnifyTag getPreferredTagForItem(@Nullable ResourceLocation item) { - if (item == null) { - return null; - } - - return replacementMap.getPreferredTagForItem(item); - } - - @Nullable - @Override - public JsonElement createIngredientReplacement(@Nullable JsonElement element) { - if (element == null) { - return null; - } - - JsonElement copy = element.deepCopy(); - tryCreateIngredientReplacement(copy); - return element.equals(copy) ? null : copy; - } - - private void tryCreateIngredientReplacement(@Nullable JsonElement element) { - if (element instanceof JsonArray array) { - for (JsonElement e : array) { - tryCreateIngredientReplacement(e); - } - } - - if (element instanceof JsonObject object) { - tryCreateIngredientReplacement(object.get(RecipeConstants.VALUE)); - tryCreateIngredientReplacement(object.get(RecipeConstants.BASE)); - tryCreateIngredientReplacement(object.get(RecipeConstants.INGREDIENT)); - - if (object.get(RecipeConstants.TAG) instanceof JsonPrimitive primitive) { - UnifyTag tag = Utils.toItemTag(primitive.getAsString()); - var ownerTag = replacementMap.getTagOwnerships().getOwnerByTag(tag); - if (ownerTag != null) { - object.addProperty(RecipeConstants.TAG, ownerTag.location().toString()); - } - } - - if (object.get(RecipeConstants.ITEM) instanceof JsonPrimitive primitive) { - ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); - UnifyTag tag = getPreferredTagForItem(item); - if (tag != null) { - object.remove(RecipeConstants.ITEM); - object.addProperty(RecipeConstants.TAG, tag.location().toString()); - } - } - } - } - - @Override - @Nullable - public JsonElement createResultReplacement(@Nullable JsonElement element) { - return createResultReplacement(element, true, RecipeConstants.ITEM); - } - - @Override - @Nullable - public JsonElement createResultReplacement(@Nullable JsonElement element, boolean tagLookup, String... lookupKeys) { - if (element == null) { - return null; - } - - JsonElement copy = element.deepCopy(); - JsonElement result = tryCreateResultReplacement(copy, tagLookup, lookupKeys); - return element.equals(result) ? null : result; - } - - @Nullable - private JsonElement tryCreateResultReplacement(JsonElement element, boolean tagLookup, String... lookupKeys) { - if (element instanceof JsonPrimitive primitive) { - ResourceLocation item = ResourceLocation.tryParse(primitive.getAsString()); - ResourceLocation replacement = getReplacementForItem(item); - if (replacement != null) { - return new JsonPrimitive(replacement.toString()); - } - return null; - } - - if (element instanceof JsonArray array && - JsonUtils.replaceOn(array, j -> tryCreateResultReplacement(j, tagLookup, lookupKeys))) { - return element; - } - - if (element instanceof JsonObject object) { - for (String key : lookupKeys) { - if (JsonUtils.replaceOn(object, key, j -> tryCreateResultReplacement(j, tagLookup, lookupKeys))) { - return element; - } - } - - // when tags are used as outputs, replace them with the preferred item - if (tagLookup && object.get(RecipeConstants.TAG) instanceof JsonPrimitive primitive) { - ResourceLocation item = getPreferredItemForTag(Utils.toItemTag(primitive.getAsString()), $ -> true); - if (item != null) { - object.remove(RecipeConstants.TAG); - object.addProperty(RecipeConstants.ITEM, item.toString()); - } - return element; - } - } - - return null; - } - - @Override - public ResourceLocation getType() { - String type = originalRecipe.get("type").getAsString(); - return new ResourceLocation(type); - } - - @Override - public boolean hasProperty(String property) { - return originalRecipe.has(property); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeDumper.java b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeDumper.java deleted file mode 100644 index 6c9f431..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeDumper.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.almostreliable.unified.recipe; - -import com.almostreliable.unified.AlmostUnifiedPlatform; -import com.almostreliable.unified.utils.FileUtils; -import net.minecraft.resources.ResourceLocation; -import org.apache.commons.lang3.StringUtils; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Collection; -import java.util.Comparator; -import java.util.Date; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class RecipeDumper { - private final RecipeTransformer.Result result; - private final long startTime; - private final long endTime; - private final DateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); - - public RecipeDumper(RecipeTransformer.Result result, long startTime, long endTime) { - this.result = result; - this.startTime = startTime; - this.endTime = endTime; - } - - public void dump(boolean dumpOverview, boolean dumpUnify, boolean dumpDuplicate) { - String last = "# Last execution: " + format.format(new Date(startTime)); - - if (dumpOverview) { - FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "overview_dump.txt", sb -> { - sb.append(last).append("\n"); - dumpOverview(sb); - }); - } - - if (dumpUnify) { - FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "unify_dump.txt", sb -> { - sb.append(last).append("\n"); - dumpUnifyRecipes(sb); - }); - } - - if (dumpDuplicate) { - FileUtils.write(AlmostUnifiedPlatform.INSTANCE.getLogPath(), "duplicates_dump.txt", sb -> { - sb.append(last).append("\n"); - dumpDuplicates(sb); - }); - } - - } - - private void dumpDuplicates(StringBuilder stringBuilder) { - getSortedUnifiedRecipeTypes().forEach(type -> { - Collection duplicates = result - .getDuplicates(type) - .stream() - .sorted(Comparator.comparing(l -> l.getMaster().getId().toString())) - .toList(); - if (duplicates.isEmpty()) return; - - stringBuilder.append(duplicates - .stream() - .map(this::createDuplicatesDump) - .collect(Collectors.joining("", type + " {\n", "}\n\n"))); - }); - } - - private String createDuplicatesDump(RecipeLink.DuplicateLink link) { - return link - .getRecipes() - .stream() - .sorted(Comparator.comparing(r -> r.getId().toString())) - .map(r -> "\t\t- " + r.getId() + "\n") - .collect(Collectors.joining("", String.format("\t%s\n", link.getMaster().getId().toString()), "\n")); - } - - private void dumpOverview(StringBuilder stringBuilder) { - stringBuilder - .append("# Overview:\n") - .append("\t- Unified: ") - .append(result.getUnifiedRecipeCount()) - .append("\n") - .append("\t- Duplicates: ") - .append(result.getDuplicatesCount()) - .append(" (Individual: ") - .append(result.getDuplicateRecipesCount()) - .append(")\n") - .append("\t- Total Recipes: ") - .append(result.getRecipeCount()) - .append("\n") - .append("\t- Elapsed Time: ") - .append(getTotalTime()) - .append("ms") - .append("\n\n") - .append("# Summary: \n"); - - stringBuilder - .append(rf("Recipe type", 45)) - .append(" | ") - .append(lf("Unifies", 10)) - .append(" | ") - .append(lf("Duplicates", 10)) - .append(" | ") - .append(lf("All", 5)) - .append("\n") - .append(StringUtils.repeat("-", 45 + 10 + 10 + 5 + 9)) - .append("\n"); - - getSortedUnifiedRecipeTypes().forEach(type -> { - int unifiedSize = result.getUnifiedRecipes(type).size(); - int allSize = result.getRecipes(type).size(); - int duplicatesSize = result.getDuplicates(type).size(); - int individualDuplicatesSize = result - .getDuplicates(type) - .stream() - .mapToInt(l -> l.getRecipes().size()) - .sum(); - - String dStr = String.format("%s (%s)", lf(duplicatesSize, 3), lf(individualDuplicatesSize, 3)); - stringBuilder - .append(rf(type, 45)) - .append(" | ") - .append(lf(unifiedSize, 10)) - .append(" | ") - .append(lf(duplicatesSize == 0 ? " " : dStr, 10)) - .append(" | ") - .append(lf(allSize, 5)) - .append("\n"); - }); - } - - private void dumpUnifyRecipes(StringBuilder stringBuilder) { - getSortedUnifiedRecipeTypes().forEach(type -> { - stringBuilder.append(type.toString()).append(" {\n"); - - getSortedUnifiedRecipes(type).forEach(recipe -> { - stringBuilder - .append("\t- ") - .append(recipe.getId()) - .append("\n") - .append("\t\t Original: ") - .append(recipe.getOriginal().toString()) - .append("\n") - .append("\t\t Transformed: ") - .append(recipe.getUnified() == null ? "NOT UNIFIED" : recipe.getUnified().toString()) - .append("\n\n"); - }); - - stringBuilder.append("}\n\n"); - }); - } - - private String rf(Object v, int size) { - return StringUtils.rightPad(v.toString(), size); - } - - private String lf(Object v, int size) { - return StringUtils.leftPad(v.toString(), size); - } - - private Stream getSortedUnifiedRecipeTypes() { - return result.getUnifiedRecipeTypes().stream().sorted(Comparator.comparing(ResourceLocation::toString)); - } - - private Stream getSortedUnifiedRecipes(ResourceLocation type) { - return result.getUnifiedRecipes(type).stream().sorted(Comparator.comparing(r -> r.getId().toString())); - } - - private long getTotalTime() { - return endTime - startTime; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeUnifierBuilderImpl.java b/Common/src/main/java/com/almostreliable/unified/recipe/RecipeUnifierBuilderImpl.java deleted file mode 100644 index 17dd517..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeUnifierBuilderImpl.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.almostreliable.unified.recipe; - -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiFunction; - -public class RecipeUnifierBuilderImpl implements RecipeUnifierBuilder { - private final Map> consumers = new HashMap<>(); - - @Override - public void forEachObject(String property, BiFunction consumer) { - BiFunction arrayConsumer = (array, ctx) -> { - for (int i = 0; i < array.size(); i++) { - JsonElement element = array.get(i); - if (element instanceof JsonObject obj) { - JsonObject result = consumer.apply(obj, ctx); - if (result != null) { - array.set(i, result); - } - } - } - return array; - }; - - put(property, JsonArray.class, arrayConsumer); - } - - @Override - public void put(String property, BiFunction consumer) { - consumers.put(property, new Entry<>(JsonElement.class, consumer)); - } - - @Override - public void put(String property, Class type, BiFunction consumer) { - consumers.put(property, new Entry<>(type, consumer)); - } - - @Nullable - public JsonObject unify(JsonObject json, RecipeContext context) { - JsonObject changedValues = new JsonObject(); - - for (var e : json.entrySet()) { - Entry consumer = consumers.get(e.getKey()); - if (consumer != null) { - JsonElement currentElement = e.getValue(); - JsonElement transformedElement = consumer.apply(currentElement.deepCopy(), context); - if (transformedElement != null && !transformedElement.equals(currentElement)) { - changedValues.add(e.getKey(), transformedElement); - } - } - } - - if (changedValues.size() == 0) { - return null; - } - - // helps to preserve the order of the elements - JsonObject result = new JsonObject(); - for (var entry : json.entrySet()) { - JsonElement changedValue = changedValues.get(entry.getKey()); - if (changedValue != null) { - result.add(entry.getKey(), changedValue); - } else { - result.add(entry.getKey(), entry.getValue()); - } - } - - return result; - } - - public Collection getKeys() { - return consumers.keySet(); - } - - private record Entry(Class expectedType, - BiFunction func) { - @Nullable - T apply(JsonElement json, RecipeContext context) { - if (expectedType.isInstance(json)) { - return func.apply(expectedType.cast(json), context); - } - - return null; - } - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/GenericRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/recipe/unifier/GenericRecipeUnifier.java deleted file mode 100644 index a1f3cff..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/GenericRecipeUnifier.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.almostreliable.unified.recipe.unifier; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -import java.util.Set; - -public class GenericRecipeUnifier implements RecipeUnifier { - public static final GenericRecipeUnifier INSTANCE = new GenericRecipeUnifier(); - private static final Set INPUT_KEYS = Set.of( - RecipeConstants.INPUT, - RecipeConstants.INPUTS, - RecipeConstants.INGREDIENT, - RecipeConstants.INGREDIENTS, - RecipeConstants.INPUT_ITEMS - ); - private static final Set OUTPUT_KEYS = Set.of( - RecipeConstants.OUTPUT, - RecipeConstants.OUTPUTS, - RecipeConstants.RESULT, - RecipeConstants.RESULTS, - RecipeConstants.OUTPUT_ITEMS - ); - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - for (String inputKey : INPUT_KEYS) { - builder.put(inputKey, (json, ctx) -> ctx.createIngredientReplacement(json)); - } - - for (String outputKey : OUTPUT_KEYS) { - builder.put(outputKey, (json, ctx) -> ctx.createResultReplacement(json)); - } - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/RecipeHandlerFactory.java b/Common/src/main/java/com/almostreliable/unified/recipe/unifier/RecipeHandlerFactory.java deleted file mode 100644 index c2470b0..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/RecipeHandlerFactory.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.almostreliable.unified.recipe.unifier; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import net.minecraft.resources.ResourceLocation; - -import java.util.HashMap; -import java.util.Map; - -public class RecipeHandlerFactory { - - private static final ResourceLocation SMITHING_TYPE = new ResourceLocation("minecraft:smithing"); - - private final Map transformersByType = new HashMap<>(); - private final Map transformersByModId = new HashMap<>(); - - public void fillUnifier(RecipeUnifierBuilder builder, RecipeContext context) { - GenericRecipeUnifier.INSTANCE.collectUnifier(builder); - - if (context.hasProperty(ShapedRecipeKeyUnifier.PATTERN_PROPERTY) && - context.hasProperty(ShapedRecipeKeyUnifier.KEY_PROPERTY)) { - ShapedRecipeKeyUnifier.INSTANCE.collectUnifier(builder); - } - - if (context.hasProperty(SmithingRecipeUnifier.ADDITION_PROPERTY) && - context.hasProperty(SmithingRecipeUnifier.BASE_PROPERTY) && - context.hasProperty(RecipeConstants.RESULT)) { - SmithingRecipeUnifier.INSTANCE.collectUnifier(builder); - } - - ResourceLocation type = context.getType(); - RecipeUnifier byMod = transformersByModId.get(type.getNamespace()); - if (byMod != null) { - byMod.collectUnifier(builder); - } - - RecipeUnifier byType = transformersByType.get(type); - if (byType != null) { - byType.collectUnifier(builder); - } - } - - public void registerForType(ResourceLocation type, RecipeUnifier transformer) { - transformersByType.put(type, transformer); - } - - public void registerForMod(String mod, RecipeUnifier transformer) { - transformersByModId.put(mod, transformer); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/ShapedRecipeKeyUnifier.java b/Common/src/main/java/com/almostreliable/unified/recipe/unifier/ShapedRecipeKeyUnifier.java deleted file mode 100644 index 29ecbe8..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/ShapedRecipeKeyUnifier.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.almostreliable.unified.recipe.unifier; - -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -public class ShapedRecipeKeyUnifier implements RecipeUnifier { - public static final RecipeUnifier INSTANCE = new ShapedRecipeKeyUnifier(); - public static final String PATTERN_PROPERTY = "pattern"; - public static final String KEY_PROPERTY = "key"; - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put(KEY_PROPERTY, JsonObject.class, (json, context) -> { - for (var entry : json.entrySet()) { - JsonElement result = context.createIngredientReplacement(entry.getValue()); - if (result != null) { - entry.setValue(result); - } - } - return json; - }); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/SmithingRecipeUnifier.java b/Common/src/main/java/com/almostreliable/unified/recipe/unifier/SmithingRecipeUnifier.java deleted file mode 100644 index 304d29f..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/SmithingRecipeUnifier.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.almostreliable.unified.recipe.unifier; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -public class SmithingRecipeUnifier implements RecipeUnifier { - - public static final RecipeUnifier INSTANCE = new SmithingRecipeUnifier(); - - public static final String ADDITION_PROPERTY = "addition"; - public static final String BASE_PROPERTY = "base"; - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put(ADDITION_PROPERTY, (json, ctx) -> ctx.createIngredientReplacement(json)); - builder.put(BASE_PROPERTY, (json, ctx) -> ctx.createIngredientReplacement(json)); - builder.put(RecipeConstants.RESULT, (json, ctx) -> ctx.createResultReplacement(json)); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/package-info.java b/Common/src/main/java/com/almostreliable/unified/recipe/unifier/package-info.java deleted file mode 100644 index 28da051..0000000 --- a/Common/src/main/java/com/almostreliable/unified/recipe/unifier/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package com.almostreliable.unified.recipe.unifier; - -import net.minecraft.MethodsReturnNonnullByDefault; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/unification/ModPrioritiesImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/ModPrioritiesImpl.java new file mode 100644 index 0000000..975b7ec --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/ModPrioritiesImpl.java @@ -0,0 +1,78 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.unification.ModPriorities; +import com.almostreliable.unified.api.unification.UnificationEntry; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public final class ModPrioritiesImpl implements ModPriorities { + + private final List modPriorities; + private final Map, String> priorityOverrides; + + public ModPrioritiesImpl(List modPriorities, Map, String> priorityOverrides) { + this.modPriorities = modPriorities; + this.priorityOverrides = priorityOverrides; + } + + @Nullable + @Override + public String getPriorityOverride(TagKey tag) { + return priorityOverrides.get(tag); + } + + @Nullable + @Override + public UnificationEntry findPriorityOverrideItem(TagKey tag, List> items) { + String priorityOverride = getPriorityOverride(tag); + if (priorityOverride == null) return null; + + var entry = findItemByNamespace(items, priorityOverride); + if (entry != null) return entry; + + AlmostUnifiedCommon.LOGGER.warn( + "Priority override mod '{}' for tag '{}' does not contain a valid item. Falling back to default priority.", + priorityOverride, + tag.location() + ); + return null; + } + + @Nullable + @Override + public UnificationEntry findTargetItem(TagKey tag, List> items) { + var overrideEntry = findPriorityOverrideItem(tag, items); + if (overrideEntry != null) { + return overrideEntry; + } + + for (String modPriority : modPriorities) { + var entry = findItemByNamespace(items, modPriority); + if (entry != null) return entry; + } + + return null; + } + + @Override + public Iterator iterator() { + return modPriorities.iterator(); + } + + @Nullable + private static UnificationEntry findItemByNamespace(List> items, String namespace) { + for (var item : items) { + if (item.id().getNamespace().equals(namespace)) { + return item; + } + } + + return null; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/StoneVariantsImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/StoneVariantsImpl.java new file mode 100644 index 0000000..6cf281c --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/StoneVariantsImpl.java @@ -0,0 +1,148 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.unification.StoneVariants; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.*; +import java.util.regex.Pattern; + +public final class StoneVariantsImpl implements StoneVariants { + + private static final Pattern ORE_TAG_PATTERN = Pattern.compile("(c:ores/.+|(minecraft|c):.+_ores)"); + + private final Map, Boolean> isOreTagCache = new HashMap<>(); + + private final List stoneVariants; + private final Map itemToStoneVariant; + + private StoneVariantsImpl(Collection stoneVariants, Map itemToStoneVariant) { + this.stoneVariants = sortStoneVariants(stoneVariants); + this.itemToStoneVariant = itemToStoneVariant; + } + + public static StoneVariants create(Collection stoneVariants, VanillaTagWrapper itemTags, VanillaTagWrapper blockTags) { + Set> stoneVariantItemTags = new HashSet<>(); + Set> stoneVariantBlockTags = new HashSet<>(); + + for (String stoneVariant : stoneVariants) { + ResourceLocation id = ResourceLocation.fromNamespaceAndPath("c", "ores_in_ground/" + stoneVariant); + stoneVariantItemTags.add(TagKey.create(Registries.ITEM, id)); + stoneVariantBlockTags.add(TagKey.create(Registries.BLOCK, id)); + } + + var itemToStoneVariantTag = mapEntriesToStoneVariantTags(stoneVariantItemTags, itemTags); + var blockToStoneVariantTag = mapEntriesToStoneVariantTags(stoneVariantBlockTags, blockTags); + + Map itemToStoneVariant = new HashMap<>(); + itemToStoneVariantTag.forEach((item, tag) -> { + String itemStoneVariant = getVariantFromStoneVariantTag(tag); + + var blockTagFromItem = blockToStoneVariantTag.get(item); + if (blockTagFromItem != null) { + String blockStoneVariant = getVariantFromStoneVariantTag(blockTagFromItem); + if (blockStoneVariant.length() > itemStoneVariant.length()) { + itemToStoneVariant.put(item, blockStoneVariant); + return; + } + } + + itemToStoneVariant.put(item, itemStoneVariant); + }); + + return new StoneVariantsImpl(stoneVariants, itemToStoneVariant); + } + + /** + * Maps all entries of a stone variant tag to its respective stone variant tag. + * + * @param stoneVariantTags the stone variant tags + * @param tags the vanilla tag wrapper to get the tag entries from + * @param the tag type + * @return the entry to stone variant tag mapping + */ + private static Map> mapEntriesToStoneVariantTags(Set> stoneVariantTags, VanillaTagWrapper tags) { + Map> idToStoneVariantTag = new HashMap<>(); + + for (var stoneVariantTag : stoneVariantTags) { + for (var holder : tags.get(stoneVariantTag)) { + ResourceLocation id = holder + .unwrapKey() + .orElseThrow(() -> new IllegalStateException("Tag is not bound for holder " + holder)) + .location(); + + var oldTag = idToStoneVariantTag.put(id, stoneVariantTag); + if (oldTag != null) { + AlmostUnifiedCommon.LOGGER.error( + "{} is bound to multiple stone variant tags: {} and {}", + id, + oldTag, + stoneVariantTag + ); + } + } + } + + return idToStoneVariantTag; + } + + /** + * Returns the variant of a stone variant tag. + *

+ * Example: {@code c:ores_in_ground/deepslate} -> {@code deepslate} + * + * @param tag the stone variant tag + * @return the stone variant + */ + private static String getVariantFromStoneVariantTag(TagKey tag) { + String tagString = tag.location().toString(); + int i = tagString.lastIndexOf('/'); + String stoneVariant = tagString.substring(i + 1); + stoneVariant = stoneVariant.equals("stone") ? "" : stoneVariant; + return stoneVariant; + } + + @Override + public String getStoneVariant(ResourceLocation item) { + return itemToStoneVariant.computeIfAbsent(item, this::computeStoneVariant); + } + + @Override + public boolean isOreTag(TagKey tag) { + return isOreTagCache.computeIfAbsent(tag, t -> ORE_TAG_PATTERN.matcher(t.location().toString()).matches()); + } + + /** + * Returns a list of all stone variants sorted from longest to shortest. + *

+ * This is required to ensure that the longest variant is returned first and no sub-matches happen.
+ * Example: "nether" and "blue_nether" would both match "nether" if the list is not sorted. + * + * @param stoneVariants the stone variants to sort + * @return the sorted stone variants + */ + private static List sortStoneVariants(Collection stoneVariants) { + return stoneVariants.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList(); + } + + /** + * Implementation logic for caching in {@link #getStoneVariant(ResourceLocation)}. + * + * @param item the item to get the stone variant of + * @return the stone variant of the item + */ + private String computeStoneVariant(ResourceLocation item) { + for (String stoneVariant : stoneVariants) { + if (item.getPath().contains(stoneVariant + "_") || item.getPath().endsWith("_" + stoneVariant)) { + return stoneVariant.equals("stone") ? "" : stoneVariant; + } + } + + return ""; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/TagInheritance.java b/Common/src/main/java/com/almostreliable/unified/unification/TagInheritance.java new file mode 100644 index 0000000..1cffa57 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/TagInheritance.java @@ -0,0 +1,246 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.unification.UnificationEntry; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.utils.Utils; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.regex.Pattern; + +public class TagInheritance { + + private final Options itemOptions; + private final Options blockOptions; + + public TagInheritance(Mode itemMode, Map, Set> itemInheritance, Mode blockMode, Map, Set> blockInheritance) { + itemOptions = new Options<>(itemMode, itemInheritance); + blockOptions = new Options<>(blockMode, blockInheritance); + } + + public boolean apply(VanillaTagWrapper itemTags, VanillaTagWrapper blockTags, List unificationLookups) { + Multimap, ResourceLocation> changedItemTags = HashMultimap.create(); + Multimap, ResourceLocation> changedBlockTags = HashMultimap.create(); + + var relations = resolveRelations(unificationLookups); + if (relations.isEmpty()) return false; + + for (var relation : relations) { + var targetItem = relation.targetItem; + var targetItemHolder = targetItem.asHolderOrThrow(); + var targetBlockHolder = findTargetBlockHolder(blockTags, targetItem); + + var targetItemTags = itemTags + .getTags(targetItem) + .stream() + .map(rl -> TagKey.create(Registries.ITEM, rl)) + .collect(ImmutableSet.toImmutableSet()); + + for (var item : relation.items) { + var appliedItemTags = applyItemTags(itemTags, targetItemHolder, targetItemTags, item); + changedItemTags.putAll(targetItem, appliedItemTags); + + if (targetBlockHolder != null) { + var appliedBlockTags = applyBlockTags(blockTags, targetBlockHolder, targetItemTags, item); + changedBlockTags.putAll(targetItem, appliedBlockTags); + } + } + } + + if (!changedBlockTags.isEmpty()) { + changedBlockTags.asMap().forEach((target, tags) -> { + AlmostUnifiedCommon.LOGGER.info("[TagInheritance] Added '{}' to block tags {}", target.id(), tags); + }); + } + + if (!changedItemTags.isEmpty()) { + changedItemTags.asMap().forEach((target, tags) -> { + AlmostUnifiedCommon.LOGGER.info("[TagInheritance] Added '{}' to item tags {}", target.id(), tags); + }); + return true; + } + + return false; + } + + @Nullable + private Holder findTargetBlockHolder(VanillaTagWrapper tagMap, UnificationEntry targetItem) { + var blockTags = tagMap.getTags(targetItem.id()); + if (blockTags.isEmpty()) return null; + + return BuiltInRegistries.BLOCK.getHolderOrThrow(ResourceKey.create(Registries.BLOCK, targetItem.id())); + } + + private Set applyItemTags(VanillaTagWrapper vanillaTags, Holder targetItem, Set> targetItemTags, UnificationEntry item) { + var itemTags = vanillaTags.getTags(item); + Set changed = new HashSet<>(); + + for (var itemTag : itemTags) { + var tag = TagKey.create(Registries.ITEM, itemTag); + if (itemOptions.shouldInherit(tag, targetItemTags) && addToVanilla(targetItem, tag, vanillaTags)) { + changed.add(itemTag); + } + } + + return changed; + } + + private Set applyBlockTags(VanillaTagWrapper blockTagMap, Holder targetBlock, Set> targetItemTags, UnificationEntry item) { + var blockTags = blockTagMap.getTags(item.id()); + Set changed = new HashSet<>(); + + for (var blockTag : blockTags) { + var tag = TagKey.create(Registries.BLOCK, blockTag); + if (blockOptions.shouldInherit(tag, targetItemTags) && addToVanilla(targetBlock, tag, blockTagMap)) { + changed.add(blockTag); + } + } + + return changed; + } + + /** + * Add given holder to the given tag. + * + * @param holder The holder + * @param tag The tag the holder should be added to + * @param vanillaTags The vanilla tag wrapper + * @return true if the holder was added, false if it was already present + */ + private static boolean addToVanilla(Holder holder, TagKey tag, VanillaTagWrapper vanillaTags) { + var tagHolders = vanillaTags.get(tag); + if (tagHolders.contains(holder)) return false; // already present, no need to add it again + + vanillaTags.add(tag.location(), holder); + return true; + } + + private Set resolveRelations(Collection unificationLookups) { + Set relations = new HashSet<>(); + + for (var unificationLookup : unificationLookups) { + for (TagKey unifyTag : unificationLookup.getTags()) { + if (itemOptions.skipForInheritance(unifyTag) && blockOptions.skipForInheritance(unifyTag)) { + continue; + } + + var itemsByTag = unificationLookup.getTagEntries(unifyTag); + + // avoid handling single entries and tags that only contain the same namespace for all items + if (Utils.allSameNamespace(itemsByTag)) continue; + + var target = unificationLookup.getTagTargetItem(unifyTag); + if (target == null) continue; + + var items = removeTargetItem(itemsByTag, target); + + if (items.isEmpty()) continue; + relations.add(new TagRelation(unifyTag, target, items)); + } + } + + return relations; + } + + /** + * Returns a set of all items that are not the target item and are valid by checking if they are registered. + * + * @param holders The set of all items that are in the tag + * @param target The target item + * @return A set of all items that are not the target item and are valid + */ + private Set> removeTargetItem(Collection> holders, UnificationEntry target) { + Set> result = new HashSet<>(holders.size()); + for (var holder : holders) { + if (!holder.equals(target)) { + result.add(holder); + } + } + + return result; + } + + private record TagRelation(TagKey tag, UnificationEntry targetItem, + Set> items) {} + + public enum Mode { + ALLOW, + DENY + } + + private record Options(Mode mode, Map, Set> inheritance) { + + /** + * Checks if given tag is used in the inheritance config. + *

+ * If mode is allowed, the tag should match any pattern in the config. If mode is deny, the tag should not match + * any pattern in the config. + * + * @param tag The tag to check + * @return True if the tag should be skipped + */ + public boolean skipForInheritance(TagKey tag) { + var tagStr = tag.location().toString(); + boolean modeResult = mode == Mode.ALLOW; + for (Set patterns : inheritance.values()) { + for (Pattern pattern : patterns) { + if (pattern.matcher(tagStr).matches()) { + return !modeResult; + } + } + } + + return modeResult; + } + + /** + * Checks if given inheritance tag would match any of the target item tags. + *

+ * E. g based on a simple config: + *

+         * {@code {
+         *     "minecraft:beacon_payment_items": [
+         *          "c:ores/silver"
+         *     ]
+         * }}
+         * 
+ * "minecraft:beacon_payment_items" would be the inheritance tag and "c:ores/silver" would be one of the target item tags. + * If mode is {@code DENY}, the check would be inverted. + * + * @param inheritanceTag The inheritance tag + * @param targetItemTags The target item tags + * @return True if we should allow the inheritance or false if we should deny the inheritance + */ + public boolean shouldInherit(TagKey inheritanceTag, Collection> targetItemTags) { + var patterns = inheritance.getOrDefault(inheritanceTag, Set.of()); + boolean result = checkPatterns(targetItemTags, patterns); + // noinspection SimplifiableConditionalExpression + return mode == Mode.ALLOW ? result : !result; + } + + private boolean checkPatterns(Collection> tags, Collection patterns) { + for (var pattern : patterns) { + for (var tag : tags) { + if (pattern.matcher(tag.location().toString()).matches()) { + return true; + } + } + } + + return false; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/TagSubstitutionsImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/TagSubstitutionsImpl.java new file mode 100644 index 0000000..5f24321 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/TagSubstitutionsImpl.java @@ -0,0 +1,140 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.unification.TagSubstitutions; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public final class TagSubstitutionsImpl implements TagSubstitutions { + + private final Map, TagKey> replacedToSubstitute; + private final Multimap, TagKey> substituteToReplaced; + + private TagSubstitutionsImpl(Map, TagKey> replacedToSubstitute, Multimap, TagKey> substituteToReplaced) { + this.replacedToSubstitute = replacedToSubstitute; + this.substituteToReplaced = substituteToReplaced; + } + + /** + * Creates a new tag substitutions instance that contains immutable maps of all tag substitution relationships. + *

+ * This method ensures that all substitute tags are unify tags and that all replaced tags are not unify tags.
+ * Since substitute tags have to be unify-tags, it is ensured that they are valid and exist in the game. Replaced + * tags still need to be validated. + * + * @param validTagFilter a filter that defines which tags are valid tags and exist in the game + * @param unifyTagFilter a filter that defines which tags are unify tags + * @param configuredSubstitutes the tag substitution relationships from the config + * @return the new tag substitutions instance + */ + public static TagSubstitutionsImpl create(Predicate> validTagFilter, Predicate> unifyTagFilter, Map> configuredSubstitutes) { + ImmutableMap.Builder, TagKey> refsToSubsBuilder = ImmutableMap.builder(); + ImmutableMultimap.Builder, TagKey> subsToRefsBuilder = ImmutableMultimap.builder(); + Set> invalidReplacedTags = new HashSet<>(); + Set> unifyReplacedTags = new HashSet<>(); + + configuredSubstitutes.forEach((rawSubstituteTag, rawReplacedTags) -> { + for (ResourceLocation rawReplacedTag : rawReplacedTags) { + var substituteTag = TagKey.create(Registries.ITEM, rawSubstituteTag); + var replacedTag = TagKey.create(Registries.ITEM, rawReplacedTag); + + if (!unifyTagFilter.test(substituteTag)) { + AlmostUnifiedCommon.LOGGER.warn( + "[TagSubstitutions] Substitute tag '#{}' is not configured as a unify tag! Config entry '#{} -> {}' will be ignored.", + substituteTag.location(), + substituteTag.location(), + rawReplacedTags.stream().map(t -> "#" + t).collect(Collectors.joining(", ")) + ); + return; // don't check other replaced tags if the substitute tag is invalid + } + + if (!validTagFilter.test(replacedTag)) { + invalidReplacedTags.add(replacedTag); + continue; // only skip the current invalid replaced tag + } + + if (unifyTagFilter.test(replacedTag)) { + unifyReplacedTags.add(replacedTag); + continue; // only skip the current invalid replaced tag + } + + refsToSubsBuilder.put(replacedTag, substituteTag); + subsToRefsBuilder.put(substituteTag, replacedTag); + } + + if (!invalidReplacedTags.isEmpty()) { + AlmostUnifiedCommon.LOGGER.warn( + "[TagSubstitutions] Substitute tag '#{}' contains invalid replaced tags! Affected tags: {}", + rawSubstituteTag, + invalidReplacedTags.stream().map(t -> "#" + t.location()).collect(Collectors.joining(", ")) + ); + } + + if (!unifyReplacedTags.isEmpty()) { + AlmostUnifiedCommon.LOGGER.warn( + "[TagSubstitutions] Substitute tag '#{}' contains replaced tags that are configured as unify tags! Affected tags: {}", + rawSubstituteTag, + unifyReplacedTags.stream().map(t -> "#" + t.location()).collect(Collectors.joining(", ")) + ); + } + }); + + return new TagSubstitutionsImpl(refsToSubsBuilder.build(), subsToRefsBuilder.build()); + } + + /** + * Applies tag substitutions to the provided item tags. + * + * @param itemTags the item tags to apply the substitutions to + */ + public void apply(VanillaTagWrapper itemTags) { + Multimap changedTags = HashMultimap.create(); + + substituteToReplaced.asMap().forEach((substituteTag, replacedTags) -> { + for (var replacedTag : replacedTags) { + var replacedTagHolders = itemTags.get(replacedTag.location()); + for (var replacedTagHolder : replacedTagHolders) { + itemTags.add(substituteTag.location(), replacedTagHolder); + replacedTagHolder + .unwrapKey() + .ifPresent(key -> changedTags.put(substituteTag.location(), key.location())); + } + } + }); + + changedTags.asMap().forEach((tag, entries) -> AlmostUnifiedCommon.LOGGER.info( + "[TagSubstitutions] Added items of replaced tags to substitute tag '#{}'. Added items: {}", + tag, + entries + )); + } + + + @Override + @Nullable + public TagKey getSubstituteTag(TagKey replacedTag) { + return replacedToSubstitute.get(replacedTag); + } + + @Override + public Collection> getReplacedTags(TagKey substituteTag) { + return Collections.unmodifiableCollection(substituteToReplaced.get(substituteTag)); + } + + @Override + public Set> getReplacedTags() { + return replacedToSubstitute.keySet(); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/UnificationEntryImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/UnificationEntryImpl.java new file mode 100644 index 0000000..6970408 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/UnificationEntryImpl.java @@ -0,0 +1,91 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.api.unification.UnificationEntry; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; + +import javax.annotation.Nullable; +import java.util.Objects; + +public class UnificationEntryImpl implements UnificationEntry { + + private final Registry registry; + private final ResourceKey key; + + @Nullable private T value; + @Nullable private TagKey tag; + + public UnificationEntryImpl(Registry registry, ResourceLocation key) { + this.registry = registry; + this.key = ResourceKey.create(registry.key(), key); + } + + public UnificationEntryImpl(Registry registry, T entry) { + this.key = registry + .getResourceKey(entry) + .orElseThrow(() -> new IllegalArgumentException("Entry " + entry + " does not belong to " + registry)); + this.registry = registry; + this.value = entry; + } + + @Override + public ResourceKey key() { + return key; + } + + @Override + public ResourceLocation id() { + return key.location(); + } + + @Override + public T value() { + if (value == null) { + value = registry + .getOptional(key) + .orElseThrow(() -> new IllegalStateException("entry " + key + " not found in " + registry)); + } + + return value; + } + + @Override + public TagKey tag() { + if (tag == null) { + throw new IllegalStateException("tag not bound to " + this); + } + + return tag; + } + + @Override + public Holder.Reference asHolderOrThrow() { + return registry.getHolderOrThrow(key); + } + + public void bindTag(TagKey tag) { + if (this.tag != null) { + throw new IllegalStateException("tag already bound to " + this.tag); + } + + this.tag = tag; + } + + @Override + public int hashCode() { + return Objects.hash(key); + } + + @Override + public boolean equals(Object other) { + return other instanceof UnificationEntry holder && holder.key() == key(); + } + + @Override + public String toString() { + return "UnificationEntry{" + key() + "}"; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/UnificationLookupImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/UnificationLookupImpl.java new file mode 100644 index 0000000..4cd9e76 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/UnificationLookupImpl.java @@ -0,0 +1,159 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.api.unification.*; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Predicate; + +public final class UnificationLookupImpl implements UnificationLookup { + + private final ModPriorities modPriorities; + private final StoneVariants stoneVariants; + private final TagSubstitutions tagSubstitutions; + private final Map, Set>> tagsToEntries; + private final Map> idsToEntries; + + private UnificationLookupImpl(ModPriorities modPriorities, StoneVariants stoneVariants, TagSubstitutions tagSubstitutions, Map, Set>> tagsToEntries, Map> idsToEntries) { + this.modPriorities = modPriorities; + this.stoneVariants = stoneVariants; + this.tagSubstitutions = tagSubstitutions; + this.tagsToEntries = tagsToEntries; + this.idsToEntries = idsToEntries; + } + + @Override + public Collection> getTags() { + return tagsToEntries.keySet(); + } + + @Override + public Collection> getTagEntries(TagKey tag) { + return tagsToEntries.getOrDefault(tag, Collections.emptySet()); + } + + @Nullable + @Override + public UnificationEntry getItemEntry(ResourceLocation item) { + return idsToEntries.get(item); + } + + @Nullable + @Override + public TagKey getRelevantItemTag(ResourceLocation item) { + UnificationEntry entry = idsToEntries.get(item); + return entry == null ? null : entry.tag(); + } + + @Nullable + @Override + public UnificationEntry getVariantItemTarget(ResourceLocation item) { + var tag = getRelevantItemTag(item); + if (tag == null) return null; + + if (stoneVariants.isOreTag(tag)) { + String stoneVariant = stoneVariants.getStoneVariant(item); + return getTagTargetItem(tag, itemId -> stoneVariant.equals(stoneVariants.getStoneVariant(itemId))); + } + + return getTagTargetItem(tag); + } + + @Nullable + @Override + public UnificationEntry getTagTargetItem(TagKey tag, Predicate itemFilter) { + var substituteTag = tagSubstitutions.getSubstituteTag(tag); + var tagToCheck = substituteTag != null ? substituteTag : tag; + + var items = getTagEntries(tagToCheck) + .stream() + .filter(entry -> itemFilter.test(entry.id())) + // sort by length so clean stone variants come first + .sorted(Comparator.comparingInt(value -> value.id().toString().length())) + .toList(); + + return items.isEmpty() ? null : modPriorities.findTargetItem(tagToCheck, items); + } + + @Override + public boolean isUnifiedIngredientItem(Ingredient ingredient, ItemStack item) { + Set> checkedTags = new HashSet<>(); + + for (ItemStack stack : ingredient.getItems()) { + ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(stack.getItem()); + + var relevantTag = getRelevantItemTag(itemId); + if (relevantTag == null || checkedTags.contains(relevantTag)) continue; + checkedTags.add(relevantTag); + + if (item.is(relevantTag)) { + return true; + } + } + + return false; + } + + public static class Builder { + + private final Set> createdEntries = new HashSet<>(); + private final Map, Set>> tagsToEntries = new HashMap<>(); + + private void put(TagKey tag, UnificationEntry entry) { + if (createdEntries.contains(entry)) { + throw new IllegalStateException("entry " + entry + " already created"); + } + + createdEntries.add(entry); + tagsToEntries.computeIfAbsent(tag, $ -> new HashSet<>()).add(entry); + } + + public Builder put(TagKey tag, ResourceLocation... ids) { + for (ResourceLocation id : ids) { + put(tag, new UnificationEntryImpl<>(BuiltInRegistries.ITEM, id)); + } + + return this; + } + + public Builder put(TagKey tag, Item... items) { + for (var item : items) { + put(tag, new UnificationEntryImpl<>(BuiltInRegistries.ITEM, item)); + } + + return this; + } + + public UnificationLookup build(ModPriorities modPriorities, StoneVariants stoneVariants, TagSubstitutions tagSubstitutions) { + ImmutableMap.Builder, Set>> tagsToEntriesBuilder = ImmutableMap.builder(); + ImmutableMap.Builder> idsToEntriesBuilder = ImmutableMap.builder(); + + tagsToEntries.forEach((tag, entries) -> { + ImmutableSet.Builder> entrySetBuilder = ImmutableSet.builder(); + for (var entry : entries) { + entrySetBuilder.add(entry); + ((UnificationEntryImpl) entry).bindTag(tag); + idsToEntriesBuilder.put(entry.id(), entry); + } + + tagsToEntriesBuilder.put(tag, entrySetBuilder.build()); + }); + + return new UnificationLookupImpl( + modPriorities, + stoneVariants, + tagSubstitutions, + tagsToEntriesBuilder.build(), + idsToEntriesBuilder.build() + ); + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/UnificationSettingsImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/UnificationSettingsImpl.java new file mode 100644 index 0000000..ce1356e --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/UnificationSettingsImpl.java @@ -0,0 +1,169 @@ +package com.almostreliable.unified.unification; + +import com.almostreliable.unified.api.unification.*; +import com.almostreliable.unified.config.UnificationConfig; +import com.almostreliable.unified.utils.VanillaTagWrapper; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.block.Block; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public final class UnificationSettingsImpl implements UnificationSettings { + + private final String name; + private final ModPriorities modPriorities; + private final StoneVariants stoneVariants; + private final Function recipeTypeCheck; + private final Function recipeIdCheck; + private final boolean recipeViewerHiding; + private final boolean lootUnification; + private final Function lootTableCheck; + private final UnificationLookup unificationLookup; + private final Runnable clearCaches; + + private UnificationSettingsImpl(String name, ModPriorities modPriorities, StoneVariants stoneVariants, Function recipeTypeCheck, Function recipeIdCheck, boolean recipeViewerHiding, boolean lootUnification, Function lootTableCheck, UnificationLookup unificationLookup, Runnable clearCaches) { + this.name = name; + this.modPriorities = modPriorities; + this.stoneVariants = stoneVariants; + this.recipeTypeCheck = recipeTypeCheck; + this.recipeIdCheck = recipeIdCheck; + this.recipeViewerHiding = recipeViewerHiding; + this.lootUnification = lootUnification; + this.lootTableCheck = lootTableCheck; + this.unificationLookup = unificationLookup; + this.clearCaches = clearCaches; + } + + public static List create(Collection configs, VanillaTagWrapper itemTags, VanillaTagWrapper blockTags, TagSubstitutionsImpl tagSubstitutions) { + return configs + .stream() + .map(config -> create(config, itemTags, blockTags, tagSubstitutions)) + .toList(); + } + + public static UnificationSettings create(UnificationConfig config, VanillaTagWrapper itemTags, VanillaTagWrapper blockTags, TagSubstitutions tagSubstitutions) { + var lookupBuilder = new UnificationLookupImpl.Builder(); + for (var tag : config.getTags()) { + var itemHolders = itemTags.get(tag); + for (var itemHolder : itemHolders) { + itemHolder.unwrapKey().ifPresent(itemKey -> { + var itemId = itemKey.location(); + if (config.shouldIncludeItem(itemId)) { + lookupBuilder.put(tag, itemId); + } + }); + } + } + + ModPriorities modPriorities = config.getModPriorities(); + StoneVariants stoneVariants = StoneVariantsImpl.create( + config.getStoneVariants(), + itemTags, + blockTags + ); + + return new UnificationSettingsImpl( + config.getName(), + modPriorities, + stoneVariants, + config::shouldIncludeRecipeType, + config::shouldIncludeRecipeId, + config.shouldHideVariantItems(), + config.shouldUnifyLoot(), + config::shouldIncludeLootTable, + lookupBuilder.build(modPriorities, stoneVariants, tagSubstitutions), + config::clearCaches + ); + } + + @Override + public String getName() { + return name; + } + + @Override + public ModPriorities getModPriorities() { + return modPriorities; + } + + @Override + public StoneVariants getStoneVariants() { + return stoneVariants; + } + + @Override + public boolean shouldIncludeRecipeType(ResourceLocation type) { + return recipeTypeCheck.apply(type); + } + + @Override + public boolean shouldIncludeRecipeId(ResourceLocation id) { + return recipeIdCheck.apply(id); + } + + @Override + public boolean shouldHideVariantItems() { + return recipeViewerHiding; + } + + @Override + public boolean shouldUnifyLoot() { + return lootUnification; + } + + @Override + public boolean shouldIncludeLootTable(ResourceLocation table) { + return lootTableCheck.apply(table); + } + + @Override + public Collection> getTags() { + return unificationLookup.getTags(); + } + + @Override + public Collection> getTagEntries(TagKey tag) { + return unificationLookup.getTagEntries(tag); + } + + @Nullable + @Override + public UnificationEntry getItemEntry(ResourceLocation item) { + return unificationLookup.getItemEntry(item); + } + + @Nullable + @Override + public TagKey getRelevantItemTag(ResourceLocation item) { + return unificationLookup.getRelevantItemTag(item); + } + + @Nullable + @Override + public UnificationEntry getVariantItemTarget(ResourceLocation item) { + return unificationLookup.getVariantItemTarget(item); + } + + @Nullable + @Override + public UnificationEntry getTagTargetItem(TagKey tag, Predicate itemFilter) { + return unificationLookup.getTagTargetItem(tag, itemFilter); + } + + @Override + public boolean isUnifiedIngredientItem(Ingredient ingredient, ItemStack item) { + return unificationLookup.isUnifiedIngredientItem(ingredient, item); + } + + public void clearCache() { + clearCaches.run(); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnification.java b/Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnification.java new file mode 100644 index 0000000..f93c6e6 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnification.java @@ -0,0 +1,60 @@ +package com.almostreliable.unified.unification.loot; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.AlmostUnifiedRuntime; +import com.almostreliable.unified.api.unification.UnificationSettings; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.storage.loot.LootTable; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +public final class LootUnification { + + private LootUnification() {} + + public static void unifyLoot(AlmostUnifiedRuntime runtime, HolderLookup.Provider registries) { + try { + var handlers = runtime.getUnificationSettings(); + + boolean enableLootUnification = handlers + .stream() + .anyMatch(UnificationSettings::shouldUnifyLoot); + if (!enableLootUnification) { + return; + } + + var lootTableRegistry = registries.lookupOrThrow(Registries.LOOT_TABLE); + + lootTableRegistry + .listElements() + .forEach(holder -> unifyLoot(holder.value(), holder.key().location(), handlers)); + } catch (Exception e) { + AlmostUnifiedCommon.LOGGER.error("Failed to unify loot", e); + } + } + + public static void unifyLoot(LootTable lootTable, ResourceLocation tableId, Collection unificationSettings) { + LootUnificationHandler lootUnificationHandler = LootUnificationHandler.cast(lootTable); + + Set modifiedTable = new HashSet<>(); + for (UnificationSettings handler : unificationSettings) { + if (handler.shouldUnifyLoot() && handler.shouldIncludeLootTable(tableId)) { + if (lootUnificationHandler.almostunified$unify(handler)) { + modifiedTable.add(handler); + } + } + } + + if (!modifiedTable.isEmpty()) { + AlmostUnifiedCommon.LOGGER.info("Loot table '{}' was unified by: {}", + tableId, + modifiedTable.stream().map(UnificationSettings::getName).collect( + Collectors.joining(", "))); + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnificationHandler.java b/Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnificationHandler.java new file mode 100644 index 0000000..ff537b9 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/loot/LootUnificationHandler.java @@ -0,0 +1,18 @@ +package com.almostreliable.unified.unification.loot; + +import com.almostreliable.unified.api.unification.UnificationLookup; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; + +public interface LootUnificationHandler { + + static LootUnificationHandler cast(LootPool pool) { + return (LootUnificationHandler) pool; + } + + static LootUnificationHandler cast(LootTable table) { + return (LootUnificationHandler) table; + } + + boolean almostunified$unify(UnificationLookup lookup); +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/loot/package-info.java b/Common/src/main/java/com/almostreliable/unified/unification/loot/package-info.java new file mode 100644 index 0000000..dac2a68 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/loot/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.unification.loot; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/unification/package-info.java b/Common/src/main/java/com/almostreliable/unified/unification/package-info.java new file mode 100644 index 0000000..91e5fcb --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.unification; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeJsonImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeJsonImpl.java new file mode 100644 index 0000000..d82ea45 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeJsonImpl.java @@ -0,0 +1,48 @@ +package com.almostreliable.unified.unification.recipe; + +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; + +public class RecipeJsonImpl implements RecipeJson { + + private final ResourceLocation id; + private final JsonObject json; + + public RecipeJsonImpl(ResourceLocation id, JsonObject json) { + this.id = id; + this.json = json; + } + + @Override + public ResourceLocation getId() { + return id; + } + + @Override + public ResourceLocation getType() { + try { + return ResourceLocation.parse(json.get("type").getAsString()); + } catch (Exception e) { + throw new IllegalArgumentException("could not detect recipe type"); + } + } + + @Override + public boolean hasProperty(String key) { + return json.has(key); + } + + @Override + public JsonElement getProperty(String key) { + return json.get(key); + } + + @Override + public void setProperty(String key, JsonElement value) { + Preconditions.checkNotNull(value, "value cannot be null"); + json.add(key, value); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeLink.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java similarity index 90% rename from Common/src/main/java/com/almostreliable/unified/recipe/RecipeLink.java rename to Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java index 4252f0b..85e77a6 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeLink.java +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeLink.java @@ -1,17 +1,18 @@ -package com.almostreliable.unified.recipe; +package com.almostreliable.unified.unification.recipe; +import com.almostreliable.unified.api.unification.recipe.RecipeData; import com.almostreliable.unified.utils.JsonCompare; +import com.google.common.base.Preconditions; import com.google.gson.JsonObject; import net.minecraft.resources.ResourceLocation; import javax.annotation.Nullable; import java.util.Collections; import java.util.HashSet; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -public class RecipeLink { +public class RecipeLink implements RecipeData { private final ResourceLocation id; private final ResourceLocation type; private final JsonObject originalRecipe; @@ -25,7 +26,7 @@ public class RecipeLink { try { this.type = ResourceLocation.tryParse(originalRecipe.get("type").getAsString()); } catch (Exception e) { - throw new IllegalArgumentException("Could not detect recipe type"); + throw new IllegalArgumentException("could not detect recipe type"); } } @@ -57,14 +58,21 @@ public class RecipeLink { return null; } + @Override public ResourceLocation getId() { return id; } + @Override public ResourceLocation getType() { return type; } + @Override + public boolean hasProperty(String key) { + return getOriginal().has(key); + } + public JsonObject getOriginal() { return originalRecipe; } @@ -79,9 +87,9 @@ public class RecipeLink { } private void updateDuplicateLink(@Nullable DuplicateLink duplicateLink) { - Objects.requireNonNull(duplicateLink); + Preconditions.checkNotNull(duplicateLink); if (hasDuplicateLink() && getDuplicateLink() != duplicateLink) { - throw new IllegalStateException("Recipe is already linked to " + getDuplicateLink()); + throw new IllegalStateException("recipe is already linked to " + getDuplicateLink()); } this.duplicateLink = duplicateLink; @@ -98,9 +106,9 @@ public class RecipeLink { } void setUnified(JsonObject json) { - Objects.requireNonNull(json); + Preconditions.checkNotNull(json); if (isUnified()) { - throw new IllegalStateException("Recipe already unified"); + throw new IllegalStateException("recipe already unified"); } this.unifiedRecipe = json; @@ -174,7 +182,7 @@ public class RecipeLink { } private void updateMaster(RecipeLink master) { - Objects.requireNonNull(master); + Preconditions.checkNotNull(master); addDuplicate(master); this.currentMaster = master; } diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java similarity index 59% rename from Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java rename to Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java index ebdf5be..519ed4f 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/RecipeTransformer.java +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeTransformer.java @@ -1,20 +1,22 @@ -package com.almostreliable.unified.recipe; +package com.almostreliable.unified.unification.recipe; -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.config.DuplicationConfig; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.api.unification.UnificationSettings; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import com.almostreliable.unified.compat.viewer.ClientRecipeTracker; +import com.almostreliable.unified.config.Config; +import com.almostreliable.unified.config.DuplicateConfig; +import com.almostreliable.unified.unification.UnificationSettingsImpl; import com.almostreliable.unified.utils.JsonCompare; -import com.almostreliable.unified.utils.JsonQuery; import com.almostreliable.unified.utils.RecipeTypePropertiesLogger; -import com.almostreliable.unified.utils.ReplacementMap; import com.google.common.base.Stopwatch; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; import net.minecraft.resources.ResourceLocation; import javax.annotation.Nullable; @@ -24,93 +26,54 @@ import java.util.stream.Collectors; public class RecipeTransformer { - private final RecipeHandlerFactory factory; - private final ReplacementMap replacementMap; - private final UnifyConfig unifyConfig; - private final DuplicationConfig duplicationConfig; - + private final RecipeUnifierRegistry factory; + private final Collection unificationSettings; + private final DuplicateConfig duplicateConfig; private final RecipeTypePropertiesLogger propertiesLogger = new RecipeTypePropertiesLogger(); - public RecipeTransformer(RecipeHandlerFactory factory, ReplacementMap replacementMap, UnifyConfig unifyConfig, DuplicationConfig duplicationConfig) { + public RecipeTransformer(RecipeUnifierRegistry factory, Collection unificationSettings) { this.factory = factory; - this.replacementMap = replacementMap; - this.unifyConfig = unifyConfig; - this.duplicationConfig = duplicationConfig; - } - - public boolean hasValidRecipeType(JsonObject json) { - if (json.get("type") instanceof JsonPrimitive primitive) { - ResourceLocation type = ResourceLocation.tryParse(primitive.getAsString()); - return type != null && unifyConfig.includeRecipeType(type); - } - return false; + this.unificationSettings = unificationSettings; + this.duplicateConfig = Config.load(DuplicateConfig.NAME, DuplicateConfig.SERIALIZER); } /** - * Transforms a map of recipes. This method will modify the map in-place. Part of the transformation is to unify recipes with the given {@link ReplacementMap}. + * Transforms a map of recipes. This method will modify the map in-place. Part of the transformation is to unify recipes with the given {@link UnificationLookup}. * After unification, recipes will be checked for duplicates. All duplicates will be removed from the map. * - * @param recipes The map of recipes to transform. - * @param skipClientTracking Whether to skip client tracking for the recipes. + * @param recipes The map of recipes to transform. * @return The result of the transformation. */ - public Result transformRecipes(Map recipes, boolean skipClientTracking) { + public Result transformRecipes(Map recipes) { Stopwatch transformationTimer = Stopwatch.createStarted(); - AlmostUnified.LOG.warn("Recipe count: " + recipes.size()); + AlmostUnifiedCommon.LOGGER.info("Recipe count: {}", recipes.size()); - ClientRecipeTracker.RawBuilder tracker = skipClientTracking ? null : new ClientRecipeTracker.RawBuilder(); + var tracker = AlmostUnifiedCommon.STARTUP_CONFIG.isServerOnly() ? null : new ClientRecipeTracker.RawBuilder(); Result result = new Result(); Map> byType = groupRecipesByType(recipes); - ResourceLocation fcLocation = new ResourceLocation("forge:conditional"); byType.forEach((type, recipeLinks) -> { - if (type.equals(fcLocation)) { - recipeLinks.forEach(recipeLink -> handleForgeConditionals(recipeLink).ifPresent(json -> recipes.put( - recipeLink.getId(), - json))); - } else { - transformRecipes(recipeLinks, recipes, tracker); - } + transformRecipes(recipeLinks, recipes, tracker); result.addAll(recipeLinks); }); - AlmostUnified.LOG.warn("Recipe count afterwards: " + recipes.size() + " (done in " + transformationTimer.stop() + ")"); + AlmostUnifiedCommon.LOGGER.info( + "Recipe count afterwards: {} (done in {})", + recipes.size(), + transformationTimer.stop() + ); - unifyConfig.clearCache(); - duplicationConfig.clearCache(); + for (UnificationSettings settings : unificationSettings) { + ((UnificationSettingsImpl) settings).clearCache(); + } + duplicateConfig.clearCache(); if (tracker != null) recipes.putAll(tracker.compute()); return result; } - private Optional handleForgeConditionals(RecipeLink recipeLink) { - JsonObject copy = recipeLink.getOriginal().deepCopy(); - - if (copy.get("recipes") instanceof JsonArray recipes) { - for (JsonElement element : recipes) { - JsonQuery - .of(element, "recipe") - .asObject() - .map(jsonObject -> new RecipeLink(recipeLink.getId(), jsonObject)) - .ifPresent(temporaryLink -> { - unifyRecipe(temporaryLink); - if (temporaryLink.isUnified()) { - element.getAsJsonObject().add("recipe", temporaryLink.getUnified()); - } - }); - } - - if (!copy.equals(recipeLink.getOriginal())) { - recipeLink.setUnified(copy); - return Optional.of(copy); - } - } - - return Optional.empty(); - } - /** - * Transforms a list of recipes. Part of the transformation is to unify recipes with the given {@link ReplacementMap}. + * Transforms a list of recipes. Part of the transformation is to unify recipes with the given {@link UnificationLookup}. * After unification, recipes will be checked for duplicates. * All duplicates will be removed from {@code Map allRecipes}, * while unified recipes will replace the original recipes in the map. @@ -123,7 +86,7 @@ public class RecipeTransformer { */ private void transformRecipes(List recipeLinks, Map allRecipes, @Nullable ClientRecipeTracker.RawBuilder tracker) { var unified = unifyRecipes(recipeLinks, r -> allRecipes.put(r.getId(), r.getUnified())); - var duplicates = handleDuplicates(duplicationConfig.isStrictMode() ? recipeLinks : unified, recipeLinks); + var duplicates = handleDuplicates(duplicateConfig.isStrictMode() ? recipeLinks : unified, recipeLinks); duplicates .stream() .flatMap(d -> d.getRecipesWithoutMaster().stream()) @@ -135,23 +98,11 @@ public class RecipeTransformer { return recipes .entrySet() .stream() - .filter(entry -> includeRecipe(entry.getKey(), entry.getValue())) .map(entry -> new RecipeLink(entry.getKey(), entry.getValue().getAsJsonObject())) .sorted(Comparator.comparing(entry -> entry.getId().toString())) .collect(Collectors.groupingByConcurrent(RecipeLink::getType)); } - /** - * Checks if a recipe should be included in the transformation. - * - * @param recipe The recipe to check. - * @param json The recipe's json. Will be used to check if the recipe has a valid type. - * @return True if the recipe should be included, false otherwise. - */ - private boolean includeRecipe(ResourceLocation recipe, JsonElement json) { - return unifyConfig.includeRecipe(recipe) && json.isJsonObject() && hasValidRecipeType(json.getAsJsonObject()); - } - /** * Compares a list of recipes against another list for duplicates. * @@ -170,11 +121,11 @@ public class RecipeTransformer { } private boolean handleDuplicate(RecipeLink curRecipe, List recipes) { - if (duplicationConfig.shouldIgnoreRecipe(curRecipe)) { + if (duplicateConfig.shouldIgnoreRecipe(curRecipe)) { return false; } - JsonCompare.CompareSettings compareSettings = duplicationConfig.getCompareSettings(curRecipe.getType()); + JsonCompare.CompareSettings compareSettings = duplicateConfig.getCompareSettings(curRecipe.getType()); boolean foundDuplicate = false; for (RecipeLink recipeLink : recipes) { if (!curRecipe.getType().equals(recipeLink.getType())) { @@ -182,7 +133,7 @@ public class RecipeTransformer { "Recipe types do not match for " + curRecipe.getId() + " and " + recipeLink.getId()); } - if (recipeLink == curRecipe || duplicationConfig.shouldIgnoreRecipe(recipeLink)) { + if (recipeLink == curRecipe || duplicateConfig.shouldIgnoreRecipe(recipeLink)) { continue; } @@ -213,23 +164,32 @@ public class RecipeTransformer { /** * Unifies a single recipe link. This method will modify the recipe link in-place. - * {@link RecipeHandlerFactory} will apply multiple unification's onto the recipe. + * {@link RecipeUnifierRegistryImpl} will apply multiple unification's onto the recipe. * * @param recipe The recipe link to unify. */ public void unifyRecipe(RecipeLink recipe) { try { - RecipeContextImpl ctx = new RecipeContextImpl(recipe.getOriginal(), replacementMap); - RecipeUnifierBuilderImpl builder = new RecipeUnifierBuilderImpl(); - factory.fillUnifier(builder, ctx); - JsonObject result = builder.unify(recipe.getOriginal(), ctx); - if (result != null) { - recipe.setUnified(result); + JsonObject recipeCopy = recipe.getOriginal().deepCopy(); + RecipeJson json = new RecipeJsonImpl(recipe.getId(), recipeCopy); + + for (var settings : unificationSettings) { + if (!settings.shouldIncludeRecipe(recipe)) { + continue; + } + + UnificationHelperImpl helper = new UnificationHelperImpl(settings); + RecipeUnifier unifier = factory.getRecipeUnifier(recipe); + unifier.unify(helper, json); } - propertiesLogger.log(recipe.getType(), recipe.getOriginal(), builder.getKeys()); + + if (!recipe.getOriginal().equals(recipeCopy)) { + recipe.setUnified(recipeCopy); + } + + propertiesLogger.log(recipe.getType(), recipe.getOriginal()); } catch (Exception e) { - AlmostUnified.LOG.warn("Error unifying recipe '{}': {}", recipe.getId(), e.getMessage()); - e.printStackTrace(); + AlmostUnifiedCommon.LOGGER.error("Error unifying recipe '{}'", recipe.getId(), e); } } @@ -241,7 +201,7 @@ public class RecipeTransformer { private void add(RecipeLink link) { if (allRecipesByType.containsEntry(link.getType(), link)) { - throw new IllegalStateException("Already tracking recipe type " + link.getType()); + throw new IllegalStateException("already tracking recipe type " + link.getType()); } allRecipesByType.put(link.getType(), link); @@ -270,10 +230,6 @@ public class RecipeTransformer { return Collections.unmodifiableCollection(duplicatesByType.get(type)); } - public int getRecipeCount() { - return allRecipesByType.size(); - } - public int getUnifiedRecipeCount() { return unifiedRecipesByType.size(); } diff --git a/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeUnifierRegistryImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeUnifierRegistryImpl.java new file mode 100644 index 0000000..8b94d90 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/RecipeUnifierRegistryImpl.java @@ -0,0 +1,52 @@ +package com.almostreliable.unified.unification.recipe; + +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.bundled.ShapedRecipeUnifier; +import com.almostreliable.unified.api.unification.bundled.SmithingRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeData; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import net.minecraft.resources.ResourceLocation; + +import java.util.HashMap; +import java.util.Map; + +public class RecipeUnifierRegistryImpl implements RecipeUnifierRegistry { + + private final Map recipeUnifiersByRecipeType = new HashMap<>(); + private final Map recipeUnifiersByModId = new HashMap<>(); + + @Override + public void registerForRecipeType(ResourceLocation recipeType, RecipeUnifier recipeUnifier) { + recipeUnifiersByRecipeType.put(recipeType, recipeUnifier); + } + + @Override + public void registerForModId(String modId, RecipeUnifier recipeUnifier) { + recipeUnifiersByModId.put(modId, recipeUnifier); + } + + @Override + public RecipeUnifier getRecipeUnifier(RecipeData recipeData) { + var type = recipeData.getType(); + var byType = recipeUnifiersByRecipeType.get(type); + if (byType != null) { + return byType; + } + + var byMod = recipeUnifiersByModId.get(type.getNamespace()); + if (byMod != null) { + return byMod; + } + + if (SmithingRecipeUnifier.isApplicable(recipeData)) { + return SmithingRecipeUnifier.INSTANCE; + } + + if (ShapedRecipeUnifier.isApplicable(recipeData)) { + return ShapedRecipeUnifier.INSTANCE; + } + + return GenericRecipeUnifier.INSTANCE; + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/recipe/UnificationHelperImpl.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/UnificationHelperImpl.java new file mode 100644 index 0000000..4a59837 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/UnificationHelperImpl.java @@ -0,0 +1,221 @@ +package com.almostreliable.unified.unification.recipe; + +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; + +import javax.annotation.Nullable; + +public record UnificationHelperImpl(UnificationLookup getUnificationLookup) implements UnificationHelper { + + @Override + public boolean unifyInputs(RecipeJson recipe, String... keys) { + Preconditions.checkArgument(keys.length > 0, "at least one key is required"); + boolean changed = false; + + for (String key : keys) { + JsonElement jsonElement = recipe.getProperty(key); + if (jsonElement == null) continue; + + changed |= unifyInputElement(jsonElement); + } + + return changed; + } + + @Override + public boolean unifyInputElement(@Nullable JsonElement jsonElement, String... keys) { + return jsonElement != null && switch (jsonElement) { + case JsonArray jsonArray -> unifyInputArray(jsonArray, keys); + case JsonObject jsonObject -> unifyInputObject(jsonObject, keys); + default -> false; + }; + } + + @Override + public boolean unifyInputArray(JsonArray jsonArray, String... keys) { + boolean changed = false; + + for (JsonElement jsonElement : jsonArray) { + changed |= unifyInputElement(jsonElement, keys); + } + + return changed; + } + + @Override + public boolean unifyInputObject(JsonObject jsonObject, String... keys) { + boolean changed = false; + + for (String key : keys.length == 0 ? RecipeConstants.DEFAULT_INPUT_INNER_KEYS : keys) { + var jsonElement = jsonObject.get(key); + if (jsonElement == null) continue; + + changed |= unifyInputElement(jsonElement); + } + + changed |= unifyInputTag(jsonObject); + changed |= unifyInputItem(jsonObject); + + return changed; + } + + @Override + public boolean unifyInputTag(JsonObject jsonObject) { + if (!(jsonObject.get(RecipeConstants.TAG) instanceof JsonPrimitive jsonPrimitive)) return false; + + var tag = TagKey.create(Registries.ITEM, ResourceLocation.parse(jsonPrimitive.getAsString())); + var substituteTag = AlmostUnified.INSTANCE.getRuntimeOrThrow().getTagSubstitutions().getSubstituteTag(tag); + if (substituteTag == null) return false; + + jsonObject.addProperty(RecipeConstants.TAG, substituteTag.location().toString()); + return true; + } + + // TODO: add ignore list for recipes that should not have its inputs converted to tags + @Override + public boolean unifyInputItem(JsonObject jsonObject) { + if (!(jsonObject.get(RecipeConstants.ITEM) instanceof JsonPrimitive jsonPrimitive)) return false; + + ResourceLocation item = ResourceLocation.parse(jsonPrimitive.getAsString()); + var tag = getUnificationLookup.getRelevantItemTag(item); + if (tag != null) { + jsonObject.remove(RecipeConstants.ITEM); + jsonObject.addProperty(RecipeConstants.TAG, tag.location().toString()); + return true; + } + + return false; + } + + @Override + public boolean unifyOutputs(RecipeJson recipe, String... keys) { + return unifyOutputs(recipe, true, keys); + } + + @Override + public boolean unifyOutputs(RecipeJson recipe, boolean tagsToItems, String... keys) { + Preconditions.checkArgument(keys.length > 0, "at least one key is required"); + boolean changed = false; + + for (String key : keys) { + JsonElement jsonElement = recipe.getProperty(key); + if (jsonElement == null) continue; + + if (jsonElement instanceof JsonPrimitive jsonPrimitive) { + var replacement = handleOutputItemReplacement(jsonPrimitive); + if (replacement == null) continue; + recipe.setProperty(key, replacement); + changed = true; + continue; + } + + changed |= unifyOutputElement(jsonElement, tagsToItems); + } + + return changed; + } + + @Override + public boolean unifyOutputs(RecipeJson recipe, String key, boolean tagsToItems, String... innerKeys) { + Preconditions.checkArgument(innerKeys.length > 0, "at least one inner key is required"); + return unifyOutputElement(recipe.getProperty(key), tagsToItems, innerKeys); + } + + @Override + public boolean unifyOutputElement(@Nullable JsonElement json, boolean tagsToItems, String... keys) { + return json != null && switch (json) { + case JsonArray jsonArray -> unifyOutputArray(jsonArray, tagsToItems, keys); + case JsonObject jsonObject -> unifyOutputObject(jsonObject, tagsToItems, keys); + default -> false; + }; + + } + + @Override + public boolean unifyOutputArray(JsonArray jsonArray, boolean tagsToItems, String... keys) { + boolean changed = false; + + for (JsonElement jsonElement : jsonArray) { + changed |= unifyOutputElement(jsonElement, tagsToItems, keys); + } + + return changed; + } + + @Override + public boolean unifyOutputObject(JsonObject jsonObject, boolean tagsToItems, String... keys) { + boolean changed = false; + + for (String key : keys.length == 0 ? RecipeConstants.DEFAULT_OUTPUT_INNER_KEYS : keys) { + var jsonElement = jsonObject.get(key); + if (jsonElement == null) continue; + + changed |= unifyOutputElement(jsonElement, tagsToItems, keys); + } + + changed |= unifyOutputTag(jsonObject, tagsToItems); + changed |= unifyOutputItem(jsonObject); + + return changed; + } + + @Override + public boolean unifyOutputTag(JsonObject jsonObject, boolean tagsToItems) { + if (!(jsonObject.get(RecipeConstants.TAG) instanceof JsonPrimitive jsonPrimitive)) return false; + + var tag = TagKey.create(Registries.ITEM, ResourceLocation.parse(jsonPrimitive.getAsString())); + + if (tagsToItems) { + var entry = getUnificationLookup.getTagTargetItem(tag); + if (entry == null) return false; + + jsonObject.remove(RecipeConstants.TAG); + jsonObject.addProperty(RecipeConstants.ITEM, entry.id().toString()); + return true; + } + + var substituteTag = AlmostUnified.INSTANCE.getRuntimeOrThrow().getTagSubstitutions().getSubstituteTag(tag); + if (substituteTag == null) return false; + + jsonObject.addProperty(RecipeConstants.TAG, substituteTag.location().toString()); + return true; + } + + @Override + public boolean unifyOutputItem(JsonObject jsonObject) { + boolean changed = unifyOutputItem(jsonObject, RecipeConstants.ITEM); + changed |= unifyOutputItem(jsonObject, RecipeConstants.ID); + return changed; + } + + @Override + public boolean unifyOutputItem(JsonObject jsonObject, String key) { + if (!(jsonObject.get(key) instanceof JsonPrimitive jsonPrimitive)) return false; + + JsonPrimitive replacement = handleOutputItemReplacement(jsonPrimitive); + if (replacement == null) return false; + + jsonObject.addProperty(key, replacement.getAsString()); + return true; + } + + @Override + @Nullable + public JsonPrimitive handleOutputItemReplacement(JsonPrimitive jsonPrimitive) { + ResourceLocation item = ResourceLocation.parse(jsonPrimitive.getAsString()); + var entry = getUnificationLookup.getVariantItemTarget(item); + if (entry == null || entry.id().equals(item)) return null; + return new JsonPrimitive(entry.id().toString()); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/unification/recipe/package-info.java b/Common/src/main/java/com/almostreliable/unified/unification/recipe/package-info.java new file mode 100644 index 0000000..6b44a36 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/unification/recipe/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.unification.recipe; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/main/java/com/almostreliable/unified/ClientTagUpdateEvent.java b/Common/src/main/java/com/almostreliable/unified/utils/ClientTagUpdateEvent.java similarity index 91% rename from Common/src/main/java/com/almostreliable/unified/ClientTagUpdateEvent.java rename to Common/src/main/java/com/almostreliable/unified/utils/ClientTagUpdateEvent.java index d3d0dc9..8ec0f21 100644 --- a/Common/src/main/java/com/almostreliable/unified/ClientTagUpdateEvent.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/ClientTagUpdateEvent.java @@ -1,4 +1,4 @@ -package com.almostreliable.unified; +package com.almostreliable.unified.utils; import java.util.ArrayList; import java.util.List; diff --git a/Common/src/main/java/com/almostreliable/unified/utils/CustomLogger.java b/Common/src/main/java/com/almostreliable/unified/utils/CustomLogger.java new file mode 100644 index 0000000..c0d8f03 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/utils/CustomLogger.java @@ -0,0 +1,91 @@ +package com.almostreliable.unified.utils; + +import com.almostreliable.unified.BuildConfig; +import com.almostreliable.unified.api.constant.ModConstants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender; +import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.RollingFileManager; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.action.Action; +import org.apache.logging.log4j.core.appender.rolling.action.DeleteAction; +import org.apache.logging.log4j.core.appender.rolling.action.IfFileName; +import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.layout.PatternLayout; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +public final class CustomLogger { + + private static final String BACKUP_FILE = ModConstants.ALMOST_UNIFIED + "-backup.log.gz"; + private static final String FILE = ModConstants.ALMOST_UNIFIED + ".log"; + private static final String LOG_PATH = "logs/" + ModConstants.ALMOST_UNIFIED; + + private CustomLogger() {} + + public static Logger create() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + + PathCondition[] conditions = { IfFileName.createNameCondition(BACKUP_FILE, null) }; + var deleteAction = DeleteAction.createDeleteAction(LOG_PATH, false, 1, false, null, conditions, null, config); + var strategy = DefaultRolloverStrategy.newBuilder().withCustomActions(new Action[]{ deleteAction }).build(); + + var layout = PatternLayout + .newBuilder() + .withConfiguration(config) + .withCharset(StandardCharsets.UTF_8) + .withPattern("[%d{HH:mm:ss.SSS}] [%level]: %minecraftFormatting{%msg{nolookup}}{strip}%n%xEx") + .build(); + + var fileAppender = RollingRandomAccessFileAppender + .newBuilder() + .withAppend(true) + .withFileName(LOG_PATH + "/" + FILE) + .withFilePattern(LOG_PATH + "/" + BACKUP_FILE) + .withStrategy(strategy) + .withPolicy(new Policy()) + .setName(BuildConfig.MOD_NAME + " File") + .setLayout(layout) + .setConfiguration(config) + .build(); + + fileAppender.start(); + + LoggerConfig loggerConfig = new LoggerConfig(BuildConfig.MOD_NAME, null, false); + loggerConfig.addAppender(fileAppender, null, null); + + Optional.ofNullable(config.getAppenders().get("Console")) // latest.log for neoforge + .ifPresent(a -> loggerConfig.addAppender(a, null, null)); + Optional.ofNullable(config.getAppenders().get("SysOut")) // latest.log for fabric + .ifPresent(a -> loggerConfig.addAppender(a, null, null)); + Optional.ofNullable(config.getAppenders().get("ServerGuiConsole")) // game console + .ifPresent(a -> loggerConfig.addAppender(a, null, null)); + + config.addLogger(BuildConfig.MOD_NAME, loggerConfig); + return LogManager.getLogger(BuildConfig.MOD_NAME); + } + + private static class Policy implements TriggeringPolicy { + + private boolean reset = true; + + @Override + public void initialize(RollingFileManager manager) {} + + @Override + public boolean isTriggeringEvent(LogEvent logEvent) { + if (reset) { + reset = false; + return true; + } + return false; + } + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/DebugHandler.java b/Common/src/main/java/com/almostreliable/unified/utils/DebugHandler.java new file mode 100644 index 0000000..850f4e9 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/utils/DebugHandler.java @@ -0,0 +1,260 @@ +package com.almostreliable.unified.utils; + +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.config.Config; +import com.almostreliable.unified.config.DebugConfig; +import com.almostreliable.unified.unification.recipe.RecipeLink; +import com.almostreliable.unified.unification.recipe.RecipeTransformer; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.Map; +import java.util.function.Supplier; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class DebugHandler { + + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final String DUPLICATES = "duplicates.txt"; + private static final String OVERVIEW = "overview.txt"; + private static final String RECIPES_AFTER = "recipes_after.txt"; + private static final String RECIPES_BEFORE = "recipes_before.txt"; + private static final String TAGS = "tags.txt"; + private static final String UNIFICATION = "unification.txt"; + + private final DebugConfig config; + private final String lastRun; + private final int recipesBefore; + + private long startTime; + private long endTime; + @Nullable private RecipeTransformer.Result transformerResult; + + private DebugHandler(int recipesBefore) { + this.config = Config.load(DebugConfig.NAME, DebugConfig.SERIALIZER); + this.lastRun = "# Last run: " + DATE_FORMAT.format(new Date(System.currentTimeMillis())); + this.recipesBefore = recipesBefore; + } + + public static DebugHandler onRunStart(Map recipes, UnificationLookup unificationLookup) { + DebugHandler handler = new DebugHandler(recipes.size()); + handler.dumpTags(unificationLookup); + handler.dumpRecipes(RECIPES_BEFORE, recipes); + return handler; + } + + public void measure(Supplier transformerSupplier) { + startTime = System.currentTimeMillis(); + transformerResult = transformerSupplier.get(); + endTime = System.currentTimeMillis(); + } + + public void onRunEnd(Map recipes) { + Preconditions.checkArgument(startTime > 0, "startTime not set"); + Preconditions.checkArgument(endTime > 0, "endTime not set"); + Preconditions.checkNotNull(transformerResult, "transformerResult not set"); + + dumpRecipes(RECIPES_AFTER, recipes); + dumpOverview(recipes.size()); + dumpUnification(); + dumpDuplicates(); + } + + private void dumpTags(UnificationLookup unificationLookup) { + if (!config.shouldDumpTags()) return; + + int maxLength = getMaxLength(unificationLookup.getTags(), t -> t.location().toString().length()); + + FileUtils.writeDebugLog(TAGS, sb -> sb + .append(lastRun).append("\n") + .append(unificationLookup + .getTags() + .stream() + .map(t -> rf(t.location(), maxLength) + " => " + + unificationLookup + .getTagEntries(t) + .stream() + .map(entry -> entry.id().toString()) + .sorted() + .collect(Collectors.joining(", ")) + "\n" + ) + .sorted() + .collect(Collectors.joining()) + )); + } + + private void dumpRecipes(String fileName, Map recipes) { + if (!config.shouldDumpRecipes()) return; + + int maxLength = getMaxLength(recipes.keySet(), id -> id.toString().length()); + + FileUtils.writeDebugLog(fileName, sb -> sb + .append(lastRun).append("\n") + .append(recipes + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .map(e -> rf(e.getKey(), maxLength) + " => " + e.getValue().toString()) + .collect(Collectors.joining("\n"))) + ); + } + + private void dumpOverview(int recipesAfter) { + if (!config.shouldDumpOverview()) return; + assert transformerResult != null; + + int maxLength = getMaxLength(transformerResult.getUnifiedRecipeTypes(), t -> t.toString().length()); + + FileUtils.writeDebugLog(OVERVIEW, sb -> { + sb + .append(lastRun).append("\n") + .append("# Statistics:\n") + .append("- Unified Recipes: ") + .append(transformerResult.getUnifiedRecipeCount()) + .append("\n") + .append("- Duplicate Recipes: ") + .append(transformerResult.getDuplicatesCount()) + .append(" (Individual: ") + .append(transformerResult.getDuplicateRecipesCount()) + .append(")\n") + .append("- Recipes Before: ") + .append(recipesBefore) + .append("\n") + .append("- Recipes After: ") + .append(recipesAfter) + .append("\n") + .append("- Elapsed Time: ") + .append(endTime - startTime) + .append(" ms") + .append("\n\n") + .append("# Summary:\n") + .append(rf("Recipe type", maxLength)) + .append(" | ") + .append(lf("Unified", 10)) + .append(" | ") + .append(lf("Duplicates", 10)) + .append(" | ") + .append(lf("All", 5)) + .append("\n") + .append(StringUtils.repeat("-", maxLength + 10 + 10 + 5 + 9)) + .append("\n"); + + getSortedUnifiedRecipeTypes().forEach(type -> { + int unifiedSize = transformerResult.getUnifiedRecipes(type).size(); + int allSize = transformerResult.getRecipes(type).size(); + int duplicatesSize = transformerResult.getDuplicates(type).size(); + int individualDuplicatesSize = transformerResult + .getDuplicates(type) + .stream() + .mapToInt(l -> l.getRecipes().size()) + .sum(); + + String dStr = String.format("%s (%s)", lf(duplicatesSize, 3), lf(individualDuplicatesSize, 3)); + sb + .append(rf(type, maxLength)) + .append(" | ") + .append(lf(unifiedSize, 10)) + .append(" | ") + .append(lf(duplicatesSize == 0 ? " " : dStr, 10)) + .append(" | ") + .append(lf(allSize, 5)) + .append("\n"); + }); + }); + } + + private void dumpUnification() { + if (!config.shouldDumpUnification()) return; + + FileUtils.writeDebugLog(UNIFICATION, sb -> { + sb.append(lastRun).append("\n"); + getSortedUnifiedRecipeTypes().forEach(type -> { + sb.append(type.toString()).append(" {\n"); + + getSortedUnifiedRecipes(type).forEach(recipe -> { + sb + .append("\t- ") + .append(recipe.getId()) + .append("\n") + .append("\t\t Original: ") + .append(recipe.getOriginal()) + .append("\n") + .append("\t\t Transformed: ") + .append(recipe.getUnified() == null ? "NOT UNIFIED" : recipe.getUnified().toString()) + .append("\n\n"); + }); + + sb.append("}\n\n"); + }); + }); + } + + private void dumpDuplicates() { + if (!config.shouldDumpDuplicates()) return; + assert transformerResult != null; + + FileUtils.writeDebugLog(DUPLICATES, sb -> { + sb.append(lastRun).append("\n"); + getSortedUnifiedRecipeTypes().forEach(type -> { + Collection duplicates = transformerResult + .getDuplicates(type) + .stream() + .sorted(Comparator.comparing(l -> l.getMaster().getId().toString())) + .toList(); + if (duplicates.isEmpty()) return; + + sb.append(duplicates + .stream() + .map(this::createDuplicatesDump) + .collect(Collectors.joining("", type + " {\n", "}\n\n"))); + }); + }); + } + + private String createDuplicatesDump(RecipeLink.DuplicateLink link) { + return link + .getRecipes() + .stream() + .sorted(Comparator.comparing(r -> r.getId().toString())) + .map(r -> "\t\t- " + r.getId() + "\n") + .collect(Collectors.joining("", String.format("\t%s\n", link.getMaster().getId().toString()), "\n")); + } + + private static int getMaxLength(Collection collection, ToIntFunction function) { + return collection.stream().mapToInt(function).max().orElse(0); + } + + private static String rf(Object v, int size) { + return StringUtils.rightPad(v.toString(), size); + } + + private String lf(Object v, int size) { + return StringUtils.leftPad(v.toString(), size); + } + + private Stream getSortedUnifiedRecipeTypes() { + Preconditions.checkNotNull(transformerResult); + return transformerResult + .getUnifiedRecipeTypes() + .stream() + .sorted(Comparator.comparing(ResourceLocation::toString)); + } + + private Stream getSortedUnifiedRecipes(ResourceLocation type) { + Preconditions.checkNotNull(transformerResult); + return transformerResult + .getUnifiedRecipes(type) + .stream() + .sorted(Comparator.comparing(r -> r.getId().toString())); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/FileUtils.java b/Common/src/main/java/com/almostreliable/unified/utils/FileUtils.java index 1720eb8..24ba138 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/FileUtils.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/FileUtils.java @@ -1,6 +1,8 @@ package com.almostreliable.unified.utils; -import com.almostreliable.unified.AlmostUnified; +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.AlmostUnifiedPlatform; +import com.almostreliable.unified.config.DebugConfig; import java.io.IOException; import java.nio.file.Files; @@ -12,21 +14,33 @@ public final class FileUtils { private FileUtils() {} - public static void write(Path path, String fileName, Consumer callback) { + public static void createGitIgnore() { + Path path = AlmostUnifiedPlatform.INSTANCE.getConfigPath(); + if (!Files.exists(path.resolve(".gitignore"))) { + write(path, ".gitignore", sb -> sb.append(DebugConfig.NAME).append(".json").append("\n")); + } + } + + public static void writeDebugLog(String fileName, Consumer callback) { + write(AlmostUnifiedPlatform.INSTANCE.getDebugLogPath(), fileName, callback); + } + + private static void write(Path path, String fileName, Consumer callback) { StringBuilder sb = new StringBuilder(); callback.accept(sb); try { Files.createDirectories(path); Path filePath = path.resolve(fileName); - Files.writeString(filePath, + Files.writeString( + filePath, sb.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, - StandardOpenOption.WRITE); + StandardOpenOption.WRITE + ); } catch (IOException e) { - AlmostUnified.LOG.warn("Dump couldn't be written '{}': {}", fileName, e.getMessage()); - e.printStackTrace(); + AlmostUnifiedCommon.LOGGER.warn("Couldn't write to file '{}'.", fileName, e); } } } diff --git a/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java b/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java index 3c1349a..ce78c17 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/JsonCompare.java @@ -122,7 +122,8 @@ public final class JsonCompare { *

* Conditions are both elements being a JSON array with the same size, both elements being * a JSON object, one element being a JSON object and the other being a JSON primitive. - * @param firstElem the first element + * + * @param firstElem the first element * @param secondElem the second element * @return true if the elements need to be sanitized, false otherwise */ @@ -139,7 +140,8 @@ public final class JsonCompare { * value from the original object under a dummy key called "au_sanitized". *

* If the element is not a string primitive, the default object is returned. - * @param value The value to sanitize + * + * @param value The value to sanitize * @param defaultValue The default value to return if the element is not a string primitive * @return The sanitized object or the default value */ @@ -161,6 +163,7 @@ public final class JsonCompare { * the original recipe, so it can be safely used for comparison. *

* If the object doesn't support this transformation, the original object is returned. + * * @param element The element to sanitize * @return The sanitized element or the original element if it can't be sanitized */ @@ -190,7 +193,7 @@ public final class JsonCompare { var sanitized = createSanitizedObjectOrDefault(jsonObject.get(key), jsonObject); // ensure the object changed (was sanitized) and that we got a JsonObject - //noinspection ObjectEquality + // noinspection ObjectEquality if (sanitized == jsonObject || !(sanitized instanceof JsonObject sanitizedObject)) { return jsonObject; } @@ -204,7 +207,8 @@ public final class JsonCompare { /** * Merges remaining properties from the original object to the sanitized object. - * @param jsonObject The original object + * + * @param jsonObject The original object * @param sanitizedObject The sanitized object */ private static void mergeRemainingProperties(JsonObject jsonObject, JsonObject sanitizedObject) { @@ -258,9 +262,9 @@ public final class JsonCompare { } public static class CompareSettings { - public static final String IGNORED_FIELDS = "ignoredFields"; + public static final String IGNORED_FIELDS = "ignored_fields"; public static final String RULES = "rules"; - public static final String SHOULD_SANITIZE = "shouldSanitize"; + public static final String SHOULD_SANITIZE = "should_sanitize"; private final LinkedHashMap rules = new LinkedHashMap<>(); private final Set ignoredFields = new HashSet<>(); @@ -274,7 +278,7 @@ public final class JsonCompare { Rule old = rules.put(key, rule); ignoreField(key); if (old != null) { - throw new IllegalStateException("Multiple rule for key <" + key + "> found"); + throw new IllegalStateException("multiple rules for key <" + key + "> found"); } } @@ -313,7 +317,7 @@ public final class JsonCompare { Rule r = switch (e.getValue().getAsString()) { case HigherRule.NAME -> new HigherRule(); case LowerRule.NAME -> new LowerRule(); - default -> throw new IllegalArgumentException("Unknown rule <" + e.getValue().getAsString() + ">"); + default -> throw new IllegalArgumentException("unknown rule <" + e.getValue().getAsString() + ">"); }; addRule(e.getKey(), r); }); diff --git a/Common/src/main/java/com/almostreliable/unified/utils/JsonUtils.java b/Common/src/main/java/com/almostreliable/unified/utils/JsonUtils.java index d1b7e50..c8f62a3 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/JsonUtils.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/JsonUtils.java @@ -1,11 +1,12 @@ package com.almostreliable.unified.utils; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import com.google.gson.*; import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,8 +19,27 @@ import java.util.stream.StreamSupport; public final class JsonUtils { + private static final Gson GSON = new Gson(); + private JsonUtils() {} + public static T readFromFile(Path path, Class clazz) throws IOException { + BufferedReader reader = Files.newBufferedReader(path); + return GSON.fromJson(reader, clazz); + } + + public static T safeReadFromFile(Path path, T defaultValue) { + try { + return readFromFile(path, Utils.cast(defaultValue.getClass())); + } catch (Exception e) { + return defaultValue; + } + } + + public static T readFromString(String jsonString, Class clazz) { + return GSON.fromJson(jsonString, clazz); + } + public static JsonArray arrayOrSelf(@Nullable JsonElement element) { if (element == null) { return new JsonArray(); @@ -98,7 +118,7 @@ public final class JsonUtils { return false; } - public static JsonArray toArray(List list) { + public static JsonArray toArray(Iterable list) { JsonArray array = new JsonArray(); list.forEach(array::add); return array; diff --git a/Common/src/main/java/com/almostreliable/unified/utils/RecipeTypePropertiesLogger.java b/Common/src/main/java/com/almostreliable/unified/utils/RecipeTypePropertiesLogger.java index 59eedf3..dd7ecdb 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/RecipeTypePropertiesLogger.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/RecipeTypePropertiesLogger.java @@ -1,11 +1,12 @@ package com.almostreliable.unified.utils; -import com.almostreliable.unified.AlmostUnifiedPlatform; import com.google.gson.JsonObject; import net.minecraft.resources.ResourceLocation; -import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class RecipeTypePropertiesLogger { private final Map> properties = new HashMap<>(); @@ -14,7 +15,7 @@ public class RecipeTypePropertiesLogger { return properties.computeIfAbsent(mod, $ -> new ArrayList<>()); } - public void log(ResourceLocation recipeType, JsonObject recipe, Collection keys) { + public void log(ResourceLocation recipeType, JsonObject recipe) { String mod = recipeType.getNamespace(); recipe.entrySet().forEach(e -> getProperties(mod).add(e.getKey())); } @@ -31,7 +32,6 @@ public class RecipeTypePropertiesLogger { props.forEach(property -> sb.append(" ").append(property).append("\n")); }); - Path path = AlmostUnifiedPlatform.INSTANCE.getLogPath(); - FileUtils.write(path, "debug_recipe_properties.txt", stringBuilder -> sb.append(sb)); + FileUtils.writeDebugLog("debug_recipe_properties.txt", stringBuilder -> sb.append(sb)); } } diff --git a/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java b/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java deleted file mode 100644 index 9b9e6a7..0000000 --- a/Common/src/main/java/com/almostreliable/unified/utils/ReplacementMap.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.almostreliable.unified.utils; - -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.api.StoneStrataHandler; -import com.almostreliable.unified.config.UnifyConfig; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Ingredient; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.Predicate; - -public class ReplacementMap { - - private final UnifyConfig unifyConfig; - private final TagMap tagMap; - private final StoneStrataHandler stoneStrataHandler; - private final TagOwnerships tagOwnerships; - private final Set warnings; - - public ReplacementMap(UnifyConfig unifyConfig, TagMap tagMap, StoneStrataHandler stoneStrataHandler, TagOwnerships tagOwnerships) { - this.tagMap = tagMap; - this.unifyConfig = unifyConfig; - this.stoneStrataHandler = stoneStrataHandler; - this.tagOwnerships = tagOwnerships; - this.warnings = new HashSet<>(); - } - - @Nullable - public UnifyTag getPreferredTagForItem(ResourceLocation item) { - Collection> tags = tagMap.getTagsByEntry(item); - - if (tags.isEmpty()) { - return null; - } - - if (tags.size() > 1 && !warnings.contains(item)) { - AlmostUnified.LOG.warn( - "Item '{}' has multiple preferred tags '{}' for recipe replacement. This needs to be manually fixed by the user.", - item, - tags.stream().map(UnifyTag::location).toList() - ); - warnings.add(item); - } - - return tags.iterator().next(); - } - - @Nullable - public ResourceLocation getReplacementForItem(ResourceLocation item) { - UnifyTag t = getPreferredTagForItem(item); - if (t == null) { - return null; - } - - if (stoneStrataHandler.isStoneStrataTag(t)) { - String stone = stoneStrataHandler.getStoneStrata(item); - return getPreferredItemForTag(t, i -> stone.equals(stoneStrataHandler.getStoneStrata(i))); - } - - return getPreferredItemForTag(t, i -> true); - } - - @Nullable - public ResourceLocation getPreferredItemForTag(UnifyTag tag, Predicate itemFilter) { - var tagToLookup = tagOwnerships.getOwnerByTag(tag); - if (tagToLookup == null) tagToLookup = tag; - - List items = tagMap - .getEntriesByTag(tagToLookup) - .stream() - .filter(itemFilter) - // Helps us to get the clean stone variant first in case of a stone strata tag - .sorted(Comparator.comparingInt(value -> value.toString().length())) - .toList(); - - if (items.isEmpty()) return null; - - ResourceLocation overrideItem = getOverrideForTag(tagToLookup, items); - if (overrideItem != null) { - return overrideItem; - } - - for (String modPriority : unifyConfig.getModPriorities()) { - ResourceLocation item = findItemByNamespace(items, modPriority); - if (item != null) return item; - } - - return null; - } - - /** - * Gets all unify tags of the items within the given ingredient and checks - * whether the given item is in one of those tags. - * - * @param ingred The ingredient to get the unify tags from. - * @param item The item to check. - * @return Whether the item is in one of the unify tags of the ingredient. - */ - public boolean isItemInUnifiedIngredient(Ingredient ingred, ItemStack item) { - Set> checkedTags = new HashSet<>(); - - for (ItemStack ingredItem : ingred.getItems()) { - ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(ingredItem.getItem()); - - var preferredTag = getPreferredTagForItem(itemId); - if (preferredTag == null || checkedTags.contains(preferredTag)) continue; - checkedTags.add(preferredTag); - - var preferredTagKey = TagKey.create(Registries.ITEM, preferredTag.location()); - if (item.is(preferredTagKey)) { - return true; - } - } - - return false; - } - - @Nullable - private ResourceLocation getOverrideForTag(UnifyTag tag, List items) { - String priorityOverride = unifyConfig.getPriorityOverrides().get(tag.location()); - if (priorityOverride != null) { - ResourceLocation item = findItemByNamespace(items, priorityOverride); - if (item != null) return item; - AlmostUnified.LOG.warn( - "Priority override mod '{}' for tag '{}' does not contain a valid item. Falling back to default priority.", - priorityOverride, - tag.location()); - } - return null; - } - - @Nullable - private ResourceLocation findItemByNamespace(List items, String namespace) { - for (ResourceLocation item : items) { - if (item.getNamespace().equals(namespace)) { - return item; - } - } - return null; - } - - public TagOwnerships getTagOwnerships() { - return tagOwnerships; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java b/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java deleted file mode 100644 index 2fcf5a1..0000000 --- a/Common/src/main/java/com/almostreliable/unified/utils/TagMap.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.almostreliable.unified.utils; - -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.tags.TagLoader; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.Block; - -import java.util.*; -import java.util.function.Predicate; - -public class TagMap { - - private final Map, Set> tagsToEntries = new HashMap<>(); - private final Map>> entriesToTags = new HashMap<>(); - - protected TagMap() {} - - /** - * Creates an item tag map from a set of item unify tags. - *

- * This should only be used for client-side tag maps or for tests.
- * It requires the registry to be loaded in order to validate the tags - * and fetch the holder from it. - *

- * For the server, use {@link #createFromItemTags(Map)} instead. - * - * @param unifyTags The unify tags. - * @return A new tag map. - */ - public static TagMap create(Set> unifyTags) { - TagMap tagMap = new TagMap<>(); - - unifyTags.forEach(ut -> { - TagKey asTagKey = TagKey.create(Registries.ITEM, ut.location()); - BuiltInRegistries.ITEM.getTagOrEmpty(asTagKey).forEach(holder -> { - ResourceLocation key = BuiltInRegistries.ITEM.getKey(holder.value()); - tagMap.put(ut, key); - }); - }); - - return tagMap; - } - - /** - * Creates an item tag map from the vanilla item tag collection passed by the {@link TagLoader}. - *

- * This should only be used on the server.
- * This tag map should later be filtered by using {@link #filtered(Predicate, Predicate)}. - *

- * For the client, use {@link #create(Set)} instead. - * - * @param tags The vanilla item tag collection. - * @return A new item tag map. - */ - public static TagMap createFromItemTags(Map>> tags) { - TagMap tagMap = new TagMap<>(); - - for (var entry : tags.entrySet()) { - UnifyTag unifyTag = UnifyTag.item(entry.getKey()); - fillEntries(tagMap, entry.getValue(), unifyTag, BuiltInRegistries.ITEM); - } - - return tagMap; - } - - /** - * Creates a block tag map from the vanilla block tag collection passed by the {@link TagLoader}. - *

- * This should only be used on the server. - * - * @param tags The vanilla block tag collection. - * @return A new block tag map. - */ - public static TagMap createFromBlockTags(Map>> tags) { - TagMap tagMap = new TagMap<>(); - - for (var entry : tags.entrySet()) { - UnifyTag unifyTag = UnifyTag.block(entry.getKey()); - fillEntries(tagMap, entry.getValue(), unifyTag, BuiltInRegistries.BLOCK); - } - - return tagMap; - } - - /** - * Unwrap all holders, verify them and put them into the tag map. - * - * @param tagMap The tag map to fill. - * @param holders The holders to unwrap. - * @param unifyTag The unify tag to use. - * @param registry The registry to use. - */ - private static void fillEntries(TagMap tagMap, Collection> holders, UnifyTag unifyTag, Registry registry) { - for (var holder : holders) { - holder - .unwrapKey() - .map(ResourceKey::location) - .filter(registry::containsKey) - .ifPresent(id -> tagMap.put(unifyTag, id)); - } - } - - /** - * Creates a filtered tag map copy. - * - * @param tagFilter A filter to determine which tags to include. - * @param entryFilter A filter to determine which entries to include. - * @return A filtered copy of this tag map. - */ - public TagMap filtered(Predicate> tagFilter, Predicate entryFilter) { - TagMap tagMap = new TagMap<>(); - - tagsToEntries.forEach((tag, items) -> { - if (!tagFilter.test(tag)) { - return; - } - items.stream().filter(entryFilter).forEach(item -> tagMap.put(tag, item)); - }); - - return tagMap; - } - - public int tagSize() { - return tagsToEntries.size(); - } - - public int itemSize() { - return entriesToTags.size(); - } - - public Set getEntriesByTag(UnifyTag tag) { - return Collections.unmodifiableSet(tagsToEntries.getOrDefault(tag, Collections.emptySet())); - } - - public Set> getTagsByEntry(ResourceLocation entry) { - return Collections.unmodifiableSet(entriesToTags.getOrDefault(entry, Collections.emptySet())); - } - - public Set> getTags() { - return Collections.unmodifiableSet(tagsToEntries.keySet()); - } - - /** - * Helper function to build a relationship between a tag and an entry. - *

- * If the entries don't exist in the internal maps yet, they will be created. That means - * it needs to be checked whether the tag or entry is valid before calling this method. - * - * @param tag The tag. - * @param entry The entry. - */ - protected void put(UnifyTag tag, ResourceLocation entry) { - tagsToEntries.computeIfAbsent(tag, k -> new HashSet<>()).add(entry); - entriesToTags.computeIfAbsent(entry, k -> new HashSet<>()).add(tag); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java b/Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java deleted file mode 100644 index bc72299..0000000 --- a/Common/src/main/java/com/almostreliable/unified/utils/TagOwnerships.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.almostreliable.unified.utils; - -import com.almostreliable.unified.AlmostUnified; -import com.google.common.collect.*; -import net.minecraft.core.Holder; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -public class TagOwnerships { - - /** - * A map holding relationships between reference tags and their owner tags. - *

- * Example:
- * If the map contains the entry {@code minecraft:logs -> minecraft:planks}, - * any recipes where the tag {@code minecraft:logs} is being used, it will - * replace the tag with {@code minecraft:planks}. - *

- * Map Key = Tag to replace
- * Map Value = Tag to replace with - */ - private final Map, UnifyTag> refsToOwner; - private final Multimap, UnifyTag> ownerToRefs; - - /** - * Creates a new TagOwnerships instance that contains immutable maps of all tag ownership relationships. - *

- * It is ensured that all owner tags are present in the {@code unifyTags} set, and that all reference tags - * aren't present in the {@code unifyTags} set. - * - * @param unifyTags The set of all unify tags in use. - * @param tagOwnershipConfig The map of all tag ownership relationships. - */ - public TagOwnerships(Set> unifyTags, Map> tagOwnershipConfig) { - ImmutableMap.Builder, UnifyTag> refsToOwnerBuilder = ImmutableMap.builder(); - ImmutableMultimap.Builder, UnifyTag> ownerToRefsBuilder = ImmutableMultimap.builder(); - - tagOwnershipConfig.forEach((rawOwner, rawRefs) -> { - for (ResourceLocation rawRef : rawRefs) { - UnifyTag owner = UnifyTag.item(rawOwner); - UnifyTag ref = UnifyTag.item(rawRef); - - if (!unifyTags.contains(owner)) { - AlmostUnified.LOG.warn( - "[TagOwnerships] Owner tag '#{}' is not present in the unify tag list!", - owner.location() - ); - continue; - } - - if (unifyTags.contains(ref)) { - AlmostUnified.LOG.warn( - "[TagOwnerships] Reference tag '#{}' of owner tag '#{}' is present in the unify tag list!", - ref.location(), - owner.location() - ); - continue; - } - - refsToOwnerBuilder.put(ref, owner); - ownerToRefsBuilder.put(owner, ref); - } - }); - - this.refsToOwner = refsToOwnerBuilder.build(); - this.ownerToRefs = ownerToRefsBuilder.build(); - } - - /** - * Applies tag ownerships to the provided raw tags. - *

- * The raw tags are then processed by the game and actual tags are created. - * - * @param rawTags The raw tags to apply ownerships to. - */ - public void applyOwnerships(Map>> rawTags) { - Multimap changedTags = HashMultimap.create(); - - ownerToRefs.asMap().forEach((owner, refs) -> { - var rawHolders = rawTags.get(owner.location()); - if (rawHolders == null) { - AlmostUnified.LOG.warn("[TagOwnerships] Owner tag '#{}' does not exist!", owner.location()); - return; - } - - ImmutableSet.Builder> holders = ImmutableSet.builder(); - holders.addAll(rawHolders); - boolean changed = false; - - for (UnifyTag ref : refs) { - var refHolders = rawTags.get(ref.location()); - if (refHolders == null) { - AlmostUnified.LOG.warn( - "[TagOwnerships] Reference tag '#{}' of owner tag '#{}' does not exist!", - ref.location(), - owner.location() - ); - continue; - } - - for (Holder holder : refHolders) { - holders.add(holder); - holder.unwrapKey().ifPresent(key -> changedTags.put(owner.location(), key.location())); - changed = true; - } - } - - if (changed) { - rawTags.put(owner.location(), holders.build()); - } - }); - - if (!changedTags.isEmpty()) { - changedTags.asMap().forEach((tag, items) -> { - AlmostUnified.LOG.info("[TagOwnerships] Modified tag '#{}', added {}", tag, items); - }); - } - } - - /** - * Gets the owner tag for the provided reference tag. - * - * @param tag The reference tag to get the owner for. - * @return The owner tag, or null if the provided tag is not a reference tag. - */ - @Nullable - public UnifyTag getOwnerByTag(UnifyTag tag) { - return refsToOwner.get(tag); - } - - /** - * Gets all reference tags for all owner tags. - * - * @return A set of all reference tags. - */ - public Set> getRefs() { - return refsToOwner.keySet(); - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/TagReloadHandler.java b/Common/src/main/java/com/almostreliable/unified/utils/TagReloadHandler.java deleted file mode 100644 index 0785894..0000000 --- a/Common/src/main/java/com/almostreliable/unified/utils/TagReloadHandler.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.almostreliable.unified.utils; - -import com.almostreliable.unified.AlmostUnified; -import com.almostreliable.unified.ReplacementData; -import com.almostreliable.unified.config.UnifyConfig; -import com.google.common.base.Preconditions; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import net.minecraft.core.Holder; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.Block; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public final class TagReloadHandler { - - private static final Object LOCK = new Object(); - - private static Map>> RAW_ITEM_TAGS; - private static Map>> RAW_BLOCK_TAGS; - - private TagReloadHandler() {} - - public static void initItemTags(Map>> rawItemTags) { - synchronized (LOCK) { - RAW_ITEM_TAGS = rawItemTags; - } - } - - public static void initBlockTags(Map>> rawBlockTags) { - synchronized (LOCK) { - RAW_BLOCK_TAGS = rawBlockTags; - } - } - - public static void run() { - if (RAW_ITEM_TAGS == null || RAW_BLOCK_TAGS == null) { - return; - } - - AlmostUnified.onTagLoaderReload(RAW_ITEM_TAGS); - - RAW_ITEM_TAGS = null; - RAW_BLOCK_TAGS = null; - } - - public static void applyCustomTags(UnifyConfig unifyConfig) { - Preconditions.checkNotNull(RAW_ITEM_TAGS, "Item tags were not loaded correctly"); - - Multimap changedItemTags = HashMultimap.create(); - - for (var entry : unifyConfig.getCustomTags().entrySet()) { - ResourceLocation tag = entry.getKey(); - Set itemIds = entry.getValue(); - - for (ResourceLocation itemId : itemIds) { - if (!BuiltInRegistries.ITEM.containsKey(itemId)) { - AlmostUnified.LOG.warn("[CustomTags] Custom tag '{}' contains invalid item '{}'", tag, itemId); - continue; - } - - ResourceKey itemKey = ResourceKey.create(Registries.ITEM, itemId); - Holder itemHolder = BuiltInRegistries.ITEM.getHolder(itemKey).orElse(null); - if (itemHolder == null) continue; - - ImmutableSet.Builder> newHolders = ImmutableSet.builder(); - var currentHolders = RAW_ITEM_TAGS.get(tag); - - if (currentHolders != null) { - if (currentHolders.contains(itemHolder)) { - AlmostUnified.LOG.warn("[CustomTags] Custom tag '{}' already contains item '{}'", tag, itemId); - continue; - } - - newHolders.addAll(currentHolders); - } - newHolders.add(itemHolder); - - RAW_ITEM_TAGS.put(tag, newHolders.build()); - changedItemTags.put(tag, itemId); - } - } - - if (!changedItemTags.isEmpty()) { - changedItemTags.asMap().forEach((tag, items) -> { - AlmostUnified.LOG.info("[CustomTags] Modified tag '#{}', added {}", tag, items); - }); - } - } - - public static boolean applyInheritance(UnifyConfig unifyConfig, ReplacementData replacementData) { - Preconditions.checkNotNull(RAW_ITEM_TAGS, "Item tags were not loaded correctly"); - Preconditions.checkNotNull(RAW_BLOCK_TAGS, "Block tags were not loaded correctly"); - - Multimap changedItemTags = HashMultimap.create(); - Multimap changedBlockTags = HashMultimap.create(); - - var relations = resolveRelations(replacementData.filteredTagMap(), replacementData.replacementMap()); - if (relations.isEmpty()) return false; - - var blockTagMap = TagMap.createFromBlockTags(RAW_BLOCK_TAGS); - var globalTagMap = replacementData.globalTagMap(); - - for (TagRelation relation : relations) { - var dominant = relation.dominant; - var dominantItemHolder = findDominantItemHolder(relation); - var dominantBlockHolder = findDominantBlockHolder(blockTagMap, dominant); - - var dominantItemTags = globalTagMap.getTagsByEntry(dominant); - - for (var item : relation.items) { - if (dominantItemHolder != null) { - var changed = applyItemTags(unifyConfig, globalTagMap, dominantItemHolder, dominantItemTags, item); - changedItemTags.putAll(dominant, changed); - } - - if (dominantBlockHolder != null) { - var changed = applyBlockTags(unifyConfig, blockTagMap, dominantBlockHolder, dominantItemTags, item); - changedBlockTags.putAll(dominant, changed); - } - } - } - - if (!changedBlockTags.isEmpty()) { - changedBlockTags.asMap().forEach((dominant, tags) -> { - AlmostUnified.LOG.info("[TagInheritance] Added '{}' to block tags {}", dominant, tags); - }); - } - - if (!changedItemTags.isEmpty()) { - changedItemTags.asMap().forEach((dominant, tags) -> { - AlmostUnified.LOG.info("[TagInheritance] Added '{}' to item tags {}", dominant, tags); - }); - return true; - } - - return false; - } - - private static Set resolveRelations(TagMap filteredTagMap, ReplacementMap repMap) { - Set relations = new HashSet<>(); - - for (var unifyTag : filteredTagMap.getTags()) { - var itemsByTag = filteredTagMap.getEntriesByTag(unifyTag); - - // avoid handling single entries and tags that only contain the same namespace for all items - if (Utils.allSameNamespace(itemsByTag)) continue; - - ResourceLocation dominant = repMap.getPreferredItemForTag(unifyTag, $ -> true); - if (dominant == null || !BuiltInRegistries.ITEM.containsKey(dominant)) continue; - - Set items = getValidatedItems(itemsByTag, dominant); - - if (items.isEmpty()) continue; - relations.add(new TagRelation(unifyTag.location(), dominant, items)); - } - - return relations; - } - - /** - * Returns a set of all items that are not the dominant item and are valid by checking if they are registered. - * - * @param itemIds The set of all items that are in the tag - * @param dominant The dominant item - * @return A set of all items that are not the dominant item and are valid - */ - private static Set getValidatedItems(Set itemIds, ResourceLocation dominant) { - Set result = new HashSet<>(itemIds.size()); - for (ResourceLocation id : itemIds) { - if (!id.equals(dominant) && BuiltInRegistries.ITEM.containsKey(id)) { - result.add(id); - } - } - - return result; - } - - @SuppressWarnings("StaticVariableUsedBeforeInitialization") - @Nullable - private static Holder findDominantItemHolder(TagRelation relation) { - var tagHolders = RAW_ITEM_TAGS.get(relation.tag); - if (tagHolders == null) return null; - - return findDominantHolder(tagHolders, relation.dominant); - } - - @SuppressWarnings("StaticVariableUsedBeforeInitialization") - @Nullable - private static Holder findDominantBlockHolder(TagMap tagMap, ResourceLocation dominant) { - var blockTags = tagMap.getTagsByEntry(dominant); - if (blockTags.isEmpty()) return null; - - var tagHolders = RAW_BLOCK_TAGS.get(blockTags.iterator().next().location()); - if (tagHolders == null) return null; - - return findDominantHolder(tagHolders, dominant); - } - - @Nullable - private static Holder findDominantHolder(Collection> holders, ResourceLocation dominant) { - for (var tagHolder : holders) { - var holderKey = tagHolder.unwrapKey(); - if (holderKey.isPresent() && holderKey.get().location().equals(dominant)) { - return tagHolder; - } - } - - return null; - } - - private static Set applyItemTags(UnifyConfig unifyConfig, TagMap globalTagMap, Holder dominantItemHolder, Set> dominantItemTags, ResourceLocation item) { - var itemTags = globalTagMap.getTagsByEntry(item); - Set changed = new HashSet<>(); - - for (var itemTag : itemTags) { - if (!unifyConfig.shouldInheritItemTag(itemTag, dominantItemTags)) continue; - if (tryUpdatingRawTags(dominantItemHolder, itemTag, RAW_ITEM_TAGS)) { - changed.add(itemTag.location()); - } - } - - return changed; - } - - private static Set applyBlockTags(UnifyConfig unifyConfig, TagMap blockTagMap, Holder dominantBlockHolder, Set> dominantItemTags, ResourceLocation item) { - var blockTags = blockTagMap.getTagsByEntry(item); - Set changed = new HashSet<>(); - - for (var blockTag : blockTags) { - if (!unifyConfig.shouldInheritBlockTag(blockTag, dominantItemTags)) continue; - if (tryUpdatingRawTags(dominantBlockHolder, blockTag, RAW_BLOCK_TAGS)) { - changed.add(blockTag.location()); - } - } - - return changed; - } - - private static boolean tryUpdatingRawTags(Holder dominantHolder, UnifyTag tag, Map>> rawTags) { - var tagHolders = rawTags.get(tag.location()); - if (tagHolders == null) return false; - if (tagHolders.contains(dominantHolder)) return false; // already present, no need to add it again - - ImmutableSet.Builder> newHolders = ImmutableSet.builder(); - newHolders.addAll(tagHolders); - newHolders.add(dominantHolder); - - rawTags.put(tag.location(), newHolders.build()); - return true; - } - - private record TagRelation(ResourceLocation tag, ResourceLocation dominant, Set items) {} -} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/UnifyTag.java b/Common/src/main/java/com/almostreliable/unified/utils/UnifyTag.java deleted file mode 100644 index 3d1d5b5..0000000 --- a/Common/src/main/java/com/almostreliable/unified/utils/UnifyTag.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.almostreliable.unified.utils; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.Block; - -public record UnifyTag(Class boundType, ResourceLocation location) { - public static UnifyTag item(ResourceLocation location) { - return new UnifyTag<>(Item.class, location); - } - - public static UnifyTag block(ResourceLocation location) { - return new UnifyTag<>(Block.class, location); - } - - @Override - public String toString() { - return "UnifyTag[" + boundType.getSimpleName().toLowerCase() + " / " + location + "]"; - } -} diff --git a/Common/src/main/java/com/almostreliable/unified/utils/Utils.java b/Common/src/main/java/com/almostreliable/unified/utils/Utils.java index be17675..f40a977 100644 --- a/Common/src/main/java/com/almostreliable/unified/utils/Utils.java +++ b/Common/src/main/java/com/almostreliable/unified/utils/Utils.java @@ -1,42 +1,31 @@ package com.almostreliable.unified.utils; -import com.almostreliable.unified.BuildConfig; +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.unification.UnificationEntry; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; -import javax.annotation.Nullable; -import java.util.Set; +import java.util.Collection; public final class Utils { - public static final ResourceLocation UNUSED_ID = new ResourceLocation(BuildConfig.MOD_ID, "unused_id"); - public static final UnifyTag UNUSED_TAG = UnifyTag.item(UNUSED_ID); + public static final ResourceLocation UNUSED_ID = getRL("unused_id"); + public static final TagKey UNUSED_TAG = TagKey.create(Registries.ITEM, UNUSED_ID); private Utils() {} - public static UnifyTag toItemTag(@Nullable String tag) { - if (tag == null) { - return UNUSED_TAG; - } - - ResourceLocation rl = ResourceLocation.tryParse(tag); - if (rl == null) { - return UNUSED_TAG; - } - - return UnifyTag.item(rl); - } - @SuppressWarnings("unchecked") public static T cast(Object o) { return (T) o; } public static ResourceLocation getRL(String path) { - return new ResourceLocation(BuildConfig.MOD_ID, path); + return ResourceLocation.fromNamespaceAndPath(ModConstants.ALMOST_UNIFIED, path); } public static String prefix(String path) { - return BuildConfig.MOD_ID + "." + path; + return ModConstants.ALMOST_UNIFIED + "." + path; } /** @@ -45,14 +34,14 @@ public final class Utils { * @param ids set of ids * @return true if all ids have the same namespace */ - public static boolean allSameNamespace(Set ids) { + public static boolean allSameNamespace(Collection> ids) { if (ids.size() <= 1) return true; var it = ids.iterator(); - var namespace = it.next().getNamespace(); + var namespace = it.next().id().getNamespace(); while (it.hasNext()) { - if (!it.next().getNamespace().equals(namespace)) return false; + if (!it.next().id().getNamespace().equals(namespace)) return false; } return true; diff --git a/Common/src/main/java/com/almostreliable/unified/utils/VanillaTagWrapper.java b/Common/src/main/java/com/almostreliable/unified/utils/VanillaTagWrapper.java new file mode 100644 index 0000000..fe078e5 --- /dev/null +++ b/Common/src/main/java/com/almostreliable/unified/utils/VanillaTagWrapper.java @@ -0,0 +1,104 @@ +package com.almostreliable.unified.utils; + +import com.almostreliable.unified.api.unification.UnificationEntry; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; + +import javax.annotation.Nullable; +import java.util.*; + +/** + * Base wrapper to store vanilla tags and their holders. The wrapper allows to add new tags and holders to a tag. + * By default, the holder collection for each tag is immutable. When attempting to modify the collection it will be copied and marked as modified. + *

+ * After all operations are done, the vanilla tags should be sealed with {@link VanillaTagWrapper#seal()} to prevent further changes. + * + * @param + */ +public class VanillaTagWrapper { + + private final Registry registry; + private final Map>> vanillaTags; + @Nullable + private Map, Set> holdersToTags; + private final Set modifiedTags = new HashSet<>(); + + public static VanillaTagWrapper of(Registry registry, Map>> vanillaTags) { + return new VanillaTagWrapper<>(registry, vanillaTags); + } + + public VanillaTagWrapper(Registry registry, Map>> vanillaTags) { + this.registry = registry; + this.vanillaTags = vanillaTags; + } + + public void add(ResourceLocation tag, Holder holder) { + if (modifiedTags.contains(tag)) { + vanillaTags.get(tag).add(holder); + return; + } + + Collection> existingHolders = vanillaTags.get(tag); + Collection> newHolders = existingHolders == null ? new HashSet<>() : new HashSet<>(existingHolders); + newHolders.add(holder); + vanillaTags.put(tag, newHolders); + modifiedTags.add(tag); + } + + public boolean has(TagKey tag) { + return vanillaTags.containsKey(tag.location()); + } + + public Collection> get(TagKey tag) { + return Collections.unmodifiableCollection(vanillaTags.getOrDefault(tag.location(), Collections.emptyList())); + } + + public Collection> get(ResourceLocation tag) { + return Collections.unmodifiableCollection(vanillaTags.getOrDefault(tag, Collections.emptyList())); + } + + public Set getTags(ResourceLocation entryId) { + var key = ResourceKey.create(registry.key(), entryId); + return registry.getHolder(key).map(this::getTags).orElse(Set.of()); + } + + public Set getTags(UnificationEntry entry) { + return getTags(entry.asHolderOrThrow()); + } + + public Set getTags(Holder holder) { + if (holdersToTags == null) { + holdersToTags = createInvertMap(); + } + + return holdersToTags.getOrDefault(holder, Set.of()); + } + + private Map, Set> createInvertMap() { + Map, Set> map = new HashMap<>(); + + for (var entry : vanillaTags.entrySet()) { + for (Holder holder : entry.getValue()) { + map.putIfAbsent(holder, new HashSet<>()); + map.get(holder).add(entry.getKey()); + } + } + + return map; + } + + public void seal() { + for (ResourceLocation modifiedTag : modifiedTags) { + Collection> holders = vanillaTags.get(modifiedTag); + if (holders != null) { + vanillaTags.put(modifiedTag, List.copyOf(holders)); + } + } + + modifiedTags.clear(); + holdersToTags = null; + } +} diff --git a/Common/src/main/resources/almostunified-common.mixins.json b/Common/src/main/resources/almostunified-common.mixins.json index 7aa1a7c..e546e2a 100644 --- a/Common/src/main/resources/almostunified-common.mixins.json +++ b/Common/src/main/resources/almostunified-common.mixins.json @@ -2,12 +2,16 @@ "required": true, "minVersion": "0.8.5", "package": "com.almostreliable.unified.mixin", - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "mixins": [ + "loot.CompositeEntryBaseMixin", + "loot.LootItemMixin", + "loot.LootPoolMixin", + "loot.LootTableMixin", "runtime.RecipeManagerMixin", "runtime.TagLoaderMixin", - "unifier.ArmorItemMixin", - "unifier.TieredItemMixin" + "unification.ArmorItemMixin", + "unification.TieredItemMixin" ], "client": [ "runtime.ClientPacketListenerMixin" diff --git a/Common/src/main/resources/kubejs.bindings.txt b/Common/src/main/resources/kubejs.bindings.txt index fe3a346..5c67d51 100644 --- a/Common/src/main/resources/kubejs.bindings.txt +++ b/Common/src/main/resources/kubejs.bindings.txt @@ -1 +1 @@ -* AlmostUnified com.almostreliable.unified.compat.AlmostKube +* AlmostUnified com.almostreliable.unified.compat.kube.AlmostKube diff --git a/Common/src/main/resources/pack.mcmeta b/Common/src/main/resources/pack.mcmeta index 3abff23..f4ef945 100644 --- a/Common/src/main/resources/pack.mcmeta +++ b/Common/src/main/resources/pack.mcmeta @@ -1,6 +1,6 @@ { "pack": { "description": "${modName} resources", - "pack_format": 9 + "pack_format": 34 } } diff --git a/Common/src/test/java/com/almostreliable/unified/util/JsonCompareTest.java b/Common/src/test/java/com/almostreliable/unified/util/JsonCompareTest.java deleted file mode 100644 index 95c242a..0000000 --- a/Common/src/test/java/com/almostreliable/unified/util/JsonCompareTest.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.almostreliable.unified.util; - -import com.almostreliable.unified.TestUtils; -import com.almostreliable.unified.utils.JsonCompare; -import com.google.gson.JsonObject; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -public class JsonCompareTest { - - private static final String EXPERIENCE = "experience"; - private static final String COOKING_TIME = "cookingtime"; - - @Test - public void simpleCompareFirst() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.2)); - JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); // 0.1 experience - - Map rules = new LinkedHashMap<>(); - rules.put(EXPERIENCE, new JsonCompare.LowerRule()); - JsonObject result = JsonCompare.compare(rules, first, second); - assertEquals(second, result); - } - - @Test - public void simpleCompareSecond() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.05)); - JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); // 0.1 experience - - Map rules = new LinkedHashMap<>(); - rules.put(EXPERIENCE, new JsonCompare.LowerRule()); - JsonObject result = JsonCompare.compare(rules, first, second); - assertEquals(first, result); - } - - @Test - public void compareHigherWins() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.05)); - JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); // 0.1 experience - - Map rules = new LinkedHashMap<>(); - rules.put(EXPERIENCE, new JsonCompare.HigherRule()); - JsonObject result = JsonCompare.compare(rules, first, second); - assertEquals(second, result); - } - - @Test - public void compareMulti() { - JsonObject a = TestUtils.json(TestUtils.Recipes.SMELTING, j -> { - j.addProperty(EXPERIENCE, 0.1); - j.addProperty(COOKING_TIME, 100); - }); - JsonObject b = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.1)); - JsonObject c = TestUtils.json(TestUtils.Recipes.SMELTING, j -> { - j.addProperty(EXPERIENCE, 0.1); - j.addProperty(COOKING_TIME, 50); - }); - JsonObject d = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.2)); - JsonObject e = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.2)); - JsonObject f = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.1)); - JsonObject g = TestUtils.json(TestUtils.Recipes.SMELTING, j -> { - j.addProperty(EXPERIENCE, 0.2); - j.addProperty(COOKING_TIME, 100); - }); - - Map rules = new LinkedHashMap<>(); - rules.put(EXPERIENCE, new JsonCompare.HigherRule()); - rules.put(COOKING_TIME, new JsonCompare.LowerRule()); - - List list = Arrays.asList(a, b, c, d, e, f, g); - list.sort((first, second) -> JsonCompare.compare(first, second, rules)); - List results = Arrays.asList(g, d, e, c, a, b, f); - for (int i = 0; i < list.size(); i++) { - assertEquals(results.get(i), list.get(i), "Failed at index " + i); - } - } - - @Test - public void simpleMatch() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING); - JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); - boolean matches = JsonCompare.matches(first, second, TestUtils.DEFAULT_COMPARE_SETTINGS); - assertTrue(matches); - } - - @Test - public void noMatch() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 100)); - JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); - boolean matches = JsonCompare.matches(first, second, new JsonCompare.CompareSettings()); - assertFalse(matches); - } - - @Test - public void matchBecauseIgnore() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 100)); - JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); - var compareSettings = TestUtils.getDefaultCompareSettings(); - compareSettings.ignoreField(EXPERIENCE); - boolean matches = JsonCompare.matches(first, second, compareSettings); - assertTrue(matches); - } - - @Test - public void shapedNoMatch() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SHAPED_NO_MATCH_1); - JsonObject second = TestUtils.json(TestUtils.Recipes.SHAPED_NO_MATCH_2); - JsonObject result = JsonCompare.compareShaped(first, second, TestUtils.DEFAULT_SHAPED_COMPARE_SETTINGS); - assertNull(result); - } - - @Test - public void shapedSpecialMatch() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SHAPED_SPECIAL_MATCH_1); - JsonObject second = TestUtils.json(TestUtils.Recipes.SHAPED_SPECIAL_MATCH_2); - JsonObject result = JsonCompare.compareShaped(first, second, TestUtils.DEFAULT_SHAPED_COMPARE_SETTINGS); - assertEquals(first, result); - } - - @Test - public void sanitizeImplicitCount() { - JsonObject first = TestUtils.json(TestUtils.Recipes.SHAPED_SANITIZE_1); - JsonObject second = TestUtils.json(TestUtils.Recipes.SHAPED_SANITIZE_2); - var compareSettings = TestUtils.getDefaultShapedCompareSettings(); - compareSettings.setShouldSanitize(true); - JsonObject result = JsonCompare.compareShaped(first, second, compareSettings); - assertEquals(first, result); - } - - @Test - public void sanitizeImplicitCountNested() { - JsonObject first = TestUtils.json(TestUtils.Recipes.CRUSHING_NESTED_SANITIZE_1); - JsonObject second = TestUtils.json(TestUtils.Recipes.CRUSHING_NESTED_SANITIZE_2); - var compareSettings = TestUtils.getDefaultCompareSettings(); - compareSettings.setShouldSanitize(true); - boolean result = JsonCompare.matches(first, second, compareSettings); - assertTrue(result); - } -} diff --git a/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java b/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java deleted file mode 100644 index f90bc2f..0000000 --- a/Common/src/test/java/com/almostreliable/unified/utils/TagMapTests.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.almostreliable.unified.utils; - -import com.almostreliable.unified.TestUtils; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class TagMapTests { - - public static TagMap testTagMap() { - TagMap tagMap = new TagMap<>(); - UnifyTag bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze")); - UnifyTag invarOreTag = UnifyTag.item(new ResourceLocation("forge:ores/invar")); - UnifyTag tinOreTag = UnifyTag.item(new ResourceLocation("forge:ores/tin")); - UnifyTag silverOreTag = UnifyTag.item(new ResourceLocation("forge:ores/silver")); - - tagMap.put(bronzeOreTag, TestUtils.mod1RL("bronze_ore")); - tagMap.put(bronzeOreTag, TestUtils.mod2RL("bronze_ore")); - tagMap.put(bronzeOreTag, TestUtils.mod3RL("bronze_ore")); - - tagMap.put(invarOreTag, TestUtils.mod1RL("invar_ore")); - tagMap.put(invarOreTag, TestUtils.mod2RL("invar_ore")); - tagMap.put(invarOreTag, TestUtils.mod3RL("invar_ore")); - tagMap.put(invarOreTag, TestUtils.mod4RL("invar_ore")); - - tagMap.put(tinOreTag, TestUtils.mod3RL("tin_ore")); - tagMap.put(tinOreTag, TestUtils.mod4RL("tin_ore")); - - tagMap.put(silverOreTag, TestUtils.mod3RL("silver_ore")); - tagMap.put(silverOreTag, TestUtils.mod4RL("silver_ore")); - tagMap.put(silverOreTag, TestUtils.mod5RL("silver_ore")); - return tagMap; - } - - @Test - public void simpleCheck() { - TagMap tagMap = new TagMap<>(); - UnifyTag bronzeOreTag = UnifyTag.item(new ResourceLocation("forge:ores/bronze")); - tagMap.put(bronzeOreTag, TestUtils.mod1RL("bronze_ore")); - tagMap.put(bronzeOreTag, TestUtils.mod2RL("bronze_ore")); - tagMap.put(bronzeOreTag, TestUtils.mod3RL("bronze_ore")); - tagMap.put(bronzeOreTag, TestUtils.mod4RL("bronze_ore")); - tagMap.put(bronzeOreTag, TestUtils.mod5RL("bronze_ore")); - - assertEquals(tagMap.getEntriesByTag(bronzeOreTag).size(), 5); - assertEquals(tagMap.getTagsByEntry(TestUtils.mod1RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTagsByEntry(TestUtils.mod2RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTagsByEntry(TestUtils.mod3RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTagsByEntry(TestUtils.mod4RL("bronze_ore")).size(), 1); - assertEquals(tagMap.getTagsByEntry(TestUtils.mod5RL("bronze_ore")).size(), 1); - - tagMap.put(UnifyTag.item(new ResourceLocation("forge:ores/invar")), TestUtils.mod1RL("invar_ore")); - - assertEquals(tagMap.tagSize(), 2); - assertEquals(tagMap.itemSize(), 6); - } -} diff --git a/Common/src/test/java/testmod/CommonTest.java b/Common/src/test/java/testmod/CommonTest.java new file mode 100644 index 0000000..0d04fcc --- /dev/null +++ b/Common/src/test/java/testmod/CommonTest.java @@ -0,0 +1,26 @@ +package testmod; + +import testmod.gametest_core.GameTestLoader; +import testmod.tests.*; +import testmod.tests.core.TagInheritanceTests; +import testmod.tests.core.TagSubstitutionTests; + +public class CommonTest { + + public static void init(boolean gametestEnabled) { + if (gametestEnabled) { + GameTestLoader.registerProviders( + ExampleTest.class, + UnificationHandlerTests.class, + ReplacementsTests.class, + UnifyTests.class, + ShapedRecipeUnifierTests.class, + SmithingRecipeUnifierTest.class, + GregTechModernRecipeUnifierTests.class, + TagSubstitutionTests.class, + TagInheritanceTests.class, + LootUnificationTests.class + ); + } + } +} diff --git a/Common/src/test/java/testmod/TestItems.java b/Common/src/test/java/testmod/TestItems.java new file mode 100644 index 0000000..8ea441f --- /dev/null +++ b/Common/src/test/java/testmod/TestItems.java @@ -0,0 +1,50 @@ +package testmod; + +import net.minecraft.util.valueproviders.ConstantInt; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DropExperienceBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; +import net.minecraft.world.level.material.MapColor; + +import java.util.function.BiConsumer; + +public class TestItems { + + public static void registerStuff(BiConsumer registerItem, BiConsumer registerBlock) { + + Block testmod$osmium_ingot = ore(); + registerBlock.accept("testmod:osmium_ore", testmod$osmium_ingot); + registerItem.accept("testmod:osmium_ore", new BlockItem(testmod$osmium_ingot, new Item.Properties())); + Block meka_fake$osmium_ingot = ore(); + registerBlock.accept("meka_fake:osmium_ore", meka_fake$osmium_ingot); + registerItem.accept("meka_fake:osmium_ore", new BlockItem(meka_fake$osmium_ingot, new Item.Properties())); + Block ie_fake$osmium_ingot = ore(); + registerBlock.accept("ie_fake:osmium_ore", ie_fake$osmium_ingot); + registerItem.accept("ie_fake:osmium_ore", new BlockItem(ie_fake$osmium_ingot, new Item.Properties())); + Block thermal_fake$osmium_ingot = ore(); + registerBlock.accept("thermal_fake:osmium_ore", thermal_fake$osmium_ingot); + registerItem.accept("thermal_fake:osmium_ore", new BlockItem(thermal_fake$osmium_ingot, new Item.Properties())); + + registerItem.accept("testmod:osmium_ingot", new Item(new Item.Properties())); + registerItem.accept("meka_fake:osmium_ingot", new Item(new Item.Properties())); + registerItem.accept("ie_fake:osmium_ingot", new Item(new Item.Properties())); + registerItem.accept("thermal_fake:osmium_ingot", new Item(new Item.Properties())); + + registerItem.accept("mod_a:silver_ore", new Item(new Item.Properties())); + registerItem.accept("mod_b:silver_ore", new Item(new Item.Properties())); + registerItem.accept("mod_c:silver_ore", new Item(new Item.Properties())); + } + + private static Block ore() { + BlockBehaviour.Properties props = BlockBehaviour.Properties + .of() + .mapColor(MapColor.STONE) + .instrument(NoteBlockInstrument.BASEDRUM) + .requiresCorrectToolForDrops() + .strength(3.0F, 3.0F); + return new DropExperienceBlock(ConstantInt.of(0), props); + } +} diff --git a/Common/src/test/java/testmod/TestUtils.java b/Common/src/test/java/testmod/TestUtils.java new file mode 100644 index 0000000..e0f7a58 --- /dev/null +++ b/Common/src/test/java/testmod/TestUtils.java @@ -0,0 +1,170 @@ +package testmod; + +import com.almostreliable.unified.api.unification.ModPriorities; +import com.almostreliable.unified.api.unification.StoneVariants; +import com.almostreliable.unified.api.unification.TagSubstitutions; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.almostreliable.unified.unification.ModPrioritiesImpl; +import com.almostreliable.unified.unification.UnificationLookupImpl; +import com.almostreliable.unified.unification.recipe.RecipeJsonImpl; +import com.almostreliable.unified.unification.recipe.RecipeLink; +import com.almostreliable.unified.unification.recipe.UnificationHelperImpl; +import com.google.common.collect.Maps; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Type; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestUtils { + + public static final Gson GSON = new GsonBuilder().create(); + + public static final ModPriorities TEST_MOD_PRIORITIES = new ModPrioritiesImpl( + List.of("testmod", "mekanism", "thermal", "create"), + new HashMap<>() + ); + + public static final ModPriorities EMPTY_MOD_PRIORITIES = new ModPrioritiesImpl( + List.of(), + new HashMap<>() + ); + + public static final StoneVariants EMPTY_STONE_VARIANTS = new StoneVariants() { + @Override + public String getStoneVariant(ResourceLocation item) { + return ""; + } + + @Override + public boolean isOreTag(TagKey tag) { + return false; + } + }; + + public static final TagSubstitutions EMPTY_TAG_SUBSTITUTIONS = new TagSubstitutions() { + + @Nullable + @Override + public TagKey getSubstituteTag(TagKey replacedTag) { + return null; + } + + @Override + public Collection> getReplacedTags(TagKey substituteTag) { + return List.of(); + } + + @Override + public Set> getReplacedTags() { + return Set.of(); + } + }; + + public static RecipeLink recipe(String jsonStr) { + var json = json(jsonStr); + return new RecipeLink(ResourceLocation.parse("test"), json); + } + + public static RecipeLink recipe(JsonObject json) { + return new RecipeLink(ResourceLocation.parse("test"), json); + } + + public static JsonObject json(String json) { + return GSON.fromJson(json, JsonObject.class); + } + + public static TagKey itemTag(String s) { + return TagKey.create(Registries.ITEM, ResourceLocation.parse(s)); + } + + + public static UnificationLookup unificationLookup() { + return new UnificationLookupImpl.Builder() + .put(itemTag("testmod:test_tag"), + ResourceLocation.parse("minecraft:test_item"), + ResourceLocation.parse("mekanism:test_item"), + ResourceLocation.parse("thermal:test_item"), + ResourceLocation.parse("testmod:test_item")) + .build(TEST_MOD_PRIORITIES, EMPTY_STONE_VARIANTS, EMPTY_TAG_SUBSTITUTIONS); + } + + + public static UnificationHelper recipeHelper() { + return new UnificationHelperImpl(unificationLookup()); + } + + public static void assertUnify(RecipeUnifier unifier, String jsonActual, String jsonExpected) { + var recipe = TestUtils.recipe(jsonActual); + JsonObject copy = recipe.getOriginal().deepCopy(); + var json = new RecipeJsonImpl(recipe.getId(), copy); + unifier.unify(recipeHelper(), json); + assertNotEquals(recipe.getOriginal(), copy); + + var expected = TestUtils.json(jsonExpected); + assertJson(expected, copy); + } + + public static void assertNoUnify(RecipeUnifier unifier, String jsonStr) { + var recipe = TestUtils.recipe(jsonStr); + JsonObject copy = recipe.getOriginal().deepCopy(); + var json = new RecipeJsonImpl(recipe.getId(), copy); + unifier.unify(recipeHelper(), json); + assertEquals(recipe.getOriginal(), copy); + + var expected = TestUtils.json(jsonStr); + assertJson(expected, copy); + } + + public static void assertJson(JsonObject expected, JsonObject actual) { + Type type = new TypeToken>() {}.getType(); + Map expectedMap = GSON.fromJson(expected, type); + Map actualMap = GSON.fromJson(actual, type); + var difference = Maps.difference(expectedMap, actualMap); + + if (difference.areEqual()) { + return; + } + + StringBuilder sb = new StringBuilder(); + sb + .append("\nExpected:\t") + .append(GSON.toJson(expected)) + .append("\nActual:\t\t") + .append(GSON.toJson(actual)) + .append("\n"); + if (!difference.entriesDiffering().isEmpty()) { + sb.append("Differences:\n"); + difference.entriesDiffering().forEach((k, v) -> { + sb.append("\t").append(k).append(": ").append(v).append("\n"); + }); + } + + if (!difference.entriesOnlyOnLeft().isEmpty()) { + sb.append("Only on left:\n"); + difference.entriesOnlyOnLeft().forEach((k, v) -> { + sb.append("\t").append(k).append(": ").append(v).append("\n"); + }); + } + + if (!difference.entriesOnlyOnRight().isEmpty()) { + sb.append("Only on right:\n"); + difference.entriesOnlyOnRight().forEach((k, v) -> { + sb.append("\t").append(k).append(": ").append(v).append("\n"); + }); + } + + fail(sb.toString()); + } +} diff --git a/Common/src/test/java/testmod/gametest_core/AlmostGameTestHelper.java b/Common/src/test/java/testmod/gametest_core/AlmostGameTestHelper.java new file mode 100644 index 0000000..e61c9b7 --- /dev/null +++ b/Common/src/test/java/testmod/gametest_core/AlmostGameTestHelper.java @@ -0,0 +1,70 @@ +package testmod.gametest_core; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.gametest.framework.GameTestAssertException; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.GameTestInfo; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.block.entity.BlockEntity; + +/** + * A custom implementation of {@link GameTestHelper} that can be used to + * add more utility methods for game tests. + */ +public class AlmostGameTestHelper extends GameTestHelper { + + private final GameTestInfo testInfo; + + public AlmostGameTestHelper(GameTestInfo testInfo) { + super(testInfo); + this.testInfo = testInfo; + } + + /** + * Convenience method to get a {@link BlockEntity} at the given position. + *

+ * Will automatically fail if the {@link BlockEntity} is not of the expected type. + * + * @param pos The position of the block entity. + * @param clazz The expected type of the block entity. + * @param The type of the block entity. + * @return The block entity at the given position. + */ + public T getBlockEntity(BlockPos pos, Class clazz) { + BlockEntity be = getBlockEntity(pos); + if (!clazz.isInstance(be)) { + throw new GameTestAssertException("expected block entity of type " + clazz.getName() + " at " + pos); + } + + return clazz.cast(be); + } + + /** + * Runs the given task at the given tick time and then succeeds the test. + *

+ * Avoids expected tick time failures that come with {@link #succeedOnTickWhen(int, Runnable)}. + * + * @param tickTime The tick time to run the task at. + * @param task The task to run. + */ + public void succeedAtTickTime(long tickTime, Runnable task) { + runAtTickTime(tickTime, () -> { + task.run(); + succeed(); + }); + } + + /** + * Returns the {@link Holder} for the given {@link ResourceKey}. + * + * @param key The {@link ResourceKey} of the entry. + * @param The type of the entry. + * @return The {@link Holder} for the given {@link ResourceKey}. + */ + public Holder getHolder(ResourceKey key) { + Registry registry = getLevel().registryAccess().registryOrThrow(key.registryKey()); + return registry.getHolderOrThrow(key); + } +} diff --git a/Common/src/test/java/testmod/gametest_core/GameTestLoader.java b/Common/src/test/java/testmod/gametest_core/GameTestLoader.java new file mode 100644 index 0000000..233f4f0 --- /dev/null +++ b/Common/src/test/java/testmod/gametest_core/GameTestLoader.java @@ -0,0 +1,196 @@ +package testmod.gametest_core; + +import net.minecraft.gametest.framework.*; +import net.minecraft.world.level.block.Rotation; +import testmod.gametest_core.mixin.GameTestHelperAccessor; +import testmod.gametest_core.mixin.GameTestRegistryAccessor; + +import javax.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GameTestLoader { + + public static final String ENABLED_NAMESPACES = "almostunified.gametest.testPackages"; + + @Nullable + private static List ENABLED_MODS; + + private GameTestLoader() {} + + public static void registerProviders(Class... providerClasses) { + for (var providerClass : providerClasses) { + if (!isAllowedModIdToRun(providerClass)) continue; + + Object instance; + try { + instance = providerClass.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + + for (Method method : providerClass.getDeclaredMethods()) { + GameTest gametest = method.getAnnotation(GameTest.class); + SimpleGameTest simpleGametest = method.getAnnotation(SimpleGameTest.class); + if (gametest != null && simpleGametest != null) { + throw new IllegalArgumentException( + "Cannot have both @GameTest and @SimpleGameTest on the same method"); + } + + if (gametest != null) { + register(method, gametest, TestMethodConsumer.of(instance, method)::accept); + } + + if (simpleGametest != null) { + register(method, simpleGametest, TestMethodConsumer.of(instance, method)::acceptAutoSucceed); + } + } + } + } + + + private static void register(Method method, GameTest gametest, Consumer consumer) { + String template = gametest.template(); + if (template.isEmpty()) { + template = "testmod:empty_test_structure"; + } + + var test = new TestFunction( + gametest.batch(), + createTestName(method), + template, + StructureUtils.getRotationForRotationSteps(gametest.rotationSteps()), + gametest.timeoutTicks(), + gametest.setupTicks(), + gametest.required(), + gametest.manualOnly(), + gametest.attempts(), + gametest.requiredSuccesses(), + gametest.skyAccess(), + consumer + ); + + GameTestRegistryAccessor.TEST_FUNCTIONS().add(test); + GameTestRegistryAccessor.TEST_CLASS_NAMES().add(method.getDeclaringClass().getSimpleName()); + } + + private static void register(Method method, SimpleGameTest gametest, Consumer consumer) { + String template = gametest.template(); + if (template.isEmpty()) { + template = "testmod:empty_test_structure"; + } + + var test = new TestFunction(gametest.batch(), + createTestName(method), + template, + Rotation.NONE, + 100, + 0, + true, + false, + gametest.attempts(), + 1, + false, + consumer); + + GameTestRegistryAccessor.TEST_FUNCTIONS().add(test); + GameTestRegistryAccessor.TEST_CLASS_NAMES().add(method.getDeclaringClass().getSimpleName()); + } + + private static String createTestName(Method method) { + String className = method.getDeclaringClass().getSimpleName().toLowerCase(); + String methodName = method.getName().replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(); + return className + "." + methodName; + } + + private static boolean isAllowedModIdToRun(Class provider) { + if (ENABLED_MODS == null) { + String enabledNamespaces = System.getProperty(ENABLED_NAMESPACES); + if (enabledNamespaces == null) { + ENABLED_MODS = Collections.emptyList(); + } else { + ENABLED_MODS = Arrays.stream(enabledNamespaces.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(Pattern::compile) + .toList(); +// AlmostLib.LOGGER.info("Enabled gametests for mods: " + ENABLED_MODS); + } + } + + String name = provider.getName(); + return ENABLED_MODS.stream().map(p -> p.matcher(name)).anyMatch(Matcher::matches); + } + + private record TestMethodConsumer(Object instance, Method method) { + + static TestMethodConsumer of(Object instance, Method method) { + if (Modifier.isStatic(method.getModifiers())) { + throw new RuntimeException("static methods are not supported"); + } + + return new TestMethodConsumer(instance, method); + } + + private String describeError(Throwable throwable) { + if (throwable.getCause() != null) { + return describeError(throwable.getCause()); + } + + if (throwable.getMessage() == null) { + return throwable.toString(); + } + + return throwable.getClass().getName() + ": " + throwable.getMessage(); + } + + public void acceptAutoSucceed(GameTestHelper testHelper) { + accept(testHelper); + testHelper.succeed(); + } + + public void accept(GameTestHelper testHelper) { + try { + unsafeAccept(testHelper); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof AssertionError ae) { + throw new GameTestAssertException(ae.getMessage()); + } + + throw new RuntimeException(describeError(e.getTargetException())); + } + } + + public void unsafeAccept(GameTestHelper testHelper) throws IllegalAccessException, InvocationTargetException { + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length == 0) { + method.invoke(instance); + return; + } + + if (parameterTypes.length == 1) { + if (!GameTestHelper.class.isAssignableFrom(parameterTypes[0])) { + throw new RuntimeException( + "unsupported parameter type, parameter must extend " + GameTestHelper.class.getName()); + } + + // noinspection CastToIncompatibleInterface + AlmostGameTestHelper almostHelper = new AlmostGameTestHelper(((GameTestHelperAccessor) testHelper).getTestInfo()); + method.invoke(instance, almostHelper); + return; + } + + throw new RuntimeException("unsupported number of parameters, must be 0 or 1"); + } + } +} diff --git a/Common/src/test/java/testmod/gametest_core/SimpleGameTest.java b/Common/src/test/java/testmod/gametest_core/SimpleGameTest.java new file mode 100644 index 0000000..b7ede78 --- /dev/null +++ b/Common/src/test/java/testmod/gametest_core/SimpleGameTest.java @@ -0,0 +1,19 @@ +package testmod.gametest_core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for game tests. Can be used to create simple game tests. Simple game tests will automatically succeed if no exception is thrown. + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface SimpleGameTest { + String batch() default "defaultBatch"; + + String template() default ""; + + int attempts() default 1; +} diff --git a/Common/src/test/java/testmod/gametest_core/mixin/GameTestHelperAccessor.java b/Common/src/test/java/testmod/gametest_core/mixin/GameTestHelperAccessor.java new file mode 100644 index 0000000..86b27d9 --- /dev/null +++ b/Common/src/test/java/testmod/gametest_core/mixin/GameTestHelperAccessor.java @@ -0,0 +1,14 @@ +package testmod.gametest_core.mixin; + + +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.GameTestInfo; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(GameTestHelper.class) +public interface GameTestHelperAccessor { + + @Accessor + GameTestInfo getTestInfo(); +} diff --git a/Common/src/test/java/testmod/gametest_core/mixin/GameTestRegistryAccessor.java b/Common/src/test/java/testmod/gametest_core/mixin/GameTestRegistryAccessor.java new file mode 100644 index 0000000..e8c2979 --- /dev/null +++ b/Common/src/test/java/testmod/gametest_core/mixin/GameTestRegistryAccessor.java @@ -0,0 +1,43 @@ +package testmod.gametest_core.mixin; + + +import net.minecraft.gametest.framework.GameTestRegistry; +import net.minecraft.gametest.framework.TestFunction; +import net.minecraft.server.level.ServerLevel; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.throwables.MixinException; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +@Mixin(GameTestRegistry.class) +public interface GameTestRegistryAccessor { + + @Accessor("TEST_FUNCTIONS") + static Collection TEST_FUNCTIONS() { + throw new MixinException("mixin failed to apply"); + } + + @Accessor("TEST_CLASS_NAMES") + static Set TEST_CLASS_NAMES() { + throw new MixinException("mixin failed to apply"); + } + + @Accessor("BEFORE_BATCH_FUNCTIONS") + static Map> BEFORE_BATCH_FUNCTIONS() { + throw new MixinException("mixin failed to apply"); + } + + @Accessor("AFTER_BATCH_FUNCTIONS") + static Map> AFTER_BATCH_FUNCTIONS() { + throw new MixinException("mixin failed to apply"); + } + + @Accessor("LAST_FAILED_TESTS") + static Set LAST_FAILED_TESTS() { + throw new MixinException("mixin failed to apply"); + } +} diff --git a/Common/src/test/java/testmod/gametest_core/mixin/ItemTooltipMixin.java b/Common/src/test/java/testmod/gametest_core/mixin/ItemTooltipMixin.java new file mode 100644 index 0000000..8184725 --- /dev/null +++ b/Common/src/test/java/testmod/gametest_core/mixin/ItemTooltipMixin.java @@ -0,0 +1,30 @@ +package testmod.gametest_core.mixin; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import org.jetbrains.annotations.Nullable; +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; + +import java.util.List; + +@Mixin(ItemStack.class) +public class ItemTooltipMixin { + + @Inject(method = "getTooltipLines", at = @At("TAIL")) + private void auTest$injectTagsToItem(Item.TooltipContext tooltipContext, @Nullable Player player, TooltipFlag tooltipFlag, CallbackInfoReturnable> cir) { + var stack = (ItemStack) (Object) this; + List l = cir.getReturnValue(); + l.add(Component.literal("---------------")); + l.add(Component.literal("Tags: ")); + stack.getItemHolder().tags().map(tag -> tag.location().toString()).sorted(String::compareTo).forEach(s -> { + l.add(Component.literal("- " + s).withStyle(ChatFormatting.GRAY)); + }); + } +} diff --git a/Common/src/main/java/com/almostreliable/unified/recipe/package-info.java b/Common/src/test/java/testmod/gametest_core/package-info.java similarity index 79% rename from Common/src/main/java/com/almostreliable/unified/recipe/package-info.java rename to Common/src/test/java/testmod/gametest_core/package-info.java index 539c588..b5d9a58 100644 --- a/Common/src/main/java/com/almostreliable/unified/recipe/package-info.java +++ b/Common/src/test/java/testmod/gametest_core/package-info.java @@ -1,5 +1,5 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -package com.almostreliable.unified.recipe; +package testmod.gametest_core; import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/Common/src/test/java/com/almostreliable/unified/FakeResourceKeyRegistry.java b/Common/src/test/java/testmod/old/FakeResourceKeyRegistry.java similarity index 75% rename from Common/src/test/java/com/almostreliable/unified/FakeResourceKeyRegistry.java rename to Common/src/test/java/testmod/old/FakeResourceKeyRegistry.java index e19735b..dd25973 100644 --- a/Common/src/test/java/com/almostreliable/unified/FakeResourceKeyRegistry.java +++ b/Common/src/test/java/testmod/old/FakeResourceKeyRegistry.java @@ -1,4 +1,4 @@ -package com.almostreliable.unified; +package testmod.old; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; @@ -13,10 +13,10 @@ public class FakeResourceKeyRegistry { try { Constructor c = ResourceKey.class.getDeclaredConstructor(ResourceLocation.class, ResourceLocation.class); c.setAccessible(true); - return (ResourceKey>) c.newInstance(new ResourceLocation("test_registry"), - new ResourceLocation(name)); + return (ResourceKey>) c.newInstance(ResourceLocation.withDefaultNamespace("test_registry"), + ResourceLocation.parse(name)); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | - IllegalAccessException e) { + IllegalAccessException e) { e.printStackTrace(); return null; } diff --git a/Common/src/test/java/com/almostreliable/unified/TestUtils.java b/Common/src/test/java/testmod/old/TestUtils.java similarity index 63% rename from Common/src/test/java/com/almostreliable/unified/TestUtils.java rename to Common/src/test/java/testmod/old/TestUtils.java index 354f9f0..6eba254 100644 --- a/Common/src/test/java/com/almostreliable/unified/TestUtils.java +++ b/Common/src/test/java/testmod/old/TestUtils.java @@ -1,23 +1,16 @@ -package com.almostreliable.unified; +package testmod.old; -import com.almostreliable.unified.api.StoneStrataHandler; +import com.almostreliable.unified.AlmostUnifiedPlatform; import com.almostreliable.unified.config.Defaults; -import com.almostreliable.unified.config.UnifyConfig; -import com.almostreliable.unified.recipe.RecipeTransformer; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; -import com.almostreliable.unified.utils.*; -import com.google.gson.Gson; -import com.google.gson.JsonObject; +import com.almostreliable.unified.utils.JsonCompare; import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.function.Consumer; public final class TestUtils { @@ -35,46 +28,27 @@ public final class TestUtils { TEST_MOD_5 ); - public static final UnifyConfig DEFAULT_UNIFY_CONFIG = new UnifyConfig( - Defaults.STONE_STRATA, - Defaults.MATERIALS, - Defaults.getTags(AlmostUnifiedPlatform.Platform.FORGE), - TEST_MOD_PRIORITIES, - new HashMap<>(), - new HashMap<>(), - new HashMap<>(), - UnifyConfig.TagInheritanceMode.ALLOW, - new HashMap<>(), - UnifyConfig.TagInheritanceMode.ALLOW, - new HashMap<>(), - new HashSet<>(), - new HashSet<>(), - new HashSet<>(), - new HashSet<>(), - true - ); - public static final JsonCompare.CompareSettings DEFAULT_COMPARE_SETTINGS = getDefaultCompareSettings(); public static final JsonCompare.CompareSettings DEFAULT_SHAPED_COMPARE_SETTINGS = getDefaultShapedCompareSettings(); private TestUtils() {} public static JsonCompare.CompareSettings getDefaultCompareSettings() { - return Defaults.getDefaultDuplicateRules(AlmostUnifiedPlatform.Platform.FORGE); + return Defaults.getDefaultDuplicateRules(AlmostUnifiedPlatform.Platform.NEO_FORGE); } public static JsonCompare.CompareSettings getDefaultShapedCompareSettings() { return Defaults - .getDefaultDuplicateOverrides(AlmostUnifiedPlatform.Platform.FORGE) - .get(new ResourceLocation("crafting_shaped")); + .getDefaultDuplicateOverrides(AlmostUnifiedPlatform.Platform.NEO_FORGE) + .get(ResourceLocation.withDefaultNamespace("crafting_shaped")); } public static final ResourceKey> FAKE_ITEM_REGISTRY = FakeResourceKeyRegistry.create("item"); - public static final UnifyTag BRONZE_ORES_TAG = tag("forge:ores/bronze"); - public static final UnifyTag INVAR_ORES_TAG = tag("forge:ores/invar"); - public static final UnifyTag TIN_ORES_TAG = tag("forge:ores/tin"); - public static final UnifyTag SILVER_ORES_TAG = tag("forge:ores/silver"); - public static final List> TEST_ALLOWED_TAGS = List.of( + public static final TagKey BRONZE_ORES_TAG = tag("c:ores/bronze"); + public static final TagKey INVAR_ORES_TAG = tag("c:ores/invar"); + public static final TagKey TIN_ORES_TAG = tag("c:ores/tin"); + public static final TagKey SILVER_ORES_TAG = tag("c:ores/silver"); + public static final List> TEST_ALLOWED_TAGS = List.of( BRONZE_ORES_TAG, INVAR_ORES_TAG, TIN_ORES_TAG, @@ -87,64 +61,56 @@ public final class TestUtils { * @param name the name of the tag * @return a TagKey for the given name */ - public static UnifyTag tag(String name) { - return UnifyTag.item(new ResourceLocation(name)); + public static TagKey tag(String name) { + return TagKey.create(Registries.ITEM, ResourceLocation.parse(name)); } public static ResourceLocation mod1RL(String name) { - return new ResourceLocation(TEST_MOD_1, name); + return ResourceLocation.fromNamespaceAndPath(TEST_MOD_1, name); } public static ResourceLocation mod2RL(String name) { - return new ResourceLocation(TEST_MOD_2, name); + return ResourceLocation.fromNamespaceAndPath(TEST_MOD_2, name); } public static ResourceLocation mod3RL(String name) { - return new ResourceLocation(TEST_MOD_3, name); + return ResourceLocation.fromNamespaceAndPath(TEST_MOD_3, name); } public static ResourceLocation mod4RL(String name) { - return new ResourceLocation(TEST_MOD_4, name); + return ResourceLocation.fromNamespaceAndPath(TEST_MOD_4, name); } public static ResourceLocation mod5RL(String name) { - return new ResourceLocation(TEST_MOD_5, name); + return ResourceLocation.fromNamespaceAndPath(TEST_MOD_5, name); } - public static StoneStrataHandler createTestStrataHandler() { - return StoneStrataHandler.create( - List.of(), - Set.of(), - TagMap.create(Set.of()) - ); - } - - public static RecipeTransformer basicTransformer(Consumer consumer) { - TagOwnerships tagOwnerships = new TagOwnerships( - DEFAULT_UNIFY_CONFIG.bakeTags(), - DEFAULT_UNIFY_CONFIG.getTagOwnerships() - ); - ReplacementMap map = new ReplacementMap( - DEFAULT_UNIFY_CONFIG, - TagMapTests.testTagMap(), - createTestStrataHandler(), - tagOwnerships - ); - RecipeHandlerFactory factory = new RecipeHandlerFactory(); - consumer.accept(factory); - return new RecipeTransformer(factory, map, DEFAULT_UNIFY_CONFIG, null); - } - - public static JsonObject json(String json) { - return new Gson().fromJson(json, JsonObject.class); - } - - public static JsonObject json(String json, Consumer consumer) { - Gson gson = new Gson(); - JsonObject obj = gson.fromJson(json, JsonObject.class); - consumer.accept(obj); - return obj; - } +// public static RecipeTransformer basicTransformer(Consumer consumer) { +// TagOwnerships tagOwnerships = new TagOwnerships( +// DEFAULT_UNIFY_CONFIG.bakeTags(), +// DEFAULT_UNIFY_CONFIG.getTagOwnerships() +// ); +// ReplacementMap map = new ReplacementMap( +// DEFAULT_UNIFY_CONFIG, +// TagMapTests.testTagMap(), +// createTestStrataHandler(), +// tagOwnerships +// ); +// RecipeHandlerFactory factory = new RecipeHandlerFactory(); +// consumer.accept(factory); +// return new RecipeTransformer(factory, map, DEFAULT_UNIFY_CONFIG, null); +// } +// +// public static JsonObject json(String json) { +// return new Gson().fromJson(json, JsonObject.class); +// } +// +// public static JsonObject json(String json, Consumer consumer) { +// Gson gson = new Gson(); +// JsonObject obj = gson.fromJson(json, JsonObject.class); +// consumer.accept(obj); +// return obj; +// } public static final class Recipes { @@ -170,7 +136,7 @@ public final class TestUtils { ], "key": { "i": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" }, "k": { "item": "minecraft:carrot" @@ -189,7 +155,7 @@ public final class TestUtils { ], "key": { "i": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" }, "k": { "item": "minecraft:pumpkin" @@ -208,7 +174,7 @@ public final class TestUtils { ], "key": { "i": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" } }, "result": "minecraft:iron_ingot" @@ -224,10 +190,10 @@ public final class TestUtils { ], "key": { "i": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" }, "k": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" } }, "result": "minecraft:iron_ingot" @@ -243,7 +209,7 @@ public final class TestUtils { ], "key": { "i": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" } }, "result": "minecraft:iron_ingot" @@ -259,7 +225,7 @@ public final class TestUtils { ], "key": { "i": { - "tag": "forge:raw_materials/iron" + "tag": "c:raw_materials/iron" } }, "result": { @@ -271,7 +237,7 @@ public final class TestUtils { public static final String CRUSHING_NESTED_SANITIZE_1 = """ { "type": "create:crushing", - "ingredients": [{ "tag": "forge:raw_materials/lead" }], + "ingredients": [{ "tag": "c:raw_materials/lead" }], "processingTime": 400, "results": [ { "item": "emendatusenigmatica:crushed_lead_ore" }, @@ -282,7 +248,7 @@ public final class TestUtils { public static final String CRUSHING_NESTED_SANITIZE_2 = """ { "type": "create:crushing", - "ingredients": [{ "tag": "forge:raw_materials/lead" }], + "ingredients": [{ "tag": "c:raw_materials/lead" }], "processingTime": 400, "results": [ { "count": 1, "item": "emendatusenigmatica:crushed_lead_ore" }, diff --git a/Common/src/test/java/testmod/old/package-info.java b/Common/src/test/java/testmod/old/package-info.java new file mode 100644 index 0000000..710af0e --- /dev/null +++ b/Common/src/test/java/testmod/old/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package testmod.old; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/test/java/com/almostreliable/unified/recipe/RecipeContextImplTest.java b/Common/src/test/java/testmod/old/recipe/RecipeContextImplTest.java similarity index 66% rename from Common/src/test/java/com/almostreliable/unified/recipe/RecipeContextImplTest.java rename to Common/src/test/java/testmod/old/recipe/RecipeContextImplTest.java index 5a8cee2..1c35828 100644 --- a/Common/src/test/java/com/almostreliable/unified/recipe/RecipeContextImplTest.java +++ b/Common/src/test/java/testmod/old/recipe/RecipeContextImplTest.java @@ -1,19 +1,13 @@ -package com.almostreliable.unified.recipe; +package testmod.old.recipe; -import com.almostreliable.unified.TestUtils; -import com.almostreliable.unified.utils.ReplacementMap; -import com.almostreliable.unified.utils.TagMapTests; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import org.junit.jupiter.api.Test; // TODO I BROKE THEM! NEED TO FIX public class RecipeContextImplTest { public static String mekaTest = """ { "type": "mekanism:combining", - "mainInput": { "amount": 8, "ingredient": { "tag": "forge:raw_materials/tin" } }, - "extraInput": { "ingredient": { "tag": "forge:cobblestone/normal" } }, + "mainInput": { "amount": 8, "ingredient": { "tag": "c:raw_materials/tin" } }, + "extraInput": { "ingredient": { "tag": "c:cobblestone/normal" } }, "output": { "item": "mekanism:tin_ore" } } """; diff --git a/Common/src/test/java/testmod/old/util/JsonCompareTest.java b/Common/src/test/java/testmod/old/util/JsonCompareTest.java new file mode 100644 index 0000000..66eb623 --- /dev/null +++ b/Common/src/test/java/testmod/old/util/JsonCompareTest.java @@ -0,0 +1,134 @@ +package testmod.old.util; + + +public class JsonCompareTest { + + private static final String EXPERIENCE = "experience"; + private static final String COOKING_TIME = "cookingtime"; + +// @Test +// public void simpleCompareFirst() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.2)); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); // 0.1 experience +// +// Map rules = new LinkedHashMap<>(); +// rules.put(EXPERIENCE, new JsonCompare.LowerRule()); +// JsonObject result = JsonCompare.compare(rules, first, second); +// assertEquals(second, result); +// } +// +// @Test +// public void simpleCompareSecond() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.05)); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); // 0.1 experience +// +// Map rules = new LinkedHashMap<>(); +// rules.put(EXPERIENCE, new JsonCompare.LowerRule()); +// JsonObject result = JsonCompare.compare(rules, first, second); +// assertEquals(first, result); +// } +// +// @Test +// public void compareHigherWins() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.05)); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); // 0.1 experience +// +// Map rules = new LinkedHashMap<>(); +// rules.put(EXPERIENCE, new JsonCompare.HigherRule()); +// JsonObject result = JsonCompare.compare(rules, first, second); +// assertEquals(second, result); +// } +// +// @Test +// public void compareMulti() { +// JsonObject a = TestUtils.json(TestUtils.Recipes.SMELTING, j -> { +// j.addProperty(EXPERIENCE, 0.1); +// j.addProperty(COOKING_TIME, 100); +// }); +// JsonObject b = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.1)); +// JsonObject c = TestUtils.json(TestUtils.Recipes.SMELTING, j -> { +// j.addProperty(EXPERIENCE, 0.1); +// j.addProperty(COOKING_TIME, 50); +// }); +// JsonObject d = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.2)); +// JsonObject e = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.2)); +// JsonObject f = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 0.1)); +// JsonObject g = TestUtils.json(TestUtils.Recipes.SMELTING, j -> { +// j.addProperty(EXPERIENCE, 0.2); +// j.addProperty(COOKING_TIME, 100); +// }); +// +// Map rules = new LinkedHashMap<>(); +// rules.put(EXPERIENCE, new JsonCompare.HigherRule()); +// rules.put(COOKING_TIME, new JsonCompare.LowerRule()); +// +// List list = Arrays.asList(a, b, c, d, e, f, g); +// list.sort((first, second) -> JsonCompare.compare(first, second, rules)); +// List results = Arrays.asList(g, d, e, c, a, b, f); +// for (int i = 0; i < list.size(); i++) { +// assertEquals(results.get(i), list.get(i), "Failed at index " + i); +// } +// } +// +// @Test +// public void simpleMatch() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); +// boolean matches = JsonCompare.matches(first, second, TestUtils.DEFAULT_COMPARE_SETTINGS); +// assertTrue(matches); +// } +// +// @Test +// public void noMatch() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 100)); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); +// boolean matches = JsonCompare.matches(first, second, new JsonCompare.CompareSettings()); +// assertFalse(matches); +// } +// +// @Test +// public void matchBecauseIgnore() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SMELTING, j -> j.addProperty(EXPERIENCE, 100)); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SMELTING); +// var compareSettings = TestUtils.getDefaultCompareSettings(); +// compareSettings.ignoreField(EXPERIENCE); +// boolean matches = JsonCompare.matches(first, second, compareSettings); +// assertTrue(matches); +// } +// +// @Test +// public void shapedNoMatch() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SHAPED_NO_MATCH_1); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SHAPED_NO_MATCH_2); +// JsonObject result = JsonCompare.compareShaped(first, second, TestUtils.DEFAULT_SHAPED_COMPARE_SETTINGS); +// assertNull(result); +// } +// +// @Test +// public void shapedSpecialMatch() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SHAPED_SPECIAL_MATCH_1); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SHAPED_SPECIAL_MATCH_2); +// JsonObject result = JsonCompare.compareShaped(first, second, TestUtils.DEFAULT_SHAPED_COMPARE_SETTINGS); +// assertEquals(first, result); +// } +// +// @Test +// public void sanitizeImplicitCount() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.SHAPED_SANITIZE_1); +// JsonObject second = TestUtils.json(TestUtils.Recipes.SHAPED_SANITIZE_2); +// var compareSettings = TestUtils.getDefaultShapedCompareSettings(); +// compareSettings.setShouldSanitize(true); +// JsonObject result = JsonCompare.compareShaped(first, second, compareSettings); +// assertEquals(first, result); +// } +// +// @Test +// public void sanitizeImplicitCountNested() { +// JsonObject first = TestUtils.json(TestUtils.Recipes.CRUSHING_NESTED_SANITIZE_1); +// JsonObject second = TestUtils.json(TestUtils.Recipes.CRUSHING_NESTED_SANITIZE_2); +// var compareSettings = TestUtils.getDefaultCompareSettings(); +// compareSettings.setShouldSanitize(true); +// boolean result = JsonCompare.matches(first, second, compareSettings); +// assertTrue(result); +// } +} diff --git a/Common/src/test/java/com/almostreliable/unified/utils/ReplacementMapTests.java b/Common/src/test/java/testmod/old/utils/ReplacementMapTests.java similarity index 84% rename from Common/src/test/java/com/almostreliable/unified/utils/ReplacementMapTests.java rename to Common/src/test/java/testmod/old/utils/ReplacementMapTests.java index 6ff4fbe..02af3d6 100644 --- a/Common/src/test/java/com/almostreliable/unified/utils/ReplacementMapTests.java +++ b/Common/src/test/java/testmod/old/utils/ReplacementMapTests.java @@ -1,11 +1,6 @@ -package com.almostreliable.unified.utils; +package testmod.old.utils; -import com.almostreliable.unified.TestUtils; -import net.minecraft.resources.ResourceLocation; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; // TODO I BROKE THEM! NEED TO FIX public class ReplacementMapTests { diff --git a/Common/src/test/java/testmod/package-info.java b/Common/src/test/java/testmod/package-info.java new file mode 100644 index 0000000..5b9a79f --- /dev/null +++ b/Common/src/test/java/testmod/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package testmod; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/test/java/testmod/tests/ExampleTest.java b/Common/src/test/java/testmod/tests/ExampleTest.java new file mode 100644 index 0000000..fb5e0ea --- /dev/null +++ b/Common/src/test/java/testmod/tests/ExampleTest.java @@ -0,0 +1,15 @@ +package testmod.tests; + +import testmod.gametest_core.SimpleGameTest; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ExampleTest { + + @SimpleGameTest + public void testMaddinCanRun() { + // Currently safe check that gradle conf is correct :') + assertTrue(true); + } + +} diff --git a/Common/src/test/java/testmod/tests/GregTechModernRecipeUnifierTests.java b/Common/src/test/java/testmod/tests/GregTechModernRecipeUnifierTests.java new file mode 100644 index 0000000..98be769 --- /dev/null +++ b/Common/src/test/java/testmod/tests/GregTechModernRecipeUnifierTests.java @@ -0,0 +1,227 @@ +package testmod.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.GregTechModernRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertUnify; + +public class GregTechModernRecipeUnifierTests { + + public static final RecipeUnifier UNIFIER = new GregTechModernRecipeUnifier(); + + @SimpleGameTest + public void test() { + assertUnify(UNIFIER, """ + { + "type": "gtceu:extruder", + "duration": 646, + "inputs": { + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "item": "testmod:test_item" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + }, + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "item": "minecraft:test_item" + } + }, + "chance": 0.0, + "tierChanceBoost": 0.0 + }, + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "item": "minecraft:ender_pearl" + } + }, + "chance": 0.0, + "tierChanceBoost": 0.0 + } + ] + }, + "outputs": { + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 2, + "ingredient": { + "item": "minecraft:test_item" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + }, + { + "content": { + "type": "gtceu:sized", + "count": 2, + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ] + }, + "tickInputs": { + "eu": [ + { + "content": 180, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ], + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "item": "minecraft:test_item" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ] + }, + "tickOutputs": { + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ] + } + } + """, """ + { + "type": "gtceu:extruder", + "duration": 646, + "inputs": { + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + }, + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "chance": 0.0, + "tierChanceBoost": 0.0 + }, + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "item": "minecraft:ender_pearl" + } + }, + "chance": 0.0, + "tierChanceBoost": 0.0 + } + ] + }, + "outputs": { + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 2, + "ingredient": { + "item": "testmod:test_item" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + }, + { + "content": { + "type": "gtceu:sized", + "count": 2, + "ingredient": { + "item": "testmod:test_item" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ] + }, + "tickInputs": { + "eu": [ + { + "content": 180, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ], + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ] + }, + "tickOutputs": { + "item": [ + { + "content": { + "type": "gtceu:sized", + "count": 1, + "ingredient": { + "item": "testmod:test_item" + } + }, + "chance": 1.0, + "tierChanceBoost": 0.0 + } + ] + } + } + """); + } +} diff --git a/Common/src/test/java/testmod/tests/LootUnificationTests.java b/Common/src/test/java/testmod/tests/LootUnificationTests.java new file mode 100644 index 0000000..8d10f8e --- /dev/null +++ b/Common/src/test/java/testmod/tests/LootUnificationTests.java @@ -0,0 +1,69 @@ +package testmod.tests; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +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.enchantment.Enchantments; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.block.Block; +import testmod.gametest_core.AlmostGameTestHelper; +import testmod.gametest_core.SimpleGameTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class LootUnificationTests { + + private Block getBlock(String name) { + return BuiltInRegistries.BLOCK.getOptional(ResourceLocation.parse(name)).orElseThrow(); + } + + /** + * Test results are based on the testmod_configs -> materials.json + * + * @param helper - GameTestHelper + */ + @SimpleGameTest + public void simpleTest(AlmostGameTestHelper helper) { + Player player = helper.makeMockPlayer(GameType.SURVIVAL); + ItemStack pickaxe = new ItemStack(Items.DIAMOND_PICKAXE); + + ArrayList positions = BlockPos + .betweenClosedStream(new BlockPos(0, 2, 0), new BlockPos(2, 10, 2)) + .collect(Collectors.toCollection(ArrayList::new)); + + testDrop(helper, player, pickaxe, positions.removeFirst(), "testmod:osmium_ore", "testmod:osmium_ingot"); + testDrop(helper, player, pickaxe, positions.removeFirst(), "meka_fake:osmium_ore", "testmod:osmium_ingot"); + testDrop(helper, player, pickaxe, positions.removeFirst(), "ie_fake:osmium_ore", "testmod:osmium_ingot"); + testDrop(helper, player, pickaxe, positions.removeFirst(), "thermal_fake:osmium_ore", "testmod:osmium_ingot"); + + ItemStack pickaxe2 = new ItemStack(Items.DIAMOND_PICKAXE); + pickaxe2.enchant(helper.getHolder(Enchantments.SILK_TOUCH), 1); + testDrop(helper, player, pickaxe2, positions.removeFirst(), "testmod:osmium_ore", "testmod:osmium_ore"); + testDrop(helper, player, pickaxe2, positions.removeFirst(), "meka_fake:osmium_ore", "testmod:osmium_ore"); + testDrop(helper, player, pickaxe2, positions.removeFirst(), "ie_fake:osmium_ore", "testmod:osmium_ore"); + testDrop(helper, player, pickaxe2, positions.removeFirst(), "thermal_fake:osmium_ore", "testmod:osmium_ore"); + } + + private void testDrop(AlmostGameTestHelper helper, Player player, ItemStack tool, BlockPos orePos, String oreId, String exceptedId) { + Block oreBlock = getBlock(oreId); + helper.setBlock(orePos, oreBlock.defaultBlockState()); + List drops = Block.getDrops(helper.getBlockState(orePos), + helper.getLevel(), + orePos, + null, + player, + tool); + assertEquals(1, drops.size()); + var result = drops.getFirst().getItem(); + var resultId = BuiltInRegistries.ITEM.getKey(result); + assertEquals(exceptedId, resultId.toString()); + } + +} diff --git a/Common/src/test/java/testmod/tests/ReplacementsTests.java b/Common/src/test/java/testmod/tests/ReplacementsTests.java new file mode 100644 index 0000000..784174d --- /dev/null +++ b/Common/src/test/java/testmod/tests/ReplacementsTests.java @@ -0,0 +1,40 @@ +package testmod.tests; + +import com.almostreliable.unified.config.PlaceholderConfig; +import com.almostreliable.unified.utils.JsonUtils; +import com.google.gson.JsonObject; +import testmod.gametest_core.SimpleGameTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ReplacementsTests { + + private static final JsonObject INFLATE_PLACEHOLDERS = JsonUtils.readFromString(""" + { + "type": [ + "gems", + "rods", + "raw_materials" + ], + "material": [ + "iron", + "gold" + ] + } + """, JsonObject.class); + + @SimpleGameTest + public void testInflate() { + var placeholderConfig = PlaceholderConfig.SERIALIZER.handleDeserialization(INFLATE_PLACEHOLDERS); + var result = placeholderConfig.apply("c:{type}/{material}"); + + assertEquals(6, result.size()); + assertTrue(result.contains("c:gems/iron")); + assertTrue(result.contains("c:gems/gold")); + assertTrue(result.contains("c:rods/iron")); + assertTrue(result.contains("c:rods/gold")); + assertTrue(result.contains("c:raw_materials/iron")); + assertTrue(result.contains("c:raw_materials/gold")); + } +} diff --git a/Common/src/test/java/testmod/tests/ShapedRecipeUnifierTests.java b/Common/src/test/java/testmod/tests/ShapedRecipeUnifierTests.java new file mode 100644 index 0000000..c8cb481 --- /dev/null +++ b/Common/src/test/java/testmod/tests/ShapedRecipeUnifierTests.java @@ -0,0 +1,58 @@ +package testmod.tests; + +import com.almostreliable.unified.api.unification.bundled.ShapedRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertUnify; + +public class ShapedRecipeUnifierTests { + + @SimpleGameTest + public void test() { + assertUnify(ShapedRecipeUnifier.INSTANCE, """ + { + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "#": { + "item": "minecraft:stick" + }, + "X": { + "item": "minecraft:test_item" + } + }, + "pattern": [ + "XXX", + " # ", + " # " + ], + "result": { + "item": "minecraft:test_item" + }, + "show_notification": true + } + """, """ + { + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "#": { + "item": "minecraft:stick" + }, + "X": { + "tag": "testmod:test_tag" + } + }, + "pattern": [ + "XXX", + " # ", + " # " + ], + "result": { + "item": "testmod:test_item" + }, + "show_notification": true + } + """); + } +} diff --git a/Common/src/test/java/testmod/tests/SmithingRecipeUnifierTest.java b/Common/src/test/java/testmod/tests/SmithingRecipeUnifierTest.java new file mode 100644 index 0000000..c013562 --- /dev/null +++ b/Common/src/test/java/testmod/tests/SmithingRecipeUnifierTest.java @@ -0,0 +1,80 @@ +package testmod.tests; + +import com.almostreliable.unified.api.unification.bundled.SmithingRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertUnify; + +public class SmithingRecipeUnifierTest { + + public static final RecipeUnifier UNIFIER = new SmithingRecipeUnifier(); + + @SimpleGameTest + public void testTrim() { + assertUnify(UNIFIER, """ + { + "type": "minecraft:smithing_trim", + "addition": { + "item": "minecraft:test_item" + }, + "base": { + "item": "minecraft:test_item" + }, + "template": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "minecraft:smithing_trim", + "addition": { + "tag": "testmod:test_tag" + }, + "base": { + "tag": "testmod:test_tag" + }, + "template": { + "tag": "testmod:test_tag" + } + } + """); + } + + @SimpleGameTest + public void testTransform() { + assertUnify(UNIFIER, """ + { + "type": "minecraft:smithing_transform", + "addition": { + "item": "minecraft:test_item" + }, + "base": { + "item": "minecraft:test_item" + }, + "result": { + "item": "minecraft:test_item" + }, + "template": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "minecraft:smithing_transform", + "addition": { + "tag": "testmod:test_tag" + }, + "base": { + "tag": "testmod:test_tag" + }, + "result": { + "item": "testmod:test_item" + }, + "template": { + "tag": "testmod:test_tag" + } + } + """); + } +} diff --git a/Common/src/test/java/testmod/tests/UnificationHandlerTests.java b/Common/src/test/java/testmod/tests/UnificationHandlerTests.java new file mode 100644 index 0000000..6a38816 --- /dev/null +++ b/Common/src/test/java/testmod/tests/UnificationHandlerTests.java @@ -0,0 +1,160 @@ +package testmod.tests; + +import com.almostreliable.unified.api.unification.ModPriorities; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.unification.ModPrioritiesImpl; +import com.almostreliable.unified.unification.UnificationLookupImpl; +import net.minecraft.Util; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import testmod.TestUtils; +import testmod.gametest_core.SimpleGameTest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnificationHandlerTests { + + private UnificationLookup createHandler(ModPriorities modPriorities) { + return new UnificationLookupImpl.Builder() + .put(TestUtils.itemTag("testmod:ingots/osmium"), + ResourceLocation.parse("minecraft:osmium_ingot"), + ResourceLocation.parse("mekanism:osmium_ingot"), + ResourceLocation.parse("thermal:osmium_ingot")) + .put(TestUtils.itemTag("testmod:ingots/cobalt"), + ResourceLocation.parse("minecraft:cobalt_ingot"), + ResourceLocation.parse("thermal:cobalt_ingot")) + .put(TestUtils.itemTag("testmod:ingots/electrum"), + ResourceLocation.parse("mekanism:electrum_ingot"), + ResourceLocation.parse("create:electrum_ingot"), + ResourceLocation.parse("thermal:electrum_ingot")) + .build(modPriorities, TestUtils.EMPTY_STONE_VARIANTS, TestUtils.EMPTY_TAG_SUBSTITUTIONS); + } + + @SimpleGameTest + public void testTagTargetItem() { + ArrayList modList = new ArrayList<>(List.of("ae2", "mekanism", "thermal", "create")); + ModPrioritiesImpl modPriorities = new ModPrioritiesImpl( + modList, + new HashMap<>() + ); + + var rm = createHandler(modPriorities); + + + assertEquals(ResourceLocation.parse("mekanism:osmium_ingot"), + rm.getTagTargetItem(TestUtils.itemTag("testmod:ingots/osmium")).id(), + "Osmium ingot from mekanism should be target"); + + assertNull(rm.getTagTargetItem(TestUtils.itemTag("testmod:not_exist/osmium")), + "Tag not found should return null"); + + assertEquals(ResourceLocation.parse("thermal:cobalt_ingot"), + rm.getTagTargetItem(TestUtils.itemTag("testmod:ingots/cobalt")).id(), + "Cobalt ingot from mekanism should be target"); + + assertNull(rm.getTagTargetItem(TestUtils.itemTag("testmod:not_exist/cobalt")), + "Tag not found should return null"); + + // Now we remove mekanism from modList. + // After that `getTagTargetItem` should return the thermal ingot, as AE2 still does not have one. + modList.remove("mekanism"); + assertEquals(ResourceLocation.parse("thermal:osmium_ingot"), + rm.getTagTargetItem(TestUtils.itemTag("testmod:ingots/osmium")).id(), + "Osmium ingot from thermal should now be target"); + assertEquals(ResourceLocation.parse("thermal:cobalt_ingot"), + rm.getTagTargetItem(TestUtils.itemTag("testmod:ingots/cobalt")).id(), + "Cobalt ingot from thermal should be target"); + } + + @SimpleGameTest + public void testTagTargetItemWithOverride() { + ModPrioritiesImpl modPriorities = new ModPrioritiesImpl( + List.of("ae2", "mekanism", "thermal", "create"), + Util.make(new HashMap<>(), + m -> m.put(TestUtils.itemTag("testmod:ingots/electrum"), "create")) + ); + + var rm = createHandler(modPriorities); + + assertEquals(ResourceLocation.parse("create:electrum_ingot"), + rm.getTagTargetItem(TestUtils.itemTag("testmod:ingots/electrum")).id(), + "Electrum ingot from create should be target as it is overridden by priorities"); + + // but for osmium it's the default behavior + assertEquals(ResourceLocation.parse("mekanism:osmium_ingot"), + rm.getTagTargetItem(TestUtils.itemTag("testmod:ingots/osmium")).id(), + "Osmium ingot from mekanism should be target"); + } + + @SimpleGameTest + public void testRelevantItemTag() { + ModPrioritiesImpl modPriorities = new ModPrioritiesImpl( + List.of("ae2", "mekanism", "thermal", "create"), + new HashMap<>() + ); + + var rm = createHandler(modPriorities); + + assertEquals(TestUtils.itemTag("testmod:ingots/osmium"), + rm.getRelevantItemTag(ResourceLocation.parse("mekanism:osmium_ingot"))); + assertEquals(TestUtils.itemTag("testmod:ingots/cobalt"), + rm.getRelevantItemTag(ResourceLocation.parse("thermal:cobalt_ingot"))); + assertEquals(TestUtils.itemTag("testmod:ingots/electrum"), + rm.getRelevantItemTag(ResourceLocation.parse("create:electrum_ingot"))); + + assertNull(rm.getRelevantItemTag(ResourceLocation.parse("not_existing_mod:osmium_ingot"))); + } + + @SimpleGameTest + public void testReplacementForItem() { + ModPrioritiesImpl modPriorities = new ModPrioritiesImpl( + List.of("ae2", "mekanism", "thermal", "create"), + new HashMap<>() + ); + + var rm = createHandler(modPriorities); + + assertEquals(ResourceLocation.parse("mekanism:osmium_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("mekanism:osmium_ingot")).id()); + assertEquals(ResourceLocation.parse("mekanism:osmium_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("minecraft:osmium_ingot")).id()); + assertEquals(ResourceLocation.parse("mekanism:osmium_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("thermal:osmium_ingot")).id()); + + assertEquals(ResourceLocation.parse("thermal:cobalt_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("thermal:cobalt_ingot")).id()); + assertEquals(ResourceLocation.parse("thermal:cobalt_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("minecraft:cobalt_ingot")).id()); + + assertEquals(ResourceLocation.parse("mekanism:electrum_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("create:electrum_ingot")).id()); + assertEquals(ResourceLocation.parse("mekanism:electrum_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("mekanism:electrum_ingot")).id()); + assertEquals(ResourceLocation.parse("mekanism:electrum_ingot"), + rm.getVariantItemTarget(ResourceLocation.parse("thermal:electrum_ingot")).id()); + } + + @SimpleGameTest + public void testItemInUnifiedIngredient() { + var rm = new UnificationLookupImpl.Builder() + .put(TestUtils.itemTag("c:tools"), Items.IRON_SWORD, Items.IRON_PICKAXE, Items.IRON_SHOVEL) + .build(TestUtils.EMPTY_MOD_PRIORITIES, + TestUtils.EMPTY_STONE_VARIANTS, + TestUtils.EMPTY_TAG_SUBSTITUTIONS + ); + + Ingredient ingredient = Ingredient.of(Items.IRON_SWORD); + + // Shovel is part of `minecraft:tools` and part of our created tag map + assertTrue(rm.isUnifiedIngredientItem(ingredient, Items.IRON_SHOVEL.getDefaultInstance()), + "SHOVEL is in our created tag map"); + + assertFalse(rm.isUnifiedIngredientItem(ingredient, Items.CARROT.getDefaultInstance()), + "CARROT is not part of `minecraft:tools`"); + } +} diff --git a/Common/src/test/java/testmod/tests/UnifyTests.java b/Common/src/test/java/testmod/tests/UnifyTests.java new file mode 100644 index 0000000..c86073e --- /dev/null +++ b/Common/src/test/java/testmod/tests/UnifyTests.java @@ -0,0 +1,127 @@ +package testmod.tests; + +import com.almostreliable.unified.api.unification.ModPriorities; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.unification.ModPrioritiesImpl; +import com.almostreliable.unified.unification.UnificationLookupImpl; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import testmod.gametest_core.SimpleGameTest; + +import java.util.HashMap; +import java.util.List; + +import static testmod.TestUtils.*; + +public class UnifyTests { + + private static final ResourceLocation TEST_ID = ResourceLocation.fromNamespaceAndPath("testmod", "test_recipe"); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public static final ModPriorities MOD_PRIORITIES = new ModPrioritiesImpl(List.of("minecraft", + "mekanism", + "thermal", + "create"), new HashMap<>()); + + public static UnificationLookup unificationLookup() { + return new UnificationLookupImpl.Builder() + .put(itemTag("testmod:ingots/iron"), Items.IRON_INGOT) + .build(MOD_PRIORITIES, EMPTY_STONE_VARIANTS, EMPTY_TAG_SUBSTITUTIONS); + } + + private static JsonObject json(String str) { + return GSON.fromJson(str, JsonObject.class); + } + + @SimpleGameTest + public void test() { + var rm = unificationLookup(); + var recipe = json(""" + { + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "#": { + "item": "minecraft:stick" + }, + "X": { + "item": "minecraft:iron_ingot" + } + }, + "pattern": [ + "X", + "#", + "#" + ], + "result": { + "item": "minecraft:iron_shovel" + }, + "show_notification": true + } + """); + + } + + @SimpleGameTest + public void test2() { + + } + + @SimpleGameTest + public void test3() { + + } + + @SimpleGameTest + public void test4() { + + } + + @SimpleGameTest + public void test5() { + + } + + @SimpleGameTest + public void test6() { + + } + + @SimpleGameTest + public void test7() { + + } + + @SimpleGameTest + public void test8() { + + } + + @SimpleGameTest + public void test9() { + + } + + @SimpleGameTest + public void test10() { + + } + + @SimpleGameTest + public void test11() { + + } + + @SimpleGameTest + public void test12() { + + } + + @SimpleGameTest + public void test13() { + + } +} diff --git a/Common/src/test/java/testmod/tests/core/TagInheritanceTests.java b/Common/src/test/java/testmod/tests/core/TagInheritanceTests.java new file mode 100644 index 0000000..329fa84 --- /dev/null +++ b/Common/src/test/java/testmod/tests/core/TagInheritanceTests.java @@ -0,0 +1,43 @@ +package testmod.tests.core; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.BeaconMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BeaconBlockEntity; +import testmod.gametest_core.AlmostGameTestHelper; +import testmod.gametest_core.SimpleGameTest; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TagInheritanceTests { + + @SimpleGameTest + public void testTagInheritance(AlmostGameTestHelper helper) { + BlockPos pos = new BlockPos(1, 2, 1); + helper.setBlock(pos, Blocks.BEACON); + BeaconBlockEntity be = helper.getBlockEntity(pos, BeaconBlockEntity.class); + Player player = helper.makeMockPlayer(GameType.SURVIVAL); + var menu = (BeaconMenu) be.createMenu(42, player.getInventory(), player); + assert menu != null; + Slot slot = menu.getSlot(0); + + assertFalse(slot.mayPlace(Items.STICK.getDefaultInstance())); + + // Both mod a and mod b silver should be placeable because of default set through `tags` json + var mod_a_silver = BuiltInRegistries.ITEM.get(ResourceLocation.parse("mod_a:silver_ore")); + assertTrue(slot.mayPlace(mod_a_silver.getDefaultInstance())); + var mod_b_silver = BuiltInRegistries.ITEM.get(ResourceLocation.parse("mod_b:silver_ore")); + assertTrue(slot.mayPlace(mod_b_silver.getDefaultInstance())); + + // mod c silver should be placeable because of tag inheritance + var mod_c_silver = BuiltInRegistries.ITEM.get(ResourceLocation.parse("mod_c:silver_ore")); + assertTrue(slot.mayPlace(mod_c_silver.getDefaultInstance())); + } +} diff --git a/Common/src/test/java/testmod/tests/core/TagSubstitutionTests.java b/Common/src/test/java/testmod/tests/core/TagSubstitutionTests.java new file mode 100644 index 0000000..52e0fd5 --- /dev/null +++ b/Common/src/test/java/testmod/tests/core/TagSubstitutionTests.java @@ -0,0 +1,42 @@ +package testmod.tests.core; + +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.unification.UnificationEntry; +import com.almostreliable.unified.api.unification.UnificationLookup; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import testmod.gametest_core.SimpleGameTest; + +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TagSubstitutionTests { + + /** + * See `tags.json` test config. + * mod_a:silver_ore and mod_b:silver_ore both have the tag `c:ores/silver`, while mod_c:silver_ore only has the `c:silver_ores` tag. + *

+ * Through the substitution system, we tell AU that `c:ores/silver` should own `c:silver_ores`. Which will result into mod_c:silver_ore is now part of `c:ores/silver` + */ + @SimpleGameTest + public void checkSilverSubstitutions() { + ResourceLocation silverOreId = ResourceLocation.parse("mod_c:silver_ore"); + Item item = BuiltInRegistries.ITEM.get(silverOreId); + Set> itemTags = item.builtInRegistryHolder().tags().collect(Collectors.toSet()); + TagKey silverTag = TagKey.create(Registries.ITEM, ResourceLocation.parse("c:ores/silver")); + assertTrue(itemTags.contains(silverTag)); + + UnificationLookup unificationLookup = AlmostUnified.INSTANCE.getRuntimeOrThrow().getUnificationLookup(); + TagKey unifyTag = unificationLookup.getRelevantItemTag(silverOreId); + assertEquals(silverTag, unifyTag); + UnificationEntry silverOreEntry = unificationLookup.getTagTargetItem(silverTag); + assertEquals(silverOreId, silverOreEntry.id()); + + } +} diff --git a/Common/src/test/java/testmod/tests/core/package-info.java b/Common/src/test/java/testmod/tests/core/package-info.java new file mode 100644 index 0000000..d7efa3e --- /dev/null +++ b/Common/src/test/java/testmod/tests/core/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package testmod.tests.core; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/test/java/testmod/tests/package-info.java b/Common/src/test/java/testmod/tests/package-info.java new file mode 100644 index 0000000..0bf730d --- /dev/null +++ b/Common/src/test/java/testmod/tests/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package testmod.tests; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Common/src/test/resources/data/c/tags/item/ingots/osmium.json b/Common/src/test/resources/data/c/tags/item/ingots/osmium.json new file mode 100644 index 0000000..dd42905 --- /dev/null +++ b/Common/src/test/resources/data/c/tags/item/ingots/osmium.json @@ -0,0 +1,8 @@ +{ + "values": [ + "testmod:osmium_ingot", + "meka_fake:osmium_ingot", + "ie_fake:osmium_ingot", + "thermal_fake:osmium_ingot" + ] +} diff --git a/Common/src/test/resources/data/c/tags/item/ores/osmium.json b/Common/src/test/resources/data/c/tags/item/ores/osmium.json new file mode 100644 index 0000000..4519fe2 --- /dev/null +++ b/Common/src/test/resources/data/c/tags/item/ores/osmium.json @@ -0,0 +1,8 @@ +{ + "values": [ + "testmod:osmium_ore", + "meka_fake:osmium_ore", + "ie_fake:osmium_ore", + "thermal_fake:osmium_ore" + ] +} diff --git a/Common/src/test/resources/data/c/tags/item/ores/silver.json b/Common/src/test/resources/data/c/tags/item/ores/silver.json new file mode 100644 index 0000000..fe35f86 --- /dev/null +++ b/Common/src/test/resources/data/c/tags/item/ores/silver.json @@ -0,0 +1,6 @@ +{ + "values": [ + "mod_a:silver_ore", + "mod_b:silver_ore" + ] +} diff --git a/Common/src/test/resources/data/c/tags/item/silver_ores.json b/Common/src/test/resources/data/c/tags/item/silver_ores.json new file mode 100644 index 0000000..ed1ea4f --- /dev/null +++ b/Common/src/test/resources/data/c/tags/item/silver_ores.json @@ -0,0 +1,5 @@ +{ + "values": [ + "mod_c:silver_ore" + ] +} diff --git a/Common/src/test/resources/data/ie_fake/loot_table/blocks/osmium_ore.json b/Common/src/test/resources/data/ie_fake/loot_table/blocks/osmium_ore.json new file mode 100644 index 0000000..15eed91 --- /dev/null +++ b/Common/src/test/resources/data/ie_fake/loot_table/blocks/osmium_ore.json @@ -0,0 +1,52 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:alternatives", + "children": [ + { + "type": "minecraft:item", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "predicates": { + "minecraft:enchantments": [ + { + "enchantment": "minecraft:silk_touch", + "levels": { + "min": 1 + } + } + ] + } + } + } + ], + "name": "ie_fake:osmium_ore" + }, + { + "type": "minecraft:item", + "functions": [ + { + "enchantment": "minecraft:fortune", + "formula": "minecraft:ore_drops", + "function": "minecraft:apply_bonus" + }, + { + "function": "minecraft:explosion_decay" + } + ], + "name": "ie_fake:osmium_ingot" + } + ] + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "minecraft:blocks/iron_ore" +} diff --git a/Common/src/test/resources/data/meka_fake/loot_table/blocks/osmium_ore.json b/Common/src/test/resources/data/meka_fake/loot_table/blocks/osmium_ore.json new file mode 100644 index 0000000..460d48d --- /dev/null +++ b/Common/src/test/resources/data/meka_fake/loot_table/blocks/osmium_ore.json @@ -0,0 +1,52 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:alternatives", + "children": [ + { + "type": "minecraft:item", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "predicates": { + "minecraft:enchantments": [ + { + "enchantment": "minecraft:silk_touch", + "levels": { + "min": 1 + } + } + ] + } + } + } + ], + "name": "meka_fake:osmium_ore" + }, + { + "type": "minecraft:item", + "functions": [ + { + "enchantment": "minecraft:fortune", + "formula": "minecraft:ore_drops", + "function": "minecraft:apply_bonus" + }, + { + "function": "minecraft:explosion_decay" + } + ], + "name": "meka_fake:osmium_ingot" + } + ] + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "minecraft:blocks/iron_ore" +} diff --git a/Common/src/test/resources/data/minecraft/tags/block/mineable/pickaxe.json b/Common/src/test/resources/data/minecraft/tags/block/mineable/pickaxe.json new file mode 100644 index 0000000..4519fe2 --- /dev/null +++ b/Common/src/test/resources/data/minecraft/tags/block/mineable/pickaxe.json @@ -0,0 +1,8 @@ +{ + "values": [ + "testmod:osmium_ore", + "meka_fake:osmium_ore", + "ie_fake:osmium_ore", + "thermal_fake:osmium_ore" + ] +} diff --git a/Common/src/test/resources/data/minecraft/tags/item/beacon_payment_items.json b/Common/src/test/resources/data/minecraft/tags/item/beacon_payment_items.json new file mode 100644 index 0000000..fe35f86 --- /dev/null +++ b/Common/src/test/resources/data/minecraft/tags/item/beacon_payment_items.json @@ -0,0 +1,6 @@ +{ + "values": [ + "mod_a:silver_ore", + "mod_b:silver_ore" + ] +} diff --git a/Common/src/test/resources/data/testmod/loot_table/blocks/osmium_ore.json b/Common/src/test/resources/data/testmod/loot_table/blocks/osmium_ore.json new file mode 100644 index 0000000..a709045 --- /dev/null +++ b/Common/src/test/resources/data/testmod/loot_table/blocks/osmium_ore.json @@ -0,0 +1,52 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:alternatives", + "children": [ + { + "type": "minecraft:item", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "predicates": { + "minecraft:enchantments": [ + { + "enchantment": "minecraft:silk_touch", + "levels": { + "min": 1 + } + } + ] + } + } + } + ], + "name": "testmod:osmium_ore" + }, + { + "type": "minecraft:item", + "functions": [ + { + "enchantment": "minecraft:fortune", + "formula": "minecraft:ore_drops", + "function": "minecraft:apply_bonus" + }, + { + "function": "minecraft:explosion_decay" + } + ], + "name": "testmod:osmium_ingot" + } + ] + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "minecraft:blocks/iron_ore" +} diff --git a/Common/src/test/resources/data/thermal_fake/loot_table/blocks/osmium_ore.json b/Common/src/test/resources/data/thermal_fake/loot_table/blocks/osmium_ore.json new file mode 100644 index 0000000..10373b0 --- /dev/null +++ b/Common/src/test/resources/data/thermal_fake/loot_table/blocks/osmium_ore.json @@ -0,0 +1,52 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:alternatives", + "children": [ + { + "type": "minecraft:item", + "conditions": [ + { + "condition": "minecraft:match_tool", + "predicate": { + "predicates": { + "minecraft:enchantments": [ + { + "enchantment": "minecraft:silk_touch", + "levels": { + "min": 1 + } + } + ] + } + } + } + ], + "name": "thermal_fake:osmium_ore" + }, + { + "type": "minecraft:item", + "functions": [ + { + "enchantment": "minecraft:fortune", + "formula": "minecraft:ore_drops", + "function": "minecraft:apply_bonus" + }, + { + "function": "minecraft:explosion_decay" + } + ], + "name": "thermal_fake:osmium_ingot" + } + ] + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "minecraft:blocks/iron_ore" +} diff --git a/Common/src/test/resources/testmod.mixins.json b/Common/src/test/resources/testmod.mixins.json new file mode 100644 index 0000000..8623bf2 --- /dev/null +++ b/Common/src/test/resources/testmod.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8.5", + "package": "testmod.gametest_core.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "GameTestHelperAccessor", + "GameTestRegistryAccessor", + "ItemTooltipMixin" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/Fabric/build.gradle.kts b/Fabric/build.gradle.kts index e72c5b0..c51d321 100644 --- a/Fabric/build.gradle.kts +++ b/Fabric/build.gradle.kts @@ -2,8 +2,13 @@ val minecraftVersion: String by project val fabricLoaderVersion: String by project val fabricApiVersion: String by project val fabricRecipeViewer: String by project +val enableRuntimeRecipeViewer: String by project val jeiVersion: String by project val reiVersion: String by project +val emiVersion: String by project + +val common by configurations +val shadowCommon by configurations plugins { id("com.github.johnrengelman.shadow") version "8.1.1" @@ -21,9 +26,6 @@ loom { } } -val common by configurations -val shadowCommon by configurations - dependencies { // loader modImplementation("net.fabricmc:fabric-loader:$fabricLoaderVersion") @@ -32,19 +34,24 @@ dependencies { // common module common(project(":Common", "namedElements")) { isTransitive = false } shadowCommon(project(":Common", "transformProductionFabric")) { isTransitive = false } + testImplementation(project(":Common", "namedElements")) - // compile time mods + // compile time modCompileOnly("mezz.jei:jei-$minecraftVersion-fabric-api:$jeiVersion") // required for common jei plugin modCompileOnly("me.shedaniel:RoughlyEnoughItems-api-fabric:$reiVersion") // required for common rei plugin + modCompileOnly("dev.emi:emi-fabric:$emiVersion+$minecraftVersion:api") // required for common emi plugin - // runtime dependencies - modLocalRuntime( - when (fabricRecipeViewer) { - "jei" -> "mezz.jei:jei-$minecraftVersion-fabric:$jeiVersion" - "rei" -> "me.shedaniel:RoughlyEnoughItems-fabric:$reiVersion" - else -> throw GradleException("Invalid fabricRecipeViewer value: $fabricRecipeViewer") - } - ) + // runtime + if (enableRuntimeRecipeViewer == "true") { + modLocalRuntime( + when (fabricRecipeViewer) { + "jei" -> "mezz.jei:jei-$minecraftVersion-fabric:$jeiVersion" + "rei" -> "me.shedaniel:RoughlyEnoughItems-fabric:$reiVersion" + "emi" -> "dev.emi:emi-fabric:$emiVersion+$minecraftVersion" + else -> throw GradleException("Invalid fabricRecipeViewer value: $fabricRecipeViewer") + } + ) + } } /** diff --git a/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedFabric.java b/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedFabric.java index 560e88d..c981bb7 100644 --- a/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedFabric.java +++ b/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedFabric.java @@ -1,17 +1,50 @@ package com.almostreliable.unified; -import com.almostreliable.unified.recipe.ClientRecipeTracker; +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.compat.PluginManager; +import com.almostreliable.unified.compat.viewer.ClientRecipeTracker; import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; +import java.util.ArrayList; +import java.util.List; + public class AlmostUnifiedFabric implements ModInitializer { @Override public void onInitialize() { - if (!AlmostUnified.getStartupConfig().isServerOnly()) { - Registry.register(BuiltInRegistries.RECIPE_SERIALIZER, ClientRecipeTracker.ID, ClientRecipeTracker.SERIALIZER); + if (!AlmostUnifiedCommon.STARTUP_CONFIG.isServerOnly()) { + Registry.register( + BuiltInRegistries.RECIPE_SERIALIZER, + ClientRecipeTracker.ID, + ClientRecipeTracker.SERIALIZER + ); Registry.register(BuiltInRegistries.RECIPE_TYPE, ClientRecipeTracker.ID, ClientRecipeTracker.TYPE); } + + initializePluginManager(); + } + + private static void initializePluginManager() { + List plugins = new ArrayList<>(); + var entrypointContainers = FabricLoader.getInstance() + .getEntrypointContainers(ModConstants.ALMOST_UNIFIED, AlmostUnifiedPlugin.class); + + for (var entrypointContainer : entrypointContainers) { + try { + plugins.add(entrypointContainer.getEntrypoint()); + } catch (Exception e) { + AlmostUnifiedCommon.LOGGER.error( + "Failed to load plugin for mod {}.", + entrypointContainer.getProvider().getMetadata().getName(), + e + ); + } + } + + PluginManager.init(plugins); } } diff --git a/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformFabric.java b/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformFabric.java index 5c07663..c6cd72c 100644 --- a/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformFabric.java +++ b/Fabric/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformFabric.java @@ -1,20 +1,11 @@ package com.almostreliable.unified; -import com.almostreliable.unified.api.ModConstants; -import com.almostreliable.unified.compat.AdAstraRecipeUnifier; -import com.almostreliable.unified.compat.AmethystImbuementRecipeUnifier; -import com.almostreliable.unified.compat.GregTechModernRecipeUnifier; -import com.almostreliable.unified.compat.ModernIndustrializationRecipeUnifier; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; -import com.almostreliable.unified.utils.UnifyTag; +import com.almostreliable.unified.api.constant.ModConstants; import com.google.auto.service.AutoService; import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.world.item.Item; import java.nio.file.Path; -import java.util.List; -import java.util.Set; @AutoService(AlmostUnifiedPlatform.class) public class AlmostUnifiedPlatformFabric implements AlmostUnifiedPlatform { @@ -36,24 +27,16 @@ public class AlmostUnifiedPlatformFabric implements AlmostUnifiedPlatform { @Override public Path getConfigPath() { - return FabricLoader.getInstance().getConfigDir().resolve(BuildConfig.MOD_ID); + return FabricLoader.getInstance().getConfigDir().resolve(ModConstants.ALMOST_UNIFIED); } @Override - public Path getLogPath() { - return FabricLoader.getInstance().getGameDir().resolve("logs").resolve(BuildConfig.MOD_ID); - } - - @Override - public void bindRecipeHandlers(RecipeHandlerFactory factory) { - factory.registerForMod(ModConstants.AD_ASTRA, new AdAstraRecipeUnifier()); - factory.registerForMod(ModConstants.AMETHYST_IMBUEMENT, new AmethystImbuementRecipeUnifier()); - factory.registerForMod(ModConstants.GREGTECH_MODERN, new GregTechModernRecipeUnifier()); - factory.registerForMod(ModConstants.MODERN_INDUSTRIALIZATION, new ModernIndustrializationRecipeUnifier()); - } - - @Override - public Set> getStoneStrataTags(List stoneStrataIds) { - return Set.of(); + public Path getDebugLogPath() { + return FabricLoader + .getInstance() + .getGameDir() + .resolve("logs") + .resolve(ModConstants.ALMOST_UNIFIED) + .resolve("debug"); } } diff --git a/Fabric/src/main/java/com/almostreliable/unified/compat/AmethystImbuementRecipeUnifier.java b/Fabric/src/main/java/com/almostreliable/unified/compat/AmethystImbuementRecipeUnifier.java deleted file mode 100644 index e825eaf..0000000 --- a/Fabric/src/main/java/com/almostreliable/unified/compat/AmethystImbuementRecipeUnifier.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -import java.util.List; - -@SuppressWarnings("SpellCheckingInspection") -public class AmethystImbuementRecipeUnifier implements RecipeUnifier { - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - final List fields = List.of("imbueA", - "imbueB", - "imbueC", - "imbueD", - "craftA", - "craftB", - "craftC", - "craftD", - "craftE", - "craftF", - "craftG", - "craftH", - "craftI"); - - fields.forEach(field -> builder.put(field, (json, ctx) -> ctx.createIngredientReplacement(json))); - builder.put("resultA", (json, ctx) -> ctx.createResultReplacement(json)); - } -} diff --git a/Fabric/src/main/java/com/almostreliable/unified/compat/ModernIndustrializationRecipeUnifier.java b/Fabric/src/main/java/com/almostreliable/unified/compat/ModernIndustrializationRecipeUnifier.java deleted file mode 100644 index 1be11ae..0000000 --- a/Fabric/src/main/java/com/almostreliable/unified/compat/ModernIndustrializationRecipeUnifier.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -public class ModernIndustrializationRecipeUnifier implements RecipeUnifier { - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put(RecipeConstants.ITEM_INPUTS, (json, ctx) -> ctx.createIngredientReplacement(json)); - builder.put(RecipeConstants.ITEM_OUTPUTS, (json, ctx) -> ctx.createResultReplacement(json)); - } -} diff --git a/Fabric/src/main/java/com/almostreliable/unified/compat/unification/AmethystImbuementRecipeUnifier.java b/Fabric/src/main/java/com/almostreliable/unified/compat/unification/AmethystImbuementRecipeUnifier.java new file mode 100644 index 0000000..6f79a32 --- /dev/null +++ b/Fabric/src/main/java/com/almostreliable/unified/compat/unification/AmethystImbuementRecipeUnifier.java @@ -0,0 +1,32 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +public class AmethystImbuementRecipeUnifier implements RecipeUnifier { + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + + helper.unifyInputs( + recipe, + "imbueA", + "imbueB", + "imbueC", + "imbueD", + "craftA", + "craftB", + "craftC", + "craftD", + "craftE", + "craftF", + "craftG", + "craftH", + "craftI" + ); + + helper.unifyOutputs(recipe, "resultA"); + } +} diff --git a/Fabric/src/main/java/com/almostreliable/unified/core/FabricPlugin.java b/Fabric/src/main/java/com/almostreliable/unified/core/FabricPlugin.java new file mode 100644 index 0000000..43d30c8 --- /dev/null +++ b/Fabric/src/main/java/com/almostreliable/unified/core/FabricPlugin.java @@ -0,0 +1,22 @@ +package com.almostreliable.unified.core; + + +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import com.almostreliable.unified.compat.unification.AmethystImbuementRecipeUnifier; +import com.almostreliable.unified.utils.Utils; +import net.minecraft.resources.ResourceLocation; + +public class FabricPlugin implements AlmostUnifiedPlugin { + + @Override + public ResourceLocation getPluginId() { + return Utils.getRL("fabric"); + } + + @Override + public void registerRecipeUnifiers(RecipeUnifierRegistry registry) { + registry.registerForModId(ModConstants.AMETHYST_IMBUEMENT, new AmethystImbuementRecipeUnifier()); + } +} diff --git a/Fabric/src/main/resources/fabric.mod.json b/Fabric/src/main/resources/fabric.mod.json index a7cde67..e43fcfb 100644 --- a/Fabric/src/main/resources/fabric.mod.json +++ b/Fabric/src/main/resources/fabric.mod.json @@ -19,10 +19,17 @@ "com.almostreliable.unified.AlmostUnifiedFabric" ], "jei_mod_plugin": [ - "com.almostreliable.unified.compat.AlmostJEI" + "com.almostreliable.unified.compat.viewers.AlmostJEI" ], "rei_client": [ - "com.almostreliable.unified.compat.AlmostREI" + "com.almostreliable.unified.compat.viewers.AlmostREI" + ], + "emi": [ + "com.almostreliable.unified.compat.viewers.AlmostEMI" + ], + "almostunified": [ + "com.almostreliable.unified.core.CommonPlugin", + "com.almostreliable.unified.core.FabricPlugin" ] }, "mixins": [ @@ -35,6 +42,7 @@ }, "suggests": { "jei": ">=${jeiVersion}", - "roughlyenoughitems": ">=${reiVersion}" + "roughlyenoughitems": ">=${reiVersion}", + "emi": ">=${emiVersion}" } } diff --git a/Fabric/src/test/java/testmod/FabricTest.java b/Fabric/src/test/java/testmod/FabricTest.java new file mode 100644 index 0000000..92f2e03 --- /dev/null +++ b/Fabric/src/test/java/testmod/FabricTest.java @@ -0,0 +1,13 @@ +package testmod; + +import net.fabricmc.api.ModInitializer; +import testmod.gametest_core.GameTestLoader; +import testmod.tests.AmethystImbuementRecipeUnifierTests; + +public class FabricTest implements ModInitializer { + @Override + public void onInitialize() { + CommonTest.init("true".equals(System.getProperty("fabric-api.gametest"))); + GameTestLoader.registerProviders(AmethystImbuementRecipeUnifierTests.class); + } +} diff --git a/Fabric/src/test/java/testmod/tests/AmethystImbuementRecipeUnifierTests.java b/Fabric/src/test/java/testmod/tests/AmethystImbuementRecipeUnifierTests.java new file mode 100644 index 0000000..ab9c883 --- /dev/null +++ b/Fabric/src/test/java/testmod/tests/AmethystImbuementRecipeUnifierTests.java @@ -0,0 +1,110 @@ +package testmod.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.AmethystImbuementRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertUnify; + +public class AmethystImbuementRecipeUnifierTests { + public static final RecipeUnifier UNIFIER = new AmethystImbuementRecipeUnifier(); + + @SimpleGameTest + public void testImbuing() { + assertUnify(UNIFIER, """ + { + "type": "amethyst_imbuement:imbuing", + "imbueA": { + "item": "testmod:test_item" + }, + "imbueB": { + "item": "testmod:test_item" + }, + "imbueC": { + "item": "testmod:test_item" + }, + "imbueD": { + "item": "testmod:test_item" + }, + "craftA": { + "item": "testmod:test_item" + }, + "craftB": { + "item": "testmod:test_item" + }, + "craftC": { + "item": "testmod:test_item" + }, + "craftD": { + "item": "testmod:test_item" + }, + "craftE": { + "item": "testmod:test_item" + }, + "craftF": { + "item": "testmod:test_item" + }, + "craftG": { + "item": "testmod:test_item" + }, + "craftH": { + "item": "testmod:test_item" + }, + "craftI": { + "item": "testmod:test_item" + }, + "title": "Witches Orb", + "cost": 19, + "resultA": "minecraft:test_item", + "countA": 1 + } + """, """ + { + "type": "amethyst_imbuement:imbuing", + "imbueA": { + "tag": "testmod:test_tag" + }, + "imbueB": { + "tag": "testmod:test_tag" + }, + "imbueC": { + "tag": "testmod:test_tag" + }, + "imbueD": { + "tag": "testmod:test_tag" + }, + "craftA": { + "tag": "testmod:test_tag" + }, + "craftB": { + "tag": "testmod:test_tag" + }, + "craftC": { + "tag": "testmod:test_tag" + }, + "craftD": { + "tag": "testmod:test_tag" + }, + "craftE": { + "tag": "testmod:test_tag" + }, + "craftF": { + "tag": "testmod:test_tag" + }, + "craftG": { + "tag": "testmod:test_tag" + }, + "craftH": { + "tag": "testmod:test_tag" + }, + "craftI": { + "tag": "testmod:test_tag" + }, + "title": "Witches Orb", + "cost": 19, + "resultA": "testmod:test_item", + "countA": 1 + } + """); + } +} diff --git a/Fabric/src/test/resources/data/testmod/gametest/structure/empty_test_structure.snbt b/Fabric/src/test/resources/data/testmod/gametest/structure/empty_test_structure.snbt new file mode 100644 index 0000000..55f91cb --- /dev/null +++ b/Fabric/src/test/resources/data/testmod/gametest/structure/empty_test_structure.snbt @@ -0,0 +1,38 @@ +{ + DataVersion: 3120, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air" + ] +} diff --git a/Fabric/src/test/resources/fabric.mod.json b/Fabric/src/test/resources/fabric.mod.json new file mode 100644 index 0000000..84b8e71 --- /dev/null +++ b/Fabric/src/test/resources/fabric.mod.json @@ -0,0 +1,13 @@ +{ + "schemaVersion": 1, + "id": "testmod", + "version": "dev", + "name": "Test Mod", + "environment": "*", + "entrypoints": { + "main": [ + "testmod.FabricTest" + ] + }, + "mixins": ["testmod.mixins.json"] +} diff --git a/Forge/build.gradle.kts b/Forge/build.gradle.kts deleted file mode 100644 index 47f4f5e..0000000 --- a/Forge/build.gradle.kts +++ /dev/null @@ -1,89 +0,0 @@ -val minecraftVersion: String by project -val modId: String by project -val junitVersion: String by project -val forgeVersion: String by project -val forgeRecipeViewer: String by project -val jeiVersion: String by project -val reiVersion: String by project - -val extraModsPrefix = "extra-mods" - -plugins { - id("com.github.johnrengelman.shadow") version "8.1.1" -} - -architectury { - platformSetupLoomIde() - forge() -} - -loom { - if (project.findProperty("enableAccessWidener") == "true") { // optional property for `gradle.properties` - accessWidenerPath.set(project(":Common").loom.accessWidenerPath) - forge { - convertAccessWideners.set(true) - extraAccessWideners.add(loom.accessWidenerPath.get().asFile.name) - } - println("Access widener enabled for project ${project.name}. Access widener path: ${loom.accessWidenerPath.get()}") - } - - forge { - mixinConfigs("$modId-common.mixins.json" /*, "$modId-forge.mixins.json"*/) - } -} - -repositories { - flatDir { - name = extraModsPrefix - dir(file("$extraModsPrefix-$minecraftVersion")) - } -} - -val common by configurations -val shadowCommon by configurations -val commonTests: SourceSetOutput = project(":Common").sourceSets["test"].output - -dependencies { - // loader - forge("net.minecraftforge:forge:$minecraftVersion-$forgeVersion") - - // common module - common(project(":Common", "namedElements")) { isTransitive = false } - shadowCommon(project(":Common", "transformProductionForge")) { isTransitive = false } - - // compile time mods - modCompileOnly("mezz.jei:jei-$minecraftVersion-forge-api:$jeiVersion") { // required for common jei plugin - isTransitive = false // prevents breaking the forge runtime - } - modCompileOnly("me.shedaniel:RoughlyEnoughItems-forge:$reiVersion") // required for common rei plugin - - // runtime mods - when (forgeRecipeViewer) { - "jei" -> modLocalRuntime("mezz.jei:jei-$minecraftVersion-forge:$jeiVersion") { isTransitive = false } - "rei" -> modLocalRuntime("me.shedaniel:RoughlyEnoughItems-forge:$reiVersion") - else -> throw GradleException("Invalid forgeRecipeViewer value: $forgeRecipeViewer") - } - - /** - * helps to load mods in development through an extra directory - * sadly, this does not support transitive dependencies - */ - fileTree("$extraModsPrefix-$minecraftVersion") { include("**/*.jar") } - .forEach { f -> - val sepIndex = f.nameWithoutExtension.lastIndexOf('-') - if (sepIndex == -1) { - throw IllegalArgumentException("Invalid mod name: '${f.nameWithoutExtension}'. Expected format: 'modName-version.jar'") - } - val mod = f.nameWithoutExtension.substring(0, sepIndex) - val version = f.nameWithoutExtension.substring(sepIndex + 1) - println("Extra mod ${f.nameWithoutExtension} detected.") - "modLocalRuntime"("extra-mods:$mod:$version") - } - - - // tests - testImplementation(project(":Common")) - testImplementation(commonTests) - testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") -} diff --git a/Forge/gradle.properties b/Forge/gradle.properties deleted file mode 100644 index 41c7d18..0000000 --- a/Forge/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -loom.platform = forge -maven-publish-method = mavenForge diff --git a/Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedForge.java b/Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedForge.java deleted file mode 100644 index 737c774..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedForge.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.recipe.ClientRecipeTracker; -import net.minecraft.core.registries.Registries; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.RegisterEvent; - -@Mod(BuildConfig.MOD_ID) -public class AlmostUnifiedForge { - - public AlmostUnifiedForge() { - if (!AlmostUnified.getStartupConfig().isServerOnly()) { - var modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); - modEventBus.addListener((RegisterEvent event) -> { - if (event.getRegistryKey().equals(Registries.RECIPE_SERIALIZER)) { - ForgeRegistries.RECIPE_SERIALIZERS.register(ClientRecipeTracker.ID, ClientRecipeTracker.SERIALIZER); - } - if (event.getRegistryKey().equals(Registries.RECIPE_TYPE)) { - ForgeRegistries.RECIPE_TYPES.register(ClientRecipeTracker.ID, ClientRecipeTracker.TYPE); - } - }); - } - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformForge.java b/Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformForge.java deleted file mode 100644 index 047abf3..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformForge.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.almostreliable.unified; - -import com.almostreliable.unified.api.ModConstants; -import com.almostreliable.unified.compat.*; -import com.almostreliable.unified.recipe.unifier.RecipeHandlerFactory; -import com.almostreliable.unified.utils.UnifyTag; -import com.google.auto.service.AutoService; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.loading.FMLLoader; -import net.minecraftforge.fml.loading.FMLPaths; -import net.minecraftforge.fml.loading.LoadingModList; -import net.minecraftforge.fml.loading.moddiscovery.ModInfo; - -import java.nio.file.Path; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@AutoService(AlmostUnifiedPlatform.class) -public class AlmostUnifiedPlatformForge implements AlmostUnifiedPlatform { - - @Override - public Platform getPlatform() { - return Platform.FORGE; - } - - @Override - public boolean isModLoaded(String modId) { - if (ModList.get() == null) { - return LoadingModList.get().getMods().stream().map(ModInfo::getModId).anyMatch(modId::equals); - } - return ModList.get().isLoaded(modId); - } - - @Override - public boolean isClient() { - return FMLLoader.getDist() == Dist.CLIENT; - } - - @Override - public Path getConfigPath() { - return FMLPaths.CONFIGDIR.get().resolve(BuildConfig.MOD_ID); - } - - @Override - public Path getLogPath() { - return FMLPaths.GAMEDIR.get().resolve("logs").resolve(BuildConfig.MOD_ID); - } - - @Override - public void bindRecipeHandlers(RecipeHandlerFactory factory) { - factory.registerForMod(ModConstants.AD_ASTRA, new AdAstraRecipeUnifier()); - List.of( - ModConstants.ARS_CREO, - ModConstants.ARS_ELEMENTAL, - ModConstants.ARS_NOUVEAU, - ModConstants.ARS_SCALAES - ).forEach(modId -> factory.registerForMod(modId, new ArsNouveauRecipeUnifier())); - factory.registerForMod(ModConstants.CYCLIC, new CyclicRecipeUnifier()); - factory.registerForMod(ModConstants.ENDER_IO, new EnderIORecipeUnifier()); - factory.registerForMod(ModConstants.GREGTECH_MODERN, new GregTechModernRecipeUnifier()); - factory.registerForMod(ModConstants.IMMERSIVE_ENGINEERING, new ImmersiveEngineeringRecipeUnifier()); - factory.registerForMod(ModConstants.INTEGRATED_DYNAMICS, new IntegratedDynamicsRecipeUnifier()); - factory.registerForMod(ModConstants.MEKANISM, new MekanismRecipeUnifier()); - } - - @Override - public Set> getStoneStrataTags(List stoneStrataIds) { - return stoneStrataIds - .stream() - .map(id -> new ResourceLocation("forge", "ores_in_ground/" + id)) - .map(UnifyTag::item) - .collect(Collectors.toSet()); - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/ArsNouveauRecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/ArsNouveauRecipeUnifier.java deleted file mode 100644 index 9edaef2..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/compat/ArsNouveauRecipeUnifier.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import com.google.gson.JsonObject; - -import javax.annotation.Nullable; - -public class ArsNouveauRecipeUnifier implements RecipeUnifier { - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - // imbuement, enchanting apparatus, enchantment, reactive enchantment, spell write, armor upgrade - builder.forEachObject(RecipeConstants.PEDESTAL_ITEMS, this::createIngredientReplacement); - // glyph - builder.forEachObject(RecipeConstants.INPUT_ITEMS, this::createIngredientReplacement); - - // enchanting apparatus - builder.put(RecipeConstants.REAGENT, (json, ctx) -> ctx.createIngredientReplacement(json)); - } - - /** - * Handles ingredient replacements for JSON arrays with nested item JSON objects. - * - * @param json The JSON object to apply the replacement on. - * @param ctx The recipe context. - * @return The JSON object with the replacement applied, or {@code null} if there is no replacement. - */ - @Nullable - private JsonObject createIngredientReplacement(JsonObject json, RecipeContext ctx) { - var replacement = ctx.createIngredientReplacement(json.get(RecipeConstants.ITEM)); - if (replacement instanceof JsonObject item) { - json.add(RecipeConstants.ITEM, item); - return json; - } - return null; - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/CyclicRecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/CyclicRecipeUnifier.java deleted file mode 100644 index 2086481..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/compat/CyclicRecipeUnifier.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -public class CyclicRecipeUnifier implements RecipeUnifier { - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put(RecipeConstants.BONUS, (json, ctx) -> ctx.createResultReplacement(json)); - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/EnderIORecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/EnderIORecipeUnifier.java deleted file mode 100644 index 6f3fa40..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/compat/EnderIORecipeUnifier.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -public class EnderIORecipeUnifier implements RecipeUnifier { - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - // grinding ball - builder.put( - RecipeConstants.ITEM, - (json, ctx) -> ctx.createResultReplacement(json, false, RecipeConstants.ITEM) - ); - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/ImmersiveEngineeringRecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/ImmersiveEngineeringRecipeUnifier.java deleted file mode 100644 index 0b1bcb1..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/compat/ImmersiveEngineeringRecipeUnifier.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import javax.annotation.Nullable; -import java.util.List; - -public class ImmersiveEngineeringRecipeUnifier implements RecipeUnifier { - - private static final String BASE_INGREDIENT = "base_ingredient"; - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - List.of( - // alloy recipes, refinery - RecipeConstants.INPUT_0, - RecipeConstants.INPUT_1, - // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press - RecipeConstants.INPUT, - // arc furnace - RecipeConstants.ADDITIVES, - // refinery - RecipeConstants.CATALYST - ).forEach(key -> builder.put(key, this::createIngredientReplacement)); - - List.of( - RecipeConstants.RESULT, - RecipeConstants.RESULTS, - // arc furnace - RecipeConstants.SLAG - ).forEach(key -> builder.put(key, (json, ctx) -> - ctx.createResultReplacement(json, true, RecipeConstants.ITEM, BASE_INGREDIENT)) - ); - - // alloy recipes, crusher - builder.forEachObject(RecipeConstants.SECONDARIES, (json, ctx) -> { - var replacement = ctx.createResultReplacement(json.get(RecipeConstants.OUTPUT), true); - if (replacement instanceof JsonObject output) { - json.add(RecipeConstants.OUTPUT, output); - return json; - } - // noinspection ReturnOfNull - return null; - } - ); - } - - @Nullable - private JsonElement createIngredientReplacement(@Nullable JsonElement element, RecipeContext ctx) { - if (element instanceof JsonObject json && json.has(BASE_INGREDIENT)) { - return ctx.createIngredientReplacement(json.get(BASE_INGREDIENT)); - } - - return ctx.createIngredientReplacement(element); - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/IntegratedDynamicsRecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/IntegratedDynamicsRecipeUnifier.java deleted file mode 100644 index 28f47ae..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/compat/IntegratedDynamicsRecipeUnifier.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeContext; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import javax.annotation.Nullable; - -public class IntegratedDynamicsRecipeUnifier implements RecipeUnifier { - - private static final String ITEMS = "items"; - - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - builder.put(RecipeConstants.ITEM, this::createItemReplacement); - builder.put(RecipeConstants.RESULT, this::createResultReplacement); - } - - @Nullable - private JsonElement createItemReplacement(@Nullable JsonElement json, RecipeContext ctx) { - if (json instanceof JsonPrimitive jsonPrimitive) { - JsonObject jsonObject = new JsonObject(); - jsonObject.add(RecipeConstants.ITEM, jsonPrimitive); - return ctx.createIngredientReplacement(jsonObject); - } - - return ctx.createIngredientReplacement(json); - } - - @Nullable - private JsonElement createResultReplacement(@Nullable JsonElement json, RecipeContext ctx) { - if (json instanceof JsonObject jsonObject && jsonObject.get(ITEMS) instanceof JsonArray jsonArray) { - ctx.createResultReplacement(jsonArray); - } - - return null; - } -} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java b/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java deleted file mode 100644 index ef41291..0000000 --- a/Forge/src/main/java/com/almostreliable/unified/compat/MekanismRecipeUnifier.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.almostreliable.unified.compat; - -import com.almostreliable.unified.api.recipe.RecipeConstants; -import com.almostreliable.unified.api.recipe.RecipeUnifier; -import com.almostreliable.unified.api.recipe.RecipeUnifierBuilder; - -import java.util.List; - -public class MekanismRecipeUnifier implements RecipeUnifier { - @Override - public void collectUnifier(RecipeUnifierBuilder builder) { - List.of(RecipeConstants.MAIN_INPUT, RecipeConstants.ITEM_INPUT) - .forEach(key -> builder.put(key, (json, ctx) -> ctx.createIngredientReplacement(json))); - - List.of(RecipeConstants.MAIN_OUTPUT, RecipeConstants.ITEM_OUTPUT, RecipeConstants.SECONDARY_OUTPUT) - .forEach(key -> builder.put(key, (json, ctx) -> ctx.createResultReplacement(json))); - } -} diff --git a/Forge/src/test/java/com/almostreliable/unified/compat/ie/IERecipeUnifierTest.java b/Forge/src/test/java/com/almostreliable/unified/compat/ie/IERecipeUnifierTest.java deleted file mode 100644 index 1415a89..0000000 --- a/Forge/src/test/java/com/almostreliable/unified/compat/ie/IERecipeUnifierTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.almostreliable.unified.compat.ie; - -import com.google.gson.Gson; -import net.minecraft.resources.ResourceLocation; - -// TODO I BROKE THEM! NEED TO FIX -public class IERecipeUnifierTest { - public final Gson gson = new Gson(); - - private final ResourceLocation defaultRecipeId = new ResourceLocation("default_test_recipe"); - private final String simpleAlloyRecipe = """ - { - "type": "immersiveengineering:alloy", - "time": 200, - "result": { "count": 2, "base_ingredient": { "tag": "forge:ingots/electrum" } }, - "input0": { "tag": "forge:ingots/gold" }, - "input1": { "tag": "forge:ingots/silver" } - } - """; - -// @Test -// public void notMatching() { -// RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE, -// new IERecipeUnifier())); -// JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class); -// RecipeLink recipe = new RecipeLink(defaultRecipeId, alloy); -// transformer.unifyRecipe(recipe); -// assertFalse(recipe.isUnified(), "Nothing to transform, so it should be false"); -// } -// -// @Test -// public void resultTagMatches() { -// RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE, -// new IERecipeUnifier())); -// JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class); -// alloy -// .getAsJsonObject("result") -// .getAsJsonObject("base_ingredient") -// .addProperty("tag", TestUtils.BRONZE_ORES_TAG.location().toString()); -// RecipeLink recipe = new RecipeLink(defaultRecipeId, alloy); -// transformer.unifyRecipe(recipe); -// -// assertNotEquals(recipe.getUnified(), alloy, "Result should be different"); -// assertNotNull(recipe.getUnified(), "Result should not be null"); -// assertNull(JsonQuery.of(recipe.getUnified(), "result/base_ingredient/tag"), "Tag key should be removed"); -// assertEquals(JsonQuery.of(recipe.getUnified(), "result/base_ingredient/item").asString(), -// TestUtils.mod1RL("bronze_ore").toString(), -// "Result should be bronze_ore"); -// } -// -// @Test -// public void resultItemMatches() { -// RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE, -// new IERecipeUnifier())); -// JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class); -// alloy.getAsJsonObject("result").getAsJsonObject("base_ingredient").remove("tag"); -// alloy -// .getAsJsonObject("result") -// .getAsJsonObject("base_ingredient") -// .addProperty("item", TestUtils.mod3RL("bronze_ore").toString()); -// RecipeLink recipe = new RecipeLink(defaultRecipeId, alloy); -// transformer.unifyRecipe(recipe); -// -// assertNotEquals(recipe.getUnified(), alloy, "Result should be different"); -// assertNotNull(recipe.getUnified(), "Result should not be null"); -// assertEquals(JsonQuery.of(recipe.getUnified(), ("result/base_ingredient/item")).asString(), -// TestUtils.mod1RL("bronze_ore").toString(), -// "Transformer should replace bronze_ore from mod3 with bronze_ore from mod1"); -// } -// -// @Test -// public void inputAlloyItemMatches() { -// RecipeTransformer transformer = TestUtils.basicTransformer(f -> f.registerForMod(ModConstants.IE, -// new IERecipeUnifier())); -// JsonObject alloy = gson.fromJson(simpleAlloyRecipe, JsonObject.class); -// alloy.getAsJsonObject("result").getAsJsonObject("base_ingredient").remove("tag"); -// alloy -// .getAsJsonObject("result") -// .getAsJsonObject("base_ingredient") -// .addProperty("item", TestUtils.mod3RL("bronze_ore").toString()); -// RecipeLink recipe = new RecipeLink(defaultRecipeId, alloy); -// transformer.unifyRecipe(recipe); -// -// assertNotEquals(recipe.getUnified(), alloy, "Result should be different"); -// assertNotNull(recipe.getUnified(), "Result should not be null"); -// assertEquals(JsonQuery.of(recipe.getUnified(), ("result/base_ingredient/item")).asString(), -// TestUtils.mod1RL("bronze_ore").toString(), -// "Transformer should replace bronze_ore from mod3 with bronze_ore from mod1"); -// } -} diff --git a/NeoForge/build.gradle.kts b/NeoForge/build.gradle.kts new file mode 100644 index 0000000..c74c321 --- /dev/null +++ b/NeoForge/build.gradle.kts @@ -0,0 +1,80 @@ +val minecraftVersion: String by project +val junitVersion: String by project +val neoforgeVersion: String by project +val neoforgeRecipeViewer: String by project +val enableRuntimeRecipeViewer: String by project +val jeiVersion: String by project +val reiVersion: String by project +val emiVersion: String by project + +val common by configurations +val shadowCommon by configurations + +plugins { + id("com.github.johnrengelman.shadow") version "8.1.1" +} + +architectury { + platformSetupLoomIde() + neoForge() +} + +loom { + // load the test mod for test run configs exclusively + runs { + named("test_client") { + mods { + create("main") { + sourceSet(sourceSets.main.get()) + sourceSet(project(":Common").sourceSets.main.get()) + } + create("testmod") { + sourceSet(sourceSets.test.get()) + sourceSet(project(":Common").sourceSets.test.get()) + } + } + } + named("gametest") { + mods { + create("main") { + sourceSet(sourceSets.main.get()) + sourceSet(project(":Common").sourceSets.main.get()) + } + create("testmod") { + sourceSet(sourceSets.test.get()) + sourceSet(project(":Common").sourceSets.test.get()) + } + } + } + } +} + +dependencies { + // loader + neoForge("net.neoforged:neoforge:${neoforgeVersion}") + + // common module + common(project(":Common", "namedElements")) { isTransitive = false } + shadowCommon(project(":Common", "transformProductionNeoForge")) { isTransitive = false } + testImplementation(project(":Common", "namedElements")) + + // compile time + modCompileOnly("mezz.jei:jei-$minecraftVersion-neoforge-api:$jeiVersion") { // required for common jei plugin + isTransitive = false // prevents breaking the forge runtime + } + // TODO go back to API when solved: https://github.com/architectury/architectury-loom/issues/204 + modCompileOnly("me.shedaniel:RoughlyEnoughItems-neoforge:$reiVersion") // required for common rei plugin + modLocalRuntime("dev.architectury:architectury-neoforge:13.0.6") // TODO: Remove on new REI version + modCompileOnly("dev.emi:emi-neoforge:$emiVersion+$minecraftVersion:api") // required for common emi plugin + + // runtime + forgeRuntimeLibrary("org.junit.jupiter:junit-jupiter-api:$junitVersion") + if (enableRuntimeRecipeViewer == "true") { + when (neoforgeRecipeViewer) { + "jei" -> modLocalRuntime("mezz.jei:jei-$minecraftVersion-neoforge:$jeiVersion") { isTransitive = false } + "rei" -> modLocalRuntime("me.shedaniel:RoughlyEnoughItems-neoforge:$reiVersion") + "emi" -> modLocalRuntime("dev.emi:emi-neoforge:$emiVersion+$minecraftVersion") + else -> throw GradleException("Invalid forgeRecipeViewer value: $neoforgeRecipeViewer") + } + } +} diff --git a/NeoForge/gradle.properties b/NeoForge/gradle.properties new file mode 100644 index 0000000..ab13cad --- /dev/null +++ b/NeoForge/gradle.properties @@ -0,0 +1,2 @@ +loom.platform = neoforge +maven-publish-method = mavenNeoForge diff --git a/NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedNeoForge.java b/NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedNeoForge.java new file mode 100644 index 0000000..7546c9d --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedNeoForge.java @@ -0,0 +1,126 @@ +package com.almostreliable.unified; + +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.plugin.AlmostUnifiedNeoPlugin; +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.compat.PluginManager; +import com.almostreliable.unified.compat.viewer.ClientRecipeTracker; +import com.almostreliable.unified.core.AlmostUnifiedCommands; +import com.almostreliable.unified.unification.worldgen.WorldGenBiomeModifier; +import com.almostreliable.unified.unification.worldgen.WorldStripper; +import com.almostreliable.unified.utils.Utils; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModList; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.registries.RegisterEvent; +import org.objectweb.asm.Type; + +import javax.annotation.Nullable; +import java.util.*; + +@Mod(ModConstants.ALMOST_UNIFIED) +public class AlmostUnifiedNeoForge { + + public AlmostUnifiedNeoForge(IEventBus eventBus) { + eventBus.addListener(this::onRegisterEvent); + eventBus.addListener(this::onCommonSetup); + NeoForge.EVENT_BUS.addListener(this::onCommandsRegister); + NeoForge.EVENT_BUS.addListener(WorldStripper::onServerTick); + } + + private void onRegisterEvent(RegisterEvent event) { + if (event.getRegistryKey() == NeoForgeRegistries.Keys.BIOME_MODIFIER_SERIALIZERS) { + Registry.register( + NeoForgeRegistries.BIOME_MODIFIER_SERIALIZERS, + Utils.getRL("worldgen_unification"), + WorldGenBiomeModifier.CODEC + ); + } + + if (AlmostUnifiedCommon.STARTUP_CONFIG.isServerOnly()) return; + + if (event.getRegistryKey() == Registries.RECIPE_SERIALIZER) { + Registry.register( + BuiltInRegistries.RECIPE_SERIALIZER, + ClientRecipeTracker.ID, + ClientRecipeTracker.SERIALIZER + ); + } + + if (event.getRegistryKey() == Registries.RECIPE_TYPE) { + Registry.register(BuiltInRegistries.RECIPE_TYPE, ClientRecipeTracker.ID, ClientRecipeTracker.TYPE); + } + } + + private void onCommandsRegister(RegisterCommandsEvent event) { + AlmostUnifiedCommands.registerCommands(event.getDispatcher()); + } + + private void onCommonSetup(FMLCommonSetupEvent event) { + event.enqueueWork(AlmostUnifiedNeoForge::initializePluginManager); + } + + private static void initializePluginManager() { + List plugins = new ArrayList<>(); + Collection> pluginClasses = getPluginClasses(); + + for (var pluginClass : pluginClasses) { + try { + plugins.add(pluginClass.getConstructor().newInstance()); + } catch (Exception e) { + AlmostUnifiedCommon.LOGGER.error("Failed to load plugin {}.", pluginClass.getName(), e); + } + } + + PluginManager.init(plugins); + } + + private static Collection> getPluginClasses() { + Set> pluginClasses = new HashSet<>(); + Type type = Type.getType(AlmostUnifiedNeoPlugin.class); + + for (var data : ModList.get().getAllScanData()) { + for (var annotation : data.getAnnotations()) { + if (!annotation.annotationType().equals(type)) { + continue; + } + + var plugin = getPluginClass(annotation.clazz().getClassName()); + if (plugin != null) { + pluginClasses.add(plugin); + } + } + } + + return pluginClasses; + } + + @Nullable + private static Class getPluginClass(String className) { + try { + Class pluginClass = Class.forName(className); + if (AlmostUnifiedPlugin.class.isAssignableFrom(pluginClass)) { + // noinspection unchecked + return (Class) pluginClass; + } + + AlmostUnifiedCommon.LOGGER.error( + "Plugin {} does not implement {}.", + className, + AlmostUnifiedPlugin.class.getName() + ); + } catch (ClassNotFoundException e) { + AlmostUnifiedCommon.LOGGER.error("Failed to load plugin {}.", className, e); + return null; + } + + return null; + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformNeoForge.java b/NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformNeoForge.java new file mode 100644 index 0000000..5895f62 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/AlmostUnifiedPlatformNeoForge.java @@ -0,0 +1,44 @@ +package com.almostreliable.unified; + +import com.almostreliable.unified.api.constant.ModConstants; +import com.google.auto.service.AutoService; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModInfo; + +import java.nio.file.Path; + +@AutoService(AlmostUnifiedPlatform.class) +public class AlmostUnifiedPlatformNeoForge implements AlmostUnifiedPlatform { + + @Override + public Platform getPlatform() { + return Platform.NEO_FORGE; + } + + @Override + public boolean isModLoaded(String modId) { + if (ModList.get() == null) { + return LoadingModList.get().getMods().stream().map(ModInfo::getModId).anyMatch(modId::equals); + } + return ModList.get().isLoaded(modId); + } + + @Override + public boolean isClient() { + return FMLLoader.getDist() == Dist.CLIENT; + } + + @Override + public Path getConfigPath() { + return FMLPaths.CONFIGDIR.get().resolve(ModConstants.ALMOST_UNIFIED); + } + + @Override + public Path getDebugLogPath() { + return FMLPaths.GAMEDIR.get().resolve("logs").resolve(ModConstants.ALMOST_UNIFIED).resolve("debug"); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ArsNouveauRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ArsNouveauRecipeUnifier.java new file mode 100644 index 0000000..8e431e0 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ArsNouveauRecipeUnifier.java @@ -0,0 +1,41 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class ArsNouveauRecipeUnifier implements RecipeUnifier { + + private static final String PEDESTAL_ITEMS = "pedestalItems"; + private static final String REAGENT = "reagent"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + helper.unifyInputs(recipe, REAGENT); + unifyItemInputs(helper, recipe, PEDESTAL_ITEMS); + unifyItemInputs(helper, recipe, RecipeConstants.INPUT_ITEMS); + } + + public void unifyItemInputs(UnificationHelper helper, RecipeJson recipe, String key) { + if (!(recipe.getProperty(key) instanceof JsonArray array)) { + return; + } + + for (JsonElement element : array) { + if (helper.unifyInputElement(element)) { + continue; + } + + if (element instanceof JsonObject obj) { + JsonElement inner = obj.get(RecipeConstants.ITEM); + helper.unifyInputElement(inner); + } + } + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/CyclicRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/CyclicRecipeUnifier.java new file mode 100644 index 0000000..52c9602 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/CyclicRecipeUnifier.java @@ -0,0 +1,15 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +public class CyclicRecipeUnifier implements RecipeUnifier { + + private static final String BONUS = "bonus"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + helper.unifyOutputs(recipe, BONUS); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/EnderIORecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/EnderIORecipeUnifier.java new file mode 100644 index 0000000..3a28121 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/EnderIORecipeUnifier.java @@ -0,0 +1,14 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +public class EnderIORecipeUnifier implements RecipeUnifier { + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + helper.unifyOutputs(recipe, RecipeConstants.ITEM); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ImmersiveEngineeringRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ImmersiveEngineeringRecipeUnifier.java new file mode 100644 index 0000000..2446e49 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ImmersiveEngineeringRecipeUnifier.java @@ -0,0 +1,66 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.List; + +public class ImmersiveEngineeringRecipeUnifier implements RecipeUnifier { + + private static final String INPUT_0 = "input0"; + private static final String INPUT_1 = "input1"; + private static final String ADDITIVES = "additives"; + private static final String BASE_INGREDIENT = "base_ingredient"; + private static final String SECONDARIES = "secondaries"; + private static final String SLAG = "slag"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + + List.of( + // alloy recipes, refinery + INPUT_0, + INPUT_1, + // arc furnace, squeezer, cloche, coke oven, fermenter, fertilizer, metal_press + RecipeConstants.INPUT, + // arc furnace + ADDITIVES, + // refinery + RecipeConstants.CATALYST + ).forEach(key -> unifyInputs(helper, recipe, key)); + + List.of( + RecipeConstants.RESULT, + RecipeConstants.RESULTS, + // arc furnace + SLAG + ).forEach(key -> helper.unifyOutputs(recipe, key, true, RecipeConstants.ITEM, BASE_INGREDIENT)); + + unifySecondaries(helper, recipe); + } + + public void unifyInputs(UnificationHelper helper, RecipeJson recipe, String key) { + if (recipe.getProperty(key) instanceof JsonObject json && json.has(BASE_INGREDIENT)) { + if (helper.unifyInputElement(json.get(BASE_INGREDIENT))) { + return; + } + } + + helper.unifyInputs(recipe, key); + } + + public void unifySecondaries(UnificationHelper helper, RecipeJson recipe) { + JsonElement secondaries = recipe.getProperty(SECONDARIES); + if (secondaries == null) { + return; + } + + helper.unifyOutputArray(secondaries.getAsJsonArray(), true, RecipeConstants.OUTPUT); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/IntegratedDynamicsRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/IntegratedDynamicsRecipeUnifier.java new file mode 100644 index 0000000..977a488 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/IntegratedDynamicsRecipeUnifier.java @@ -0,0 +1,60 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +public class IntegratedDynamicsRecipeUnifier implements RecipeUnifier { + + private static final String ITEMS = "items"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + + unifyItemInput(helper, recipe); + helper.unifyOutputs(recipe, RecipeConstants.RESULT, true, ITEMS); + unifyItemResult(helper, recipe); + } + + /** + * Integrated dynamics allows primitive values for `item` keys in their recipes. + * AlmostUnified will convert them into JsonObject so that they can be unified to use tags. + * + * @param helper the unification helper + * @param recipe the recipe + */ + private static void unifyItemInput(UnificationHelper helper, RecipeJson recipe) { + JsonElement element = recipe.getProperty(RecipeConstants.ITEM); + if (element instanceof JsonPrimitive primitive) { + JsonObject itemAsObject = new JsonObject(); + itemAsObject.add(RecipeConstants.ITEM, primitive); + if (helper.unifyInputElement(itemAsObject)) { + recipe.setProperty(RecipeConstants.ITEM, itemAsObject); + return; + } + } + + helper.unifyInputs(recipe, RecipeConstants.ITEM); + } + + private void unifyItemResult(UnificationHelper helper, RecipeJson recipe) { + JsonElement element = recipe.getProperty(RecipeConstants.RESULT); + if (!(element instanceof JsonObject result)) { + return; + } + + var itemsElement = result.get(ITEMS); + if (!(itemsElement instanceof JsonArray items)) { + return; + } + + helper.unifyOutputArray(items, true, RecipeConstants.ITEM); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/MekanismRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/MekanismRecipeUnifier.java new file mode 100644 index 0000000..f428c42 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/MekanismRecipeUnifier.java @@ -0,0 +1,29 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +public class MekanismRecipeUnifier implements RecipeUnifier { + + private static final String MAIN_INPUT = "mainInput"; + private static final String ITEM_INPUT = "itemInput"; + private static final String EXTRA_INPUT = "extraInput"; + private static final String MAIN_OUTPUT = "mainOutput"; + private static final String ITEM_OUTPUT = "itemOutput"; + private static final String SECONDARY_OUTPUT = "secondaryOutput"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + + helper.unifyInputs(recipe, MAIN_INPUT); + helper.unifyInputs(recipe, ITEM_INPUT); + helper.unifyInputs(recipe, EXTRA_INPUT); + + helper.unifyOutputs(recipe, MAIN_OUTPUT); + helper.unifyOutputs(recipe, ITEM_OUTPUT); + helper.unifyOutputs(recipe, SECONDARY_OUTPUT); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ModernIndustrializationRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ModernIndustrializationRecipeUnifier.java new file mode 100644 index 0000000..e506a6e --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ModernIndustrializationRecipeUnifier.java @@ -0,0 +1,19 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +public class ModernIndustrializationRecipeUnifier implements RecipeUnifier { + + private static final String ITEM_INPUTS = "item_inputs"; + private static final String ITEM_OUTPUTS = "item_outputs"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + helper.unifyInputs(recipe, ITEM_INPUTS); + helper.unifyOutputs(recipe, ITEM_OUTPUTS); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/OccultismRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/OccultismRecipeUnifier.java new file mode 100644 index 0000000..485961d --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/OccultismRecipeUnifier.java @@ -0,0 +1,54 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonObject; + +public class OccultismRecipeUnifier implements RecipeUnifier { + + static final String TYPE = "type"; + private static final String ACTIVATION_ITEM = "activation_item"; + private static final String ITEM_TO_USE = "item_to_use"; + private static final String STACK = "stack"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unifyInputs(helper, recipe); + helper.unifyInputs(recipe, ACTIVATION_ITEM, ITEM_TO_USE); + + if (recipe.getProperty(RecipeConstants.RESULT) instanceof JsonObject result && result.has(TYPE)) { + unifyTypedOutput(helper, recipe, result); + return; + } + + GenericRecipeUnifier.INSTANCE.unifyOutputs(helper, recipe); + } + + static void unifyTypedOutput(UnificationHelper helper, RecipeJson recipe, JsonObject result) { + helper.unifyOutputs(recipe, RecipeConstants.RESULT, true, STACK, RecipeConstants.ID); + + // check if the type is a tag but the entry was converted to an item + var type = result.get(TYPE).getAsString(); + if (!type.contains(RecipeConstants.TAG) || result.has(RecipeConstants.TAG)) return; + + // replace the item key with id + result.add(RecipeConstants.ID, result.get(RecipeConstants.ITEM)); + result.remove(RecipeConstants.ITEM); + + // use correct type: weighted_tag → weighted_item, tag → item + result.addProperty(TYPE, type.replace(RecipeConstants.TAG, RecipeConstants.ITEM)); + + // wrap weighted tag values in json object to make it a valid weighted item + if (type.contains("weighted_tag")) { + JsonObject stack = new JsonObject(); + stack.add("count", result.get("count")); + stack.add(RecipeConstants.ID, result.get(RecipeConstants.ID)); + result.add(STACK, stack); + result.remove("count"); + result.remove(RecipeConstants.ID); + } + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ProductiveTreesRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ProductiveTreesRecipeUnifier.java new file mode 100644 index 0000000..9d723ae --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/ProductiveTreesRecipeUnifier.java @@ -0,0 +1,20 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; + +public final class ProductiveTreesRecipeUnifier implements RecipeUnifier { + + private static final String LEAF_A = "leafA"; + private static final String LEAF_B = "leafB"; + private static final String SECONDARY = "secondary"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unify(helper, recipe); + helper.unifyInputs(recipe, LEAF_A, LEAF_B); + helper.unifyOutputs(recipe, SECONDARY); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/TheurgyRecipeUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/TheurgyRecipeUnifier.java new file mode 100644 index 0000000..1410e7c --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/unification/TheurgyRecipeUnifier.java @@ -0,0 +1,32 @@ +package com.almostreliable.unified.compat.unification; + +import com.almostreliable.unified.api.constant.RecipeConstants; +import com.almostreliable.unified.api.unification.bundled.GenericRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeJson; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.UnificationHelper; +import com.google.gson.JsonObject; + +public class TheurgyRecipeUnifier implements RecipeUnifier { + + private static final String MERCURY = "mercury"; + private static final String SALT = "salt"; + private static final String SOLUTE = "solute"; + private static final String SOURCES = "sources"; + private static final String SULFUR = "sulfur"; + private static final String TARGET = "target"; + + @Override + public void unify(UnificationHelper helper, RecipeJson recipe) { + GenericRecipeUnifier.INSTANCE.unifyInputs(helper, recipe); + helper.unifyInputs(recipe, MERCURY, SALT, SOLUTE, SOURCES, SULFUR, TARGET); + + if (recipe.getProperty(RecipeConstants.RESULT) instanceof JsonObject result && + result.has(OccultismRecipeUnifier.TYPE)) { + OccultismRecipeUnifier.unifyTypedOutput(helper, recipe, result); + return; + } + + GenericRecipeUnifier.INSTANCE.unifyOutputs(helper, recipe); + } +} diff --git a/Forge/src/main/java/com/almostreliable/unified/compat/AlmostREIForge.java b/NeoForge/src/main/java/com/almostreliable/unified/compat/viewer/AlmostREIForge.java similarity index 52% rename from Forge/src/main/java/com/almostreliable/unified/compat/AlmostREIForge.java rename to NeoForge/src/main/java/com/almostreliable/unified/compat/viewer/AlmostREIForge.java index 8bc9674..60201cf 100644 --- a/Forge/src/main/java/com/almostreliable/unified/compat/AlmostREIForge.java +++ b/NeoForge/src/main/java/com/almostreliable/unified/compat/viewer/AlmostREIForge.java @@ -1,8 +1,8 @@ -package com.almostreliable.unified.compat; +package com.almostreliable.unified.compat.viewer; import me.shedaniel.rei.forge.REIPluginClient; @REIPluginClient public class AlmostREIForge extends AlmostREI { - // dummy class to activate the plugin on Forge + // dummy class to activate the plugin on NeoForge } diff --git a/NeoForge/src/main/java/com/almostreliable/unified/core/AlmostUnifiedCommands.java b/NeoForge/src/main/java/com/almostreliable/unified/core/AlmostUnifiedCommands.java new file mode 100644 index 0000000..3f0b645 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/core/AlmostUnifiedCommands.java @@ -0,0 +1,65 @@ +package com.almostreliable.unified.core; + +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.unification.worldgen.WorldStripper; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; + +public final class AlmostUnifiedCommands { + + private static final String RADIUS = "radius"; + + private AlmostUnifiedCommands() {} + + public static void registerCommands(CommandDispatcher dispatcher) { + var radiusArgument = Commands.argument(RADIUS, IntegerArgumentType.integer(0, 16 * 8)) + .executes(AlmostUnifiedCommands::onStripCommand); + var stripSubCommand = Commands.literal("strip") + .requires(source -> source.hasPermission(2)) + .then(radiusArgument) + .executes(AlmostUnifiedCommands::onStripCommand); + var mainCommand = Commands.literal(ModConstants.ALMOST_UNIFIED) + .then(stripSubCommand) + .executes(AlmostUnifiedCommands::onHelpCommand); + + dispatcher.register(mainCommand); + } + + private static int onHelpCommand(CommandContext context) { + //@formatter:off + context.getSource().sendSuccess(() -> Component.literal(""), false); + context.getSource().sendSuccess(() -> Component.literal("Almost Unified Commands").withStyle(ChatFormatting.GOLD), false); + context.getSource().sendSuccess(() -> Component.literal("--------------------"), false); + context.getSource().sendSuccess(() -> Component.literal("strip - strips everything in the specified radius except for Bedrock and ores"), false); + //@formatter:on + + return Command.SINGLE_SUCCESS; + } + + private static int onStripCommand(CommandContext context) { + var player = context.getSource().getPlayer(); + var level = context.getSource().getLevel(); + if (player == null) { + context.getSource().sendFailure(Component.literal("This command can only be used in-game!")); + return 0; + } + + try { + var radius = context.getArgument(RADIUS, Integer.class); + WorldStripper.stripWorld(player, level, radius); + } catch (Exception e) { + context.getSource().sendFailure( + Component.literal("Please provide a valid radius!").withStyle(ChatFormatting.DARK_RED) + ); + return 0; + } + + return Command.SINGLE_SUCCESS; + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/core/NeoForgePlugin.java b/NeoForge/src/main/java/com/almostreliable/unified/core/NeoForgePlugin.java new file mode 100644 index 0000000..e884700 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/core/NeoForgePlugin.java @@ -0,0 +1,44 @@ +package com.almostreliable.unified.core; + +import com.almostreliable.unified.api.constant.ModConstants; +import com.almostreliable.unified.api.plugin.AlmostUnifiedNeoPlugin; +import com.almostreliable.unified.api.plugin.AlmostUnifiedPlugin; +import com.almostreliable.unified.api.unification.bundled.ShapedRecipeUnifier; +import com.almostreliable.unified.api.unification.recipe.RecipeUnifierRegistry; +import com.almostreliable.unified.compat.unification.*; +import com.almostreliable.unified.utils.Utils; +import net.minecraft.resources.ResourceLocation; + +import java.util.List; + +@AlmostUnifiedNeoPlugin +public class NeoForgePlugin implements AlmostUnifiedPlugin { + + @Override + public ResourceLocation getPluginId() { + return Utils.getRL("neoforge"); + } + + @Override + public void registerRecipeUnifiers(RecipeUnifierRegistry registry) { + List.of( + ModConstants.ARS_CREO, + ModConstants.ARS_ELEMENTAL, + ModConstants.ARS_NOUVEAU, + ModConstants.ARS_SCALAES + ).forEach(modId -> registry.registerForModId(modId, new ArsNouveauRecipeUnifier())); + registry.registerForModId(ModConstants.CYCLIC, new CyclicRecipeUnifier()); + registry.registerForModId(ModConstants.ENDER_IO, new EnderIORecipeUnifier()); + registry.registerForModId(ModConstants.IMMERSIVE_ENGINEERING, new ImmersiveEngineeringRecipeUnifier()); + registry.registerForModId(ModConstants.INTEGRATED_DYNAMICS, new IntegratedDynamicsRecipeUnifier()); + registry.registerForModId(ModConstants.MEKANISM, new MekanismRecipeUnifier()); + registry.registerForModId(ModConstants.MODERN_INDUSTRIALIZATION, new ModernIndustrializationRecipeUnifier()); + registry.registerForModId(ModConstants.OCCULTISM, new OccultismRecipeUnifier()); + registry.registerForModId(ModConstants.PRODUCTIVE_TREES, new ProductiveTreesRecipeUnifier()); + registry.registerForModId(ModConstants.THEURGY, new TheurgyRecipeUnifier()); + registry.registerForRecipeType( + ResourceLocation.fromNamespaceAndPath(ModConstants.THEURGY, "divination_rod"), + ShapedRecipeUnifier.INSTANCE + ); + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/OreConfigurationAccessor.java b/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/OreConfigurationAccessor.java new file mode 100644 index 0000000..62d92b9 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/OreConfigurationAccessor.java @@ -0,0 +1,18 @@ +package com.almostreliable.unified.mixin.neoforge.worldgen; + +import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(OreConfiguration.class) +public interface OreConfigurationAccessor { + + @Accessor("targetStates") + @Final + @Mutable + void almostunified$setTargets(List targetStates); +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/ServerLifecycleHooksMixin.java b/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/ServerLifecycleHooksMixin.java new file mode 100644 index 0000000..73c3455 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/ServerLifecycleHooksMixin.java @@ -0,0 +1,31 @@ +package com.almostreliable.unified.mixin.neoforge.worldgen; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.unification.worldgen.WorldGenBiomeModifier; +import net.minecraft.server.MinecraftServer; +import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +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.CallbackInfo; + +@Mixin(ServerLifecycleHooks.class) +public class ServerLifecycleHooksMixin { + + @Inject(method = "runModifiers", at = @At("HEAD"), remap = false) + private static void almostunified$injectRegistryAccess(MinecraftServer server, CallbackInfo ci) { + var registryAccess = server.registryAccess(); + var biomeModifiers = registryAccess.registryOrThrow(NeoForgeRegistries.Keys.BIOME_MODIFIERS); + for (var bm : biomeModifiers) { + if (bm instanceof WorldGenBiomeModifier wgbm) { + try { + WorldGenBiomeModifier.bindUnifier(wgbm, registryAccess); + } catch (Exception e) { + var id = biomeModifiers.getKey(bm); + AlmostUnifiedCommon.LOGGER.error("Failed to bind registry access to biome modifier " + id, e); + } + } + } + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/package-info.java b/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/package-info.java new file mode 100644 index 0000000..c3d5463 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/mixin/neoforge/worldgen/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.mixin.neoforge.worldgen; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenBiomeModifier.java b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenBiomeModifier.java new file mode 100644 index 0000000..d54c077 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenBiomeModifier.java @@ -0,0 +1,82 @@ +package com.almostreliable.unified.unification.worldgen; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.utils.Utils; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.neoforged.neoforge.common.world.BiomeModifier; +import net.neoforged.neoforge.common.world.ModifiableBiomeInfo; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class WorldGenBiomeModifier implements BiomeModifier { + + public static final MapCodec CODEC = MapCodec.unit(WorldGenBiomeModifier::new); + public static final ResourceLocation UNKNOWN_BIOME_ID = Utils.getRL("unknown_biome_id"); + + public static void bindUnifier(WorldGenBiomeModifier modifier, RegistryAccess registryAccess) { + if (AlmostUnifiedCommon.STARTUP_CONFIG.allowWorldGenUnification()) { + WorldGenUnifier unifier = new WorldGenUnifier(registryAccess); + unifier.process(); + modifier.unifier = unifier; + } + } + + @Nullable + private WorldGenUnifier unifier; + + @Override + public void modify(Holder biome, Phase phase, ModifiableBiomeInfo.BiomeInfo.Builder builder) { + if (phase != Phase.AFTER_EVERYTHING) { + return; + } + + if (unifier == null) { + return; + } + + Map>> removedFeatures = new LinkedHashMap<>(); + + for (GenerationStep.Decoration dec : GenerationStep.Decoration.values()) { + var features = builder.getGenerationSettings().getFeatures(dec); + features.removeIf(feature -> { + if (unifier.shouldRemovePlacedFeature(feature)) { + removedFeatures.computeIfAbsent(dec, $ -> new ArrayList<>()).add(feature); + return true; + } + + return false; + }); + } + + if (!removedFeatures.isEmpty()) { + AlmostUnifiedCommon.LOGGER.info("[WorldGen] Removed features from Biome {}:", + biome.unwrapKey().map(ResourceKey::location).orElse(UNKNOWN_BIOME_ID)); + removedFeatures.forEach((decoration, features) -> { + String ids = features + .stream() + .flatMap(f -> f.unwrapKey().map(ResourceKey::location).stream()) + .map(ResourceLocation::toString) + .collect(Collectors.joining(", ")); + + AlmostUnifiedCommon.LOGGER.info("[WorldGen]\t{}: {}", decoration.getName(), ids); + }); + } + } + + @Override + public MapCodec codec() { + return CODEC; + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenUnifier.java b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenUnifier.java new file mode 100644 index 0000000..f3f5385 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldGenUnifier.java @@ -0,0 +1,117 @@ +package com.almostreliable.unified.unification.worldgen; + +import com.almostreliable.unified.AlmostUnifiedCommon; +import com.almostreliable.unified.api.AlmostUnified; +import com.almostreliable.unified.api.unification.UnificationLookup; +import com.almostreliable.unified.mixin.neoforge.worldgen.OreConfigurationAccessor; +import com.almostreliable.unified.utils.Utils; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class WorldGenUnifier { + public static final ResourceLocation UNKNOWN_FEATURE_ID = Utils.getRL("unknown_feature_id"); + private final Registry> cfRegistry; + private final Set>> featuresToRemove = new HashSet<>(); + + public WorldGenUnifier(RegistryAccess registryAccess) { + this.cfRegistry = registryAccess.registryOrThrow(Registries.CONFIGURED_FEATURE); + } + + public void process() { + var unificationLookup = AlmostUnified.INSTANCE.getRuntimeOrThrow().getUnificationLookup(); + cfRegistry.holders().forEach(holder -> { + switch (handleConfiguredFeature(unificationLookup, holder)) { + case SAME -> { + // do nothing + } + case REMOVE -> { + AlmostUnifiedCommon.LOGGER.info("[WorldGen] Mark ConfiguredFeature '{}' for removal:", + holder.unwrapKey().map(ResourceKey::location).orElse(UNKNOWN_FEATURE_ID)); + featuresToRemove.add(holder); + } + case CHANGE -> { + AlmostUnifiedCommon.LOGGER.info("[WorldGen] Changed ConfiguredFeature '{}':", + holder.unwrapKey().map(ResourceKey::location).orElse(UNKNOWN_FEATURE_ID)); + } + } + }); + } + + private Result handleConfiguredFeature(UnificationLookup unificationLookup, Holder> cfHolder) { + if (!(cfHolder.value().config() instanceof OreConfiguration oreConfig)) { + return Result.SAME; + } + + boolean changed = false; + List newTargetStates = new ArrayList<>(oreConfig.targetStates); + + var it = newTargetStates.listIterator(); + while (it.hasNext()) { + var currentTargetState = it.next(); + if (handleTargetState(unificationLookup, currentTargetState)) { + changed = true; + it.remove(); + } + } + + if (changed) { + ((OreConfigurationAccessor) oreConfig).almostunified$setTargets(newTargetStates); + return newTargetStates.isEmpty() ? Result.REMOVE : Result.CHANGE; + } + + return Result.SAME; + } + + private boolean handleTargetState(UnificationLookup unificationLookup, OreConfiguration.TargetBlockState targetState) { + var blockHolder = targetState.state.getBlockHolder(); + if (!(blockHolder instanceof Holder.Reference ref)) { + return false; + } + + var blockId = ref.key().location(); + var replacement = unificationLookup.getVariantItemTarget(blockId); + if (replacement == null || replacement.id().equals(blockId)) { + return false; + } + + Block replacementBlock = BuiltInRegistries.BLOCK.getOptional(replacement.id()).orElse(null); + if (replacementBlock == null) { + AlmostUnifiedCommon.LOGGER.error( + "Trying to find replacement for block {} (Replacement: {}), but it does not exist.", + blockId, + replacement.id()); + return false; + } + + return true; + } + + public boolean shouldRemovePlacedFeature(Holder placedFeature) { + Holder> cfHolder = placedFeature.value().feature(); + if (cfHolder instanceof Holder.Reference> ref) { + return featuresToRemove.contains(ref); + } + + return false; + } + + private enum Result { + SAME, + CHANGE, + REMOVE + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldStripper.java b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldStripper.java new file mode 100644 index 0000000..e282dd9 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/WorldStripper.java @@ -0,0 +1,93 @@ +package com.almostreliable.unified.unification.worldgen; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.DropExperienceBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.pattern.BlockInWorld; +import net.neoforged.neoforge.event.tick.ServerTickEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Collectors; + +public final class WorldStripper { + + private static final int BLOCKS_PER_TICK = 16 * 16 * 16; + + @Nullable private static Worker WORKER; + + private WorldStripper() {} + + @SuppressWarnings("StaticVariableUsedBeforeInitialization") + public static void onServerTick(ServerTickEvent.Post ignoredEvent) { + if (WORKER == null) return; + WORKER.doWork(); + if (WORKER.isDone()) WORKER = null; + } + + public static void stripWorld(ServerPlayer player, ServerLevel level, int radius) { + BlockPos playerPos = player.blockPosition(); + BlockPos min = new BlockPos(playerPos.getX() - radius, level.getMinBuildHeight(), playerPos.getZ() - radius); + BlockPos max = new BlockPos(playerPos.getX() + radius, level.getMaxBuildHeight(), playerPos.getZ() + radius); + + @SuppressWarnings("SimplifyStreamApiCallChains") + var blockIterator = BlockPos.betweenClosedStream(min, max) + .map(pos -> blockInWorld(level, pos)) + .collect(Collectors.toList()) + .iterator(); + + WORKER = new Worker(level, blockIterator); + } + + private static BlockInWorld blockInWorld(ServerLevel level, BlockPos pos) { + return new BlockInWorld(level, pos, true); + } + + private static final class Worker { + + private final ServerLevel level; + private final Iterator iterator; + private final Map removeCache = new IdentityHashMap<>(); + + private Worker(ServerLevel level, Iterator blockIterator) { + this.level = level; + this.iterator = blockIterator; + } + + private void doWork() { + int count = 0; + while (iterator.hasNext() && count < BLOCKS_PER_TICK) { + BlockInWorld block = iterator.next(); + iterator.remove(); + + if (removeCache.computeIfAbsent(block.getState().getBlock(), $ -> shouldRemove(block))) { + level.setBlock(block.getPos(), Blocks.AIR.defaultBlockState(), 3); + } + + count++; + } + } + + private boolean isDone() { + return !iterator.hasNext(); + } + + private boolean shouldRemove(BlockInWorld block) { + BlockState state = block.getState(); + return !(state.isAir() || state.is(Blocks.BEDROCK) || + block.getEntity() != null || + state.getBlock() instanceof DropExperienceBlock || + BuiltInRegistries.BLOCK + .wrapAsHolder(state.getBlock()) + .tags() + .anyMatch(t -> t.location().toString().startsWith("c:ores"))); + } + } +} diff --git a/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/package-info.java b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/package-info.java new file mode 100644 index 0000000..95fb858 --- /dev/null +++ b/NeoForge/src/main/java/com/almostreliable/unified/unification/worldgen/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package com.almostreliable.unified.unification.worldgen; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/Forge/src/main/resources/META-INF/mods.toml b/NeoForge/src/main/resources/META-INF/neoforge.mods.toml similarity index 63% rename from Forge/src/main/resources/META-INF/mods.toml rename to NeoForge/src/main/resources/META-INF/neoforge.mods.toml index 3f37e90..c7c827a 100644 --- a/Forge/src/main/resources/META-INF/mods.toml +++ b/NeoForge/src/main/resources/META-INF/neoforge.mods.toml @@ -1,8 +1,7 @@ modLoader = "javafml" -loaderVersion = "[${forgeFMLVersion},)" +loaderVersion = "[${2},)" issueTrackerURL = "https://github.com/${githubUser}/${githubRepo}/issues" license = "${license}" -logoFile = "logo.png" [[mods]] modId = "${modId}" @@ -11,30 +10,31 @@ displayName = "${modName}" authors = "${modAuthor}" description = '''${modDescription}''' +[[mixins]] +config = "$modId-common.mixins.json" + +[[mixins]] +config = "$modId-neoforge.mixins.json" + [[dependencies."${modId}"]] -modId = "forge" -mandatory = true -versionRange = "[${forgeVersion},)" -ordering = "NONE" -side = "BOTH" +modId = "neoforge" +versionRange = "[${neoforgeVersion},)" [[dependencies."${modId}"]] modId = "minecraft" -mandatory = true versionRange = "[${minecraftVersion},)" -ordering = "NONE" -side = "BOTH" [[dependencies."${modId}"]] modId = "jei" -mandatory = false +type = "optional" versionRange = "[${jeiVersion},)" -ordering = "BEFORE" -side = "BOTH" [[dependencies."${modId}"]] modId = "roughlyenoughitems" -mandatory = false +type = "optional" versionRange = "[${reiVersion},)" -ordering = "BEFORE" -side = "BOTH" + +[[dependencies."${modId}"]] +modId = "emi" +type = "optional" +versionRange = "[${emiVersion},)" diff --git a/NeoForge/src/main/resources/almostunified-neoforge.mixins.json b/NeoForge/src/main/resources/almostunified-neoforge.mixins.json new file mode 100644 index 0000000..b1b4106 --- /dev/null +++ b/NeoForge/src/main/resources/almostunified-neoforge.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8.5", + "package": "com.almostreliable.unified.mixin.neoforge", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "worldgen.OreConfigurationAccessor", + "worldgen.ServerLifecycleHooksMixin" + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/NeoForge/src/main/resources/data/almostunified/neoforge/biome_modifier/worldgen.json b/NeoForge/src/main/resources/data/almostunified/neoforge/biome_modifier/worldgen.json new file mode 100644 index 0000000..7be4a1f --- /dev/null +++ b/NeoForge/src/main/resources/data/almostunified/neoforge/biome_modifier/worldgen.json @@ -0,0 +1,3 @@ +{ + "type": "almostunified:worldgen_unification" +} diff --git a/NeoForge/src/test/java/testmod/neoforge/NeoForgeTest.java b/NeoForge/src/test/java/testmod/neoforge/NeoForgeTest.java new file mode 100644 index 0000000..4a573c4 --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/NeoForgeTest.java @@ -0,0 +1,45 @@ +package testmod.neoforge; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.gametest.GameTestHooks; +import net.neoforged.neoforge.registries.RegisterEvent; +import testmod.CommonTest; +import testmod.TestItems; +import testmod.gametest_core.GameTestLoader; +import testmod.neoforge.tests.*; + +@Mod("testmod") +public class NeoForgeTest { + + public NeoForgeTest(IEventBus bus) { + CommonTest.init(GameTestHooks.isGametestEnabled()); + GameTestLoader.registerProviders(ArsNouveauRecipeTests.class, + MekanismRecipeUnifierTests.class, + ModernIndustrializationRecipeUnifierTests.class, + ImmersiveEngineeringRecipeUnifierTests.class, + EnderIORecipeUnifierTests.class, + IntegratedDynamicsRecipeUnifierTests.class); + + bus.addListener(this::onRegistry); + } + + private static void registerItem(String str, Item item) { + Registry.register(BuiltInRegistries.ITEM, ResourceLocation.parse(str), item); + } + + private static void registerBlock(String str, Block block) { + Registry.register(BuiltInRegistries.BLOCK, ResourceLocation.parse(str), block); + } + + public void onRegistry(RegisterEvent event) { + if (event.getRegistry() == BuiltInRegistries.BLOCK) { + TestItems.registerStuff(NeoForgeTest::registerItem, NeoForgeTest::registerBlock); + } + } +} diff --git a/NeoForge/src/test/java/testmod/neoforge/tests/ArsNouveauRecipeTests.java b/NeoForge/src/test/java/testmod/neoforge/tests/ArsNouveauRecipeTests.java new file mode 100644 index 0000000..f74cc5c --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/tests/ArsNouveauRecipeTests.java @@ -0,0 +1,159 @@ +package testmod.neoforge.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.ArsNouveauRecipeUnifier; +import testmod.TestUtils; +import testmod.gametest_core.SimpleGameTest; + +public class ArsNouveauRecipeTests { + + public static final RecipeUnifier UNIFIER = new ArsNouveauRecipeUnifier(); + + @SimpleGameTest + public void testPedestalItems() { + TestUtils.assertUnify(UNIFIER, """ + { + "type": "ars_nouveau:enchanting_apparatus", + "keepNbtOfReagent": false, + "output": { + "item": "ars_nouveau:jar_of_light" + }, + "pedestalItems": [ + { + "item": "minecraft:glowstone" + }, + { + "item": "testmod:test_item" + }, + { + "tag": "c:glass" + } + ], + "reagent": [ + { + "item": "minecraft:glass_bottle" + } + ], + "sourceCost": 0 + } + """, """ + { + "type": "ars_nouveau:enchanting_apparatus", + "keepNbtOfReagent": false, + "output": { + "item": "ars_nouveau:jar_of_light" + }, + "pedestalItems": [ + { + "item": "minecraft:glowstone" + }, + { + "tag": "testmod:test_tag" + }, + { + "tag": "c:glass" + } + ], + "reagent": [ + { + "item": "minecraft:glass_bottle" + } + ], + "sourceCost": 0 + } + """); + } + + @SimpleGameTest + public void testPedestalItemsNested() { + TestUtils.assertUnify(UNIFIER, """ + { + "type": "ars_nouveau:reactive_enchantment", + "pedestalItems": [ + { + "item": { + "item": "testmod:test_item" + } + }, + { + "item": { + "item": "minecraft:test_item" + } + }, + { + "item": { + "tag": "c:storage_blocks/source" + } + } + ], + "sourceCost": 3000 + } + """, """ + { + "type": "ars_nouveau:reactive_enchantment", + "pedestalItems": [ + { + "item": { + "tag": "testmod:test_tag" + } + }, + { + "item": { + "tag": "testmod:test_tag" + } + }, + { + "item": { + "tag": "c:storage_blocks/source" + } + } + ], + "sourceCost": 3000 + } + """); + } + + + @SimpleGameTest + public void testInputItemsNested() { + TestUtils.assertUnify(UNIFIER, """ + { + "type": "ars_nouveau:glyph", + "count": 1, + "exp": 27, + "inputItems": [ + { + "item": { + "item": "ars_nouveau:abjuration_essence" + } + }, + { + "item": { + "item": "minecraft:test_item" + } + } + ], + "output": "ars_nouveau:glyph_bounce" + } + """, """ + { + "type": "ars_nouveau:glyph", + "count": 1, + "exp": 27, + "inputItems": [ + { + "item": { + "item": "ars_nouveau:abjuration_essence" + } + }, + { + "item": { + "tag": "testmod:test_tag" + } + } + ], + "output": "ars_nouveau:glyph_bounce" + } + """); + } +} diff --git a/NeoForge/src/test/java/testmod/neoforge/tests/EnderIORecipeUnifierTests.java b/NeoForge/src/test/java/testmod/neoforge/tests/EnderIORecipeUnifierTests.java new file mode 100644 index 0000000..547f9ba --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/tests/EnderIORecipeUnifierTests.java @@ -0,0 +1,50 @@ +package testmod.neoforge.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.EnderIORecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertNoUnify; +import static testmod.TestUtils.assertUnify; + +public class EnderIORecipeUnifierTests { + + public static final RecipeUnifier UNIFIER = new EnderIORecipeUnifier(); + + @SimpleGameTest + public void test() { + assertUnify(UNIFIER, """ + { + "type": "enderio:grinding_ball", + "chance": 1.65, + "durability": 40000, + "grinding": 1.2, + "item": "minecraft:test_item", + "power": 0.8 + } + """, """ + { + "type": "enderio:grinding_ball", + "chance": 1.65, + "durability": 40000, + "grinding": 1.2, + "item": "testmod:test_item", + "power": 0.8 + } + """); + } + + @SimpleGameTest + public void testNot() { + assertNoUnify(UNIFIER, """ + { + "type": "enderio:grinding_ball", + "chance": 1.65, + "durability": 40000, + "grinding": 1.2, + "item": "enderio:copper_alloy_grinding_ball", + "power": 0.8 + } + """); + } +} diff --git a/NeoForge/src/test/java/testmod/neoforge/tests/ImmersiveEngineeringRecipeUnifierTests.java b/NeoForge/src/test/java/testmod/neoforge/tests/ImmersiveEngineeringRecipeUnifierTests.java new file mode 100644 index 0000000..a6aeaca --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/tests/ImmersiveEngineeringRecipeUnifierTests.java @@ -0,0 +1,366 @@ +package testmod.neoforge.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.ImmersiveEngineeringRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertUnify; + +public class ImmersiveEngineeringRecipeUnifierTests { + public static final RecipeUnifier UNIFIER = new ImmersiveEngineeringRecipeUnifier(); + + @SimpleGameTest + public void testAlloy() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:alloy", + "input0": { + "item": "minecraft:test_item" + }, + "input1": { + "item": "minecraft:test_item" + }, + "result": { + "base_ingredient": { + "tag": "testmod:test_tag" + }, + "count": 2 + }, + "time": 200 + } + """, """ + { + "type": "immersiveengineering:alloy", + "input0": { + "tag": "testmod:test_tag" + }, + "input1": { + "tag": "testmod:test_tag" + }, + "result": { + "base_ingredient": { + "item": "testmod:test_item" + }, + "count": 2 + }, + "time": 200 + } + """); + } + + @SimpleGameTest + public void testRafinery() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:refinery", + "catalyst": { + "item": "minecraft:test_item" + }, + "energy": 80, + "input0": { + "amount": 8, + "item": "minecraft:test_item" + }, + "input1": { + "amount": 8, + "item": "minecraft:test_item" + }, + "result": { + "amount": 16, + "fluid": "immersiveengineering:biodiesel" + } + } + """, """ + { + "type": "immersiveengineering:refinery", + "catalyst": { + "tag": "testmod:test_tag" + }, + "energy": 80, + "input0": { + "amount": 8, + "tag": "testmod:test_tag" + }, + "input1": { + "amount": 8, + "tag": "testmod:test_tag" + }, + "result": { + "amount": 16, + "fluid": "immersiveengineering:biodiesel" + } + } + """); + } + + @SimpleGameTest + public void testArcFurnace() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:arc_furnace", + "additives": [ + { + "item": "testmod:test_item" + } + ], + "energy": 51200, + "input": { + "base_ingredient": { + "item": "minecraft:test_item" + }, + "count": 2 + }, + "results": [ + { + "base_ingredient": { + "tag": "testmod:test_tag" + }, + "count": 3 + } + ], + "time": 100 + } + """, """ + { + "type": "immersiveengineering:arc_furnace", + "additives": [ + { + "tag": "testmod:test_tag" + } + ], + "energy": 51200, + "input": { + "base_ingredient": { + "tag": "testmod:test_tag" + }, + "count": 2 + }, + "results": [ + { + "base_ingredient": { + "item": "testmod:test_item" + }, + "count": 3 + } + ], + "time": 100 + } + """); + } + + @SimpleGameTest + public void testArcFurnaceSecondaries() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:arc_furnace", + "additives": [], + "energy": 230400, + "input": { + "item": "testmod:test_item" + }, + "results": [ + { + "base_ingredient": { + "tag": "testmod:test_tag" + }, + "count": 13 + } + ], + "secondaries": [ + { + "chance": 0.5, + "output": { + "tag": "testmod:test_tag" + } + } + ], + "time": 900 + } + """, """ + { + "type": "immersiveengineering:arc_furnace", + "additives": [], + "energy": 230400, + "input": { + "tag": "testmod:test_tag" + }, + "results": [ + { + "base_ingredient": { + "item": "testmod:test_item" + }, + "count": 13 + } + ], + "secondaries": [ + { + "chance": 0.5, + "output": { + "item": "testmod:test_item" + } + } + ], + "time": 900 + } + """); + } + + @SimpleGameTest + public void testSawmill() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:sawmill", + "energy": 800, + "input": { + "item": "minecraft:test_item" + }, + "result": { + "count": 2, + "item": "minecraft:test_item" + }, + "secondaries": [ + { + "output": { + "tag": "testmod:test_tag" + }, + "stripping": false + } + ] + } + """, """ + { + "type": "immersiveengineering:sawmill", + "energy": 800, + "input": { + "tag": "testmod:test_tag" + }, + "result": { + "count": 2, + "item": "testmod:test_item" + }, + "secondaries": [ + { + "output": { + "item": "testmod:test_item" + }, + "stripping": false + } + ] + } + """); + } + + @SimpleGameTest + public void testSqueezer() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:squeezer", + "energy": 6400, + "fluid": { + "amount": 60, + "fluid": "immersiveengineering:plantoil" + }, + "input": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "immersiveengineering:squeezer", + "energy": 6400, + "fluid": { + "amount": 60, + "fluid": "immersiveengineering:plantoil" + }, + "input": { + "tag": "testmod:test_tag" + } + } + """); + } + + @SimpleGameTest + public void testFertilizer() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:fertilizer", + "growthModifier": 1.25, + "input": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "immersiveengineering:fertilizer", + "growthModifier": 1.25, + "input": { + "tag": "testmod:test_tag" + } + } + """); + } + + @SimpleGameTest + public void testMetalPress() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:metal_press", + "energy": 3200, + "input": { + "base_ingredient": { + "item": "minecraft:test_item" + }, + "count": 5 + }, + "mold": "immersiveengineering:mold_rod", + "result": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "immersiveengineering:metal_press", + "energy": 3200, + "input": { + "base_ingredient": { + "tag": "testmod:test_tag" + }, + "count": 5 + }, + "mold": "immersiveengineering:mold_rod", + "result": { + "item": "testmod:test_item" + } + } + """); + } + + @SimpleGameTest + public void testCokeOven() { + assertUnify(UNIFIER, """ + { + "type": "immersiveengineering:coke_oven", + "creosote": 250, + "input": { + "item": "minecraft:test_item" + }, + "result": { + "item": "minecraft:test_item" + }, + "time": 900 + } + """, """ + { + "type": "immersiveengineering:coke_oven", + "creosote": 250, + "input": { + "tag": "testmod:test_tag" + }, + "result": { + "item": "testmod:test_item" + }, + "time": 900 + } + """); + } +} diff --git a/NeoForge/src/test/java/testmod/neoforge/tests/IntegratedDynamicsRecipeUnifierTests.java b/NeoForge/src/test/java/testmod/neoforge/tests/IntegratedDynamicsRecipeUnifierTests.java new file mode 100644 index 0000000..68b441e --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/tests/IntegratedDynamicsRecipeUnifierTests.java @@ -0,0 +1,108 @@ +package testmod.neoforge.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.IntegratedDynamicsRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertUnify; + +public class IntegratedDynamicsRecipeUnifierTests { + + public static final RecipeUnifier UNIFIER = new IntegratedDynamicsRecipeUnifier(); + + @SimpleGameTest + public void testBasin() { + assertUnify(UNIFIER, """ + { + "type": "integrateddynamics:drying_basin", + "item": "minecraft:test_item", + "fluid": { + "fluid": "minecraft:water", + "amount": 250 + }, + "duration": 100, + "result": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "integrateddynamics:drying_basin", + "item": { + "tag": "testmod:test_tag" + }, + "fluid": { + "fluid": "minecraft:water", + "amount": 250 + }, + "duration": 100, + "result": { + "item": "testmod:test_item" + } + } + """); + } + + @SimpleGameTest + public void testMechanicalSqueezerItemInItems() { + assertUnify(UNIFIER, """ + { + "type": "integrateddynamics:mechanical_squeezer", + "item": "testmod:test_item", + "result": { + "fluid": { + "fluid": "integrateddynamics:menril_resin", + "amount": 1000 + }, + "items": [ + { + "item": { + "tag": "testmod:test_tag", + "count": 2 + } + }, + { + "item": "minecraft:test_item", + "chance": 0.5 + }, + { + "item": "testmod:test_item", + "chance": 0.5 + } + ] + }, + "duration": 15 + } + """, """ + { + "type": "integrateddynamics:mechanical_squeezer", + "item": { + "tag": "testmod:test_tag" + }, + "result": { + "fluid": { + "fluid": "integrateddynamics:menril_resin", + "amount": 1000 + }, + "items": [ + { + "item": { + "item": "testmod:test_item", + "count": 2 + } + }, + { + "item": "testmod:test_item", + "chance": 0.5 + }, + { + "item": "testmod:test_item", + "chance": 0.5 + } + ] + }, + "duration": 15 + } + """); + } +} diff --git a/NeoForge/src/test/java/testmod/neoforge/tests/MekanismRecipeUnifierTests.java b/NeoForge/src/test/java/testmod/neoforge/tests/MekanismRecipeUnifierTests.java new file mode 100644 index 0000000..9b1a259 --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/tests/MekanismRecipeUnifierTests.java @@ -0,0 +1,310 @@ +package testmod.neoforge.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.MekanismRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertNoUnify; +import static testmod.TestUtils.assertUnify; + +public class MekanismRecipeUnifierTests { + + public static final RecipeUnifier UNIFIER = new MekanismRecipeUnifier(); + + @SimpleGameTest + public void testMainInputAndExtraInput() { + assertUnify(UNIFIER, """ + { + "type": "mekanism:combining", + "extraInput": { + "ingredient": { + "item": "minecraft:test_item" + } + }, + "mainInput": { + "ingredient": { + "item": "minecraft:test_item" + } + }, + "output": { + "item": "minecraft:gravel" + } + } + """, """ + { + "type": "mekanism:combining", + "extraInput": { + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "mainInput": { + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "output": { + "item": "minecraft:gravel" + } + } + """); + } + + @SimpleGameTest + public void testMainInputAndExtraInput_Noop() { + assertNoUnify(UNIFIER, """ + { + "type": "mekanism:combining", + "extraInput": { + "ingredient": { + "item": "minecraft:nether_star" + } + }, + "mainInput": { + "ingredient": { + "item": "minecraft:stick" + } + }, + "output": { + "item": "minecraft:gravel" + } + } + """); + } + + @SimpleGameTest + public void testItemInput_PaintingType() { + assertUnify(UNIFIER, """ + { + "type": "mekanism:painting", + "chemicalInput": { + "amount": 256, + "pigment": "mekanism:gray" + }, + "itemInput": { + "ingredient": [ + { + "item": "minecraft:test_item" + }, + { + "item": "testmod:test_item" + }, + { + "item": "minecraft:stick" + } + ] + }, + "output": { + "item": "ilikewood:biomesoplenty_gray_cherry_bed" + } + } + """, """ + { + "type": "mekanism:painting", + "chemicalInput": { + "amount": 256, + "pigment": "mekanism:gray" + }, + "itemInput": { + "ingredient": [ + { + "tag": "testmod:test_tag" + }, + { + "tag": "testmod:test_tag" + }, + { + "item": "minecraft:stick" + } + ] + }, + "output": { + "item": "ilikewood:biomesoplenty_gray_cherry_bed" + } + } + """); + } + + @SimpleGameTest + public void testItemInput_PaintingType_Noop() { + assertNoUnify(UNIFIER, """ + { + "type": "mekanism:painting", + "chemicalInput": { + "amount": 256, + "pigment": "mekanism:gray" + }, + "itemInput": { + "ingredient": [ + { + "item": "minecraft:apple" + }, + { + "item": "testmod:invalid_item" + }, + { + "item": "minecraft:stick" + } + ] + }, + "output": { + "item": "ilikewood:biomesoplenty_gray_cherry_bed" + } + } + """); + } + + @SimpleGameTest + public void testItemInput_MetallurgicInfusingType() { + assertUnify(UNIFIER, """ + { + "type": "mekanism:metallurgic_infusing", + "chemicalInput": { + "amount": 10, + "tag": "mekanism:bio" + }, + "itemInput": { + "ingredient": { + "item": "minecraft:test_item" + } + }, + "output": { + "item": "byg:mossy_stone_slab" + } + } + """, """ + { + "type": "mekanism:metallurgic_infusing", + "chemicalInput": { + "amount": 10, + "tag": "mekanism:bio" + }, + "itemInput": { + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "output": { + "item": "byg:mossy_stone_slab" + } + } + """); + } + + @SimpleGameTest + public void testItemInput_MetallurgicInfusingType_Noop() { + assertNoUnify(UNIFIER, """ + { + "type": "mekanism:metallurgic_infusing", + "chemicalInput": { + "amount": 10, + "tag": "mekanism:bio" + }, + "itemInput": { + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "output": { + "item": "byg:mossy_stone_slab" + } + } + """); + } + + @SimpleGameTest + public void testMainOutputSecondaryOutput() { + assertUnify(UNIFIER, """ + { + "type": "mekanism:sawing", + "input": { + "ingredient": { + "item": "testmod:test_item" + } + }, + "mainOutput": { + "count": 3, + "item": "minecraft:test_item" + }, + "secondaryChance": 1.0, + "secondaryOutput": { + "count": 3, + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "mekanism:sawing", + "input": { + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "mainOutput": { + "count": 3, + "item": "testmod:test_item" + }, + "secondaryChance": 1.0, + "secondaryOutput": { + "count": 3, + "item": "testmod:test_item" + } + } + """); + } + + @SimpleGameTest + public void testItemInputItemOutput() { + assertUnify(UNIFIER, """ + { + "type": "mekanism:reaction", + "duration": 100, + "fluidInput": { + "amount": 1000, + "tag": "minecraft:water" + }, + "gasInput": { + "amount": 1000, + "gas": "mekanism:plutonium" + }, + "gasOutput": { + "amount": 1000, + "gas": "mekanism:spent_nuclear_waste" + }, + "itemInput": { + "ingredient": { + "item": "minecraft:test_item" + } + }, + "itemOutput": { + "item": "minecraft:test_item" + } + } + """, """ + { + "type": "mekanism:reaction", + "duration": 100, + "fluidInput": { + "amount": 1000, + "tag": "minecraft:water" + }, + "gasInput": { + "amount": 1000, + "gas": "mekanism:plutonium" + }, + "gasOutput": { + "amount": 1000, + "gas": "mekanism:spent_nuclear_waste" + }, + "itemInput": { + "ingredient": { + "tag": "testmod:test_tag" + } + }, + "itemOutput": { + "item": "testmod:test_item" + } + } + """); + } +} diff --git a/NeoForge/src/test/java/testmod/neoforge/tests/ModernIndustrializationRecipeUnifierTests.java b/NeoForge/src/test/java/testmod/neoforge/tests/ModernIndustrializationRecipeUnifierTests.java new file mode 100644 index 0000000..2c03926 --- /dev/null +++ b/NeoForge/src/test/java/testmod/neoforge/tests/ModernIndustrializationRecipeUnifierTests.java @@ -0,0 +1,132 @@ +package testmod.neoforge.tests; + +import com.almostreliable.unified.api.unification.recipe.RecipeUnifier; +import com.almostreliable.unified.compat.unification.ModernIndustrializationRecipeUnifier; +import testmod.gametest_core.SimpleGameTest; + +import static testmod.TestUtils.assertNoUnify; +import static testmod.TestUtils.assertUnify; + +public class ModernIndustrializationRecipeUnifierTests { + + public static final RecipeUnifier UNIFIER = new ModernIndustrializationRecipeUnifier(); + + @SimpleGameTest + public void test() { + assertUnify(UNIFIER, """ + { + "type": "modern_industrialization:assembler", + "eu": 8, + "duration": 200, + "item_inputs": { + "item": "minecraft:test_item", + "amount": 8 + }, + "fluid_inputs": { + "fluid": "minecraft:lava", + "amount": 1000 + }, + "item_outputs": [ + { + "item": "minecraft:test_item", + "amount": 1 + } + ] + } + """, """ + { + "type": "modern_industrialization:assembler", + "eu": 8, + "duration": 200, + "item_inputs": { + "tag": "testmod:test_tag", + "amount": 8 + }, + "fluid_inputs": { + "fluid": "minecraft:lava", + "amount": 1000 + }, + "item_outputs": [ + { + "item": "testmod:test_item", + "amount": 1 + } + ] + } + """); + } + + @SimpleGameTest + public void testArrays() { + assertUnify(UNIFIER, """ + { + "type": "modern_industrialization:mixer", + "duration": 100, + "eu": 2, + "item_inputs": [ + { + "amount": 1, + "item": "minecraft:test_item" + }, + { + "amount": 1, + "item": "testmod:test_item" + } + ], + "item_outputs": [ + { + "amount": 2, + "item": "minecraft:test_item" + } + ] + } + """, """ + { + "type": "modern_industrialization:mixer", + "duration": 100, + "eu": 2, + "item_inputs": [ + { + "amount": 1, + "tag": "testmod:test_tag" + }, + { + "amount": 1, + "tag": "testmod:test_tag" + } + ], + "item_outputs": [ + { + "amount": 2, + "item": "testmod:test_item" + } + ] + } + """); + } + + @SimpleGameTest + public void testNot() { + assertNoUnify(UNIFIER, """ + { + "type": "modern_industrialization:assembler", + "eu": 8, + "duration": 200, + "item_inputs": { + "tag": "c:plates/steel", + "amount": 8 + }, + "fluid_inputs": { + "fluid": "minecraft:lava", + "amount": 1000 + }, + "item_outputs": [ + { + "item": "modern_industrialization:trash_can", + "amount": 1 + } + ] + } + """); + } +} diff --git a/NeoForge/src/test/resources/META-INF/neoforge.mods.toml b/NeoForge/src/test/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..47d5e22 --- /dev/null +++ b/NeoForge/src/test/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,25 @@ +modLoader = "javafml" +loaderVersion = "[2,)" +license = "ARR" + +[[mods]] +modId = "testmod" +version = "13.3.7" +displayName = "Test Mod" + +[[dependencies.testmod]] +modId = "neoforge" +type = "required" +versionRange = "[1,)" +ordering = "NONE" +side = "BOTH" + +[[dependencies.testmod]] +modId = "minecraft" +type = "required" +versionRange = "[1,)" +ordering = "NONE" +side = "BOTH" + +[[mixins]] +config = "testmod.mixins.json" diff --git a/NeoForge/src/test/resources/data/testmod/structure/empty_test_structure.nbt b/NeoForge/src/test/resources/data/testmod/structure/empty_test_structure.nbt new file mode 100644 index 0000000000000000000000000000000000000000..86434d682f26173c25a43e89634bbc5dac059a5d GIT binary patch literal 223 zcmb2|=3oGW|Gk&JxeghKv_34(uiet6U7+44Yh9uhxL#sSZl+O@ao$y~W9%8WjXX^C zKMu9}G%z(k(4WTm;|6Qd>P7wcnBJ{EkP&@tckqmo_R7XJE1kRMYacdn5r13wI9l0d zv2CwFSk^BcMIBWzTw=c0^wVO&MHlC}ad?9uCkS$9UUT`J-{y7Z!+g~i)t03oXuqcL zu~+xSck$81JYny0X3Vcm-F;2=(V~^!@_pOx@5stOSpGf8q}uh|c3Gp?T`#U(V~Y { options.encoding = "UTF-8" - options.release.set(17) + options.release.set(21) } withType { @@ -51,7 +53,7 @@ allprojects { } extensions.configure { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) withSourcesJar() } } @@ -62,7 +64,6 @@ allprojects { subprojects { apply(plugin = "architectury-plugin") apply(plugin = "dev.architectury.loom") - apply(plugin = "io.github.juuxel.loom-vineflower") apply(plugin = "maven-publish") base { @@ -71,14 +72,17 @@ subprojects { } repositories { + maven("https://maven.neoforged.net/releases") maven("https://maven.parchmentmc.org") // Parchment maven("https://maven.shedaniel.me") // REI maven("https://maven.blamejared.com/") // JEI + maven("https://maven.terraformersmc.com/") // EMI mavenLocal() } val loom = project.extensions.getByName("loom") loom.silentMojangMappingsLicense() + loom.createRemapConfigurations(sourceSets.getByName("test")) // create test implementations that allow remapping dependencies { /** @@ -95,17 +99,30 @@ subprojects { /** * non-Minecraft dependencies */ - compileOnly("com.google.auto.service:auto-service:$autoServiceVersion") - annotationProcessor("com.google.auto.service:auto-service:$autoServiceVersion") + compileOnly(testCompileOnly("com.google.auto.service:auto-service:$autoServiceVersion")!!) + annotationProcessor(testAnnotationProcessor("com.google.auto.service:auto-service:$autoServiceVersion")!!) + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") } tasks { + val apiJar = register("apiJar") { + val remapJar = named("remapJar") + archiveClassifier.set("api") + dependsOn(remapJar) + from(zipTree(remapJar.get().archiveFile)) + include(modPackage.replace('.', '/') + "/api/**") + } + + build { + dependsOn(apiJar) + } + /** * resource processing for defined targets * will replace `${key}` with the specified values from the map below */ processResources { - val resourceTargets = listOf("META-INF/mods.toml", "pack.mcmeta", "fabric.mod.json") + val resourceTargets = listOf("META-INF/neoforge.mods.toml", "fabric.mod.json", "pack.mcmeta") val replaceProperties = mapOf( "version" to project.version as String, @@ -116,12 +133,10 @@ subprojects { "modAuthor" to modAuthor, "modDescription" to modDescription, "fabricApiVersion" to fabricApiVersion, - "forgeVersion" to forgeVersion, - // use major version for FML only because wrong Forge version error message - // is way better than FML error message - "forgeFMLVersion" to forgeVersion.substringBefore("."), + "neoforgeVersion" to neoforgeVersion, "jeiVersion" to jeiVersion, "reiVersion" to reiVersion, + "emiVersion" to emiVersion, "githubUser" to githubUser, "githubRepo" to githubRepo ) @@ -146,6 +161,11 @@ subprojects { register(mpm, MavenPublication::class) { artifactId = base.archivesName.get() from(components["java"]) + + val apiJarTask = tasks.named("apiJar") + artifact(apiJarTask) { + classifier = "api" + } } } @@ -183,8 +203,39 @@ subprojects { apply(plugin = "com.github.johnrengelman.shadow") + /** + * add the outputs of the common test source set to the test source set classpath + */ + sourceSets.named("test") { + val cst = project(":Common").sourceSets.getByName("test") + this.compileClasspath += cst.output + this.runtimeClasspath += cst.output + } + extensions.configure { runs { + create("test_client") { + name("Testmod Client") + client() + source(sourceSets.test.get()) + property("fabric-api.gametest", "true") + property("neoforge.gameTestServer", "true") + property("neoforge.enabledGameTestNamespaces", "testmod") + property("$modId.gametest.testPackages", "testmod.*") + property("$modId.configDir", rootProject.projectDir.toPath().resolve("testmod_configs").toString()) + } + + create("gametest") { + name("Gametest") + server() + source(sourceSets.test.get()) + property("fabric-api.gametest", "true") + property("neoforge.gameTestServer", "true") + property("neoforge.enabledGameTestNamespaces", "testmod") + property("$modId.gametest.testPackages", "testmod.*") + property("$modId.configDir", rootProject.projectDir.toPath().resolve("testmod_configs").toString()) + } + forEach { val dir = "../run/${project.name.lowercase()}_${it.environment}" println("[Run Config] ${project.name} '${it.name}' directory: $dir") diff --git a/gradle.properties b/gradle.properties index cca3b6d..7c9caec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,11 @@ # Project group = com.almostreliable.mods license = All Rights Reserved -enabledPlatforms = fabric,forge +enabledPlatforms = fabric,neoforge enableAccessWidener = false # Minecraft -minecraftVersion = 1.20.1 +minecraftVersion = 1.21 # Mod modVersion = 0.7.2 @@ -18,20 +18,22 @@ modDescription = Unify all resources. # Project Dependencies autoServiceVersion = 1.1.1 junitVersion = 5.9.0 -parchmentVersion = 2023.09.03 +parchmentVersion = 2024.07.28 -# Mod Dependencies -jeiVersion = 15.1.0.18 -reiVersion = 12.0.625 +# Recipe Viewers +enableRuntimeRecipeViewer = true +jeiVersion = 19.5.2.66 +reiVersion = 16.0.744 +emiVersion = 1.1.10 +fabricRecipeViewer = rei +neoforgeRecipeViewer = emi # Fabric Dependencies -fabricLoaderVersion = 0.14.21 -fabricApiVersion = 0.83.1 -fabricRecipeViewer = rei +fabricLoaderVersion = 0.15.11 +fabricApiVersion = 0.100.1 -# Forge Dependencies -forgeVersion = 47.0.1 -forgeRecipeViewer = jei +# NeoForge Dependencies +neoforgeVersion = 21.0.143 # Github githubUser = AlmostReliable diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3762f15..72c0eb4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase = GRADLE_USER_HOME distributionPath = wrapper/dists -distributionUrl = https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl = https\://services.gradle.org/distributions/gradle-8.8-bin.zip zipStoreBase = GRADLE_USER_HOME zipStorePath = wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 107acd3..67d24d9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -35,6 +35,24 @@ for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +@rem Parse command line arguments to find -Dorg.gradle.java.home +set JAVA_HOME_FROM_ARG= +:parseArgs +if "%1"=="" goto argsParsed +if "%1"=="-Dorg.gradle.java.home" ( + set JAVA_HOME_FROM_ARG=%2 + shift + shift + goto parseArgs +) +shift +goto parseArgs + +:argsParsed +if defined JAVA_HOME_FROM_ARG ( + set JAVA_HOME=%JAVA_HOME_FROM_ARG% +) + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/settings.gradle.kts b/settings.gradle.kts index 41d94b8..3af02a5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ pluginManagement { maven("https://maven.architectury.dev/") maven("https://maven.fabricmc.net/") maven("https://maven.minecraftforge.net/") + maven("https://maven.neoforged.net/releases") gradlePluginPortal() } } @@ -10,4 +11,4 @@ pluginManagement { val modName: String by extra val minecraftVersion: String by extra rootProject.name = "$modName-$minecraftVersion" -include("Common", "Fabric", "Forge") +include("Common", "NeoForge", "Fabric") diff --git a/testmod_configs/debug.json b/testmod_configs/debug.json new file mode 100644 index 0000000..6aa6f84 --- /dev/null +++ b/testmod_configs/debug.json @@ -0,0 +1,7 @@ +{ + "dump_duplicates": true, + "dump_overview": true, + "dump_recipes": false, + "dump_tags": true, + "dump_unification": true +} diff --git a/testmod_configs/duplicates.json b/testmod_configs/duplicates.json new file mode 100644 index 0000000..1bd2443 --- /dev/null +++ b/testmod_configs/duplicates.json @@ -0,0 +1,35 @@ +{ + "ignored_recipe_types": [ + "cucumber:shaped_tag" + ], + "ignored_recipes": [], + "default_duplicate_rules": { + "ignored_fields": [ + "show_notification", + "neoforge:conditions", + "category", + "group" + ], + "rules": { + "cookingtime": "HigherRule", + "energy": "HigherRule", + "experience": "HigherRule" + }, + "should_sanitize": false + }, + "override_duplicate_rules": { + "minecraft:crafting_shaped": { + "ignored_fields": [ + "pattern", + "show_notification", + "neoforge:conditions", + "category", + "key", + "group" + ], + "rules": {}, + "should_sanitize": false + } + }, + "strict_mode": false +} diff --git a/testmod_configs/placeholders.json b/testmod_configs/placeholders.json new file mode 100644 index 0000000..3a60afe --- /dev/null +++ b/testmod_configs/placeholders.json @@ -0,0 +1,51 @@ +{ + "material": [ + "aeternium", + "aluminum", + "amber", + "apatite", + "bitumen", + "brass", + "bronze", + "charcoal", + "chrome", + "cinnabar", + "coal", + "coal_coke", + "cobalt", + "constantan", + "copper", + "diamond", + "electrum", + "elementium", + "emerald", + "enderium", + "fluorite", + "gold", + "graphite", + "invar", + "iridium", + "iron", + "lapis", + "lead", + "lumium", + "mithril", + "netherite", + "nickel", + "obsidian", + "osmium", + "peridot", + "platinum", + "potassium_nitrate", + "ruby", + "sapphire", + "signalum", + "silver", + "steel", + "sulfur", + "tin", + "tungsten", + "uranium", + "zinc" + ] +} \ No newline at end of file diff --git a/testmod_configs/startup.json b/testmod_configs/startup.json new file mode 100644 index 0000000..ff2718e --- /dev/null +++ b/testmod_configs/startup.json @@ -0,0 +1,4 @@ +{ + "server_only": false, + "world_gen_unification": false +} diff --git a/testmod_configs/tags.json b/testmod_configs/tags.json new file mode 100644 index 0000000..c5b0a7d --- /dev/null +++ b/testmod_configs/tags.json @@ -0,0 +1,18 @@ +{ + "custom_tags": {}, + "tag_substitutions": { + "c:ores/silver": [ + "c:silver_ores" + ] + }, + "item_tag_inheritance_mode": "ALLOW", + "item_tag_inheritance": { + "minecraft:beacon_payment_items": [ + "c:not_in_use", + "c:ores/silver" + ] + }, + "block_tag_inheritance_mode": "ALLOW", + "block_tag_inheritance": {}, + "emi_strict_hiding": true +} diff --git a/testmod_configs/unification/materials.json b/testmod_configs/unification/materials.json new file mode 100644 index 0000000..b784f50 --- /dev/null +++ b/testmod_configs/unification/materials.json @@ -0,0 +1,48 @@ +{ + "mod_priorities": [ + "testmod", + "minecraft", + "mod_c", + "mod_b", + "mod_a", + "meka_fake", + "ie_fake", + "thermal_fake", + "create", + "thermal", + "immersiveengineering", + "mekanism" + ], + "priority_overrides": {}, + "stone_variants": [ + "stone", + "nether", + "deepslate", + "granite", + "diorite", + "andesite" + ], + "tags": [ + "c:dusts/{material}", + "c:gears/{material}", + "c:gems/{material}", + "c:ingots/{material}", + "c:nuggets/{material}", + "c:ores/{material}", + "c:plates/{material}", + "c:raw_materials/{material}", + "c:rods/{material}", + "c:storage_blocks/raw_{material}", + "c:storage_blocks/{material}", + "c:wires/{material}" + ], + "ignored_tags": [], + "ignored_items": [], + "ignored_recipe_types": [ + "cucumber:shaped_tag" + ], + "ignored_recipe_ids": [], + "recipe_viewer_hiding": true, + "loot_unification": true, + "ignored_loot_tables": [] +}