From 2ec0dce02f5674580b9c7e63267450be0122245a Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Sun, 19 Jul 2026 02:01:53 -0400 Subject: [PATCH 1/6] fix(forge): guard null recipe source details --- .../dissolver_enhanced/data/EMCValues.java | 5 +--- .../data/EMCValuesTest.java | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/data/EMCValues.java b/src/main/java/net/exohayvan/dissolver_enhanced/data/EMCValues.java index dd1f45f..ebce9ea 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/data/EMCValues.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/data/EMCValues.java @@ -990,10 +990,7 @@ private static void addRecipeSourceDetail( int resultCount, BigInteger extraEMC ) { - List sourceDetails = new ArrayList<>(); - if (RECIPE_ITEM_SOURCE_DETAILS.containsKey(resultId)) { - sourceDetails = RECIPE_ITEM_SOURCE_DETAILS.get(resultId); - } + List sourceDetails = RECIPE_ITEM_SOURCE_DETAILS.computeIfAbsent(resultId, ignored -> new ArrayList<>()); String sourceDetail = formatRecipeSourceDetail(recipeKey, resultId, resultCount, ingredients, emcValue, extraEMC); if (!sourceDetails.contains(sourceDetail)) { diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCValuesTest.java b/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCValuesTest.java index 6b303de..7ce04c4 100644 --- a/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCValuesTest.java +++ b/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCValuesTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -237,6 +239,30 @@ void test_setEMC_COLLECTION() { } + @Test + void recipeSourceDetailReplacesNullList() throws Exception { + Field detailsField = EMCValues.class.getDeclaredField("RECIPE_ITEM_SOURCE_DETAILS"); + detailsField.setAccessible(true); + @SuppressWarnings("unchecked") + Map> details = (Map>) detailsField.get(null); + details.clear(); + details.put("test:result", null); + + Method addSourceDetail = EMCValues.class.getDeclaredMethod( + "addRecipeSourceDetail", + String.class, + String.class, + BigInteger.class, + List.class, + int.class, + BigInteger.class + ); + addSourceDetail.setAccessible(true); + addSourceDetail.invoke(null, "test:result", "test:recipe__1__0__1", BigInteger.ONE, List.of("minecraft:dirt"), 1, BigInteger.ZERO); + + assertThat(details.get("test:result")).contains("Recipe test:recipe__1__0__1: 1x minecraft:dirt -> 1x test:result = 1 EMC"); + } + private void validateEMCValues(List expected) { Set set = EMCValues.getList(); From 66c63aa84ee402a0e63381b69e265349dd077b29 Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Sun, 19 Jul 2026 11:31:49 -0400 Subject: [PATCH 2/6] ci: use common branch for aggregate builds --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b96ec9..f963f70 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,14 +10,14 @@ permissions: contents: read env: - COMMON_BRANCH: main + COMMON_BRANCH: common COMMON_DIR: Dissolver Enhanced (Common) TARGET_DIR: Dissolver Enhanced (Target) jobs: build-common: name: Build Common - if: ${{ github.ref_name == 'main' }} + if: ${{ github.ref_name == 'common' }} runs-on: ubuntu-22.04 steps: - name: Checkout Common @@ -59,7 +59,7 @@ jobs: discover-version-branches: name: Discover Version Branches - if: ${{ github.ref_name == 'main' }} + if: ${{ github.ref_name == 'common' }} runs-on: ubuntu-22.04 outputs: branches: ${{ steps.branches.outputs.branches }} @@ -81,7 +81,7 @@ jobs: build-all-version-branches: name: Build ${{ matrix.branch }} - if: ${{ github.ref_name == 'main' && needs.discover-version-branches.outputs.branches != '[]' }} + if: ${{ github.ref_name == 'common' && needs.discover-version-branches.outputs.branches != '[]' }} needs: discover-version-branches runs-on: ubuntu-22.04 strategy: @@ -186,7 +186,7 @@ jobs: package-all-builds: name: Package All Builds - if: ${{ github.ref_name == 'main' && needs.build-common.result == 'success' && needs.build-all-version-branches.result == 'success' }} + if: ${{ github.ref_name == 'common' && needs.build-common.result == 'success' && needs.build-all-version-branches.result == 'success' }} needs: - build-common - build-all-version-branches From 7d11ea790707e1db674711ddbd305df234a58913 Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Sun, 19 Jul 2026 19:14:13 -0400 Subject: [PATCH 3/6] updated gradle files --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 4543754..5e0180f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ version = project.mod_version group = project.maven_group def commonDir = [ + "../../common/minecraft", "../Common/minecraft", "../Dissolver Enhanced (Common)/minecraft", "Common/minecraft" From f7a5b509ba7729412fbae8af36082348f65d8a8c Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Mon, 20 Jul 2026 01:26:47 -0400 Subject: [PATCH 4/6] fix(forge): support one jar across 1.20-1.20.4 --- build.gradle | 9 + compat/criterion-1.20.2/build.gradle | 33 +++ .../compat/v1202/CriterionValueCompat.java | 45 ++++ .../compat/v1202/EmcBalanceCriterion1202.java | 40 ++++ .../compat/v1202/EmcOrbCriterion1202.java | 44 ++++ .../v1202/LearnedCountCriterion1202.java | 38 +++ .../v1202/LearnedItemCriterion1202.java | 55 +++++ .../v1202/CriterionValueCompatTest.java | 32 +++ compat/criterion-1.20.3/build.gradle | 33 +++ .../compat/v1203/CriterionValueCompat.java | 45 ++++ .../compat/v1203/EmcBalanceCriterion1203.java | 34 +++ .../compat/v1203/EmcOrbCriterion1203.java | 40 ++++ .../v1203/LearnedCountCriterion1203.java | 33 +++ .../v1203/LearnedItemCriterion1203.java | 48 ++++ .../v1203/CriterionValueCompatTest.java | 32 +++ gradle.properties | 4 +- settings.gradle | 5 + .../overlay/EMCOverlay.java | 3 +- .../packets/DataSenderClient.java | 2 +- .../dissolver_enhanced/DissolverEnhanced.java | 2 +- .../advancement/CriterionCompat.java | 197 ++++++++++++++++ .../advancement/ModCriteria.java | 29 ++- .../analytics/ModAnalytics.java | 3 +- .../dissolver_enhanced/block/ModBlocks.java | 7 +- .../data/StateSaverAndLoader.java | 11 +- .../event/ForgeEmcValueLoader.java | 15 +- .../helpers/BlockPropertiesCompat.java | 29 +++ .../helpers/MinecraftServerCompat.java | 43 ++++ .../helpers/RecipeCompat.java | 47 ++++ .../helpers/RegistryKeyCompat.java | 54 +++++ .../helpers/SavedDataCompat.java | 90 +++++++ .../item/ModItemGroups.java | 4 +- .../packets/DataSender.java | 5 +- .../packets/NetworkCompat.java | 220 ++++++++++++++++++ .../dissolver_enhanced/packets/Packets.java | 37 +-- .../packets/clientbound/EMCValuesPayload.java | 10 +- .../clientbound/PlayerDataPayload.java | 10 +- .../packets/serverbound/ClientPayload.java | 11 +- .../ForgeModMetadataTest.java | 49 ++++ .../advancement/CriterionCompatTest.java | 25 ++ .../dissolver_enhanced/data/EMCExpected.java | 2 + .../helpers/SavedDataCompatTest.java | 15 ++ .../overlay/EMCOverlayDistTest.java | 26 +++ .../packets/NetworkCompatTest.java | 29 +++ 44 files changed, 1476 insertions(+), 69 deletions(-) create mode 100644 compat/criterion-1.20.2/build.gradle create mode 100644 compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java create mode 100644 compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java create mode 100644 compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java create mode 100644 compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedCountCriterion1202.java create mode 100644 compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java create mode 100644 compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java create mode 100644 compat/criterion-1.20.3/build.gradle create mode 100644 compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java create mode 100644 compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java create mode 100644 compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java create mode 100644 compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedCountCriterion1203.java create mode 100644 compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java create mode 100644 compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/BlockPropertiesCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/MinecraftServerCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/RecipeCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java create mode 100644 src/test/java/net/exohayvan/dissolver_enhanced/ForgeModMetadataTest.java create mode 100644 src/test/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompatTest.java create mode 100644 src/test/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompatTest.java create mode 100644 src/test/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlayDistTest.java create mode 100644 src/test/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompatTest.java diff --git a/build.gradle b/build.gradle index 5e0180f..b66d62e 100644 --- a/build.gradle +++ b/build.gradle @@ -155,6 +155,15 @@ test { } jar { + dependsOn ':criterion-compat-1.20.2:reobfJar', ':criterion-compat-1.20.3:reobfJar' + + from({ zipTree(project(':criterion-compat-1.20.2').tasks.named('jar').get().archiveFile.get().asFile) }) { + include 'net/exohayvan/dissolver_enhanced/advancement/compat/v1202/**' + } + from({ zipTree(project(':criterion-compat-1.20.3').tasks.named('jar').get().archiveFile.get().asFile) }) { + include 'net/exohayvan/dissolver_enhanced/advancement/compat/v1203/**' + } + manifest { attributes([ "Specification-Title": project.archives_base_name, diff --git a/compat/criterion-1.20.2/build.gradle b/compat/criterion-1.20.2/build.gradle new file mode 100644 index 0000000..7630970 --- /dev/null +++ b/compat/criterion-1.20.2/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'net.minecraftforge.gradle' version '6.0.53' +} + +group = 'net.exohayvan.dissolver_enhanced.compat' +version = '1.20.2' + +java { + toolchain.languageVersion = JavaLanguageVersion.of(17) +} + +minecraft { + mappings channel: 'official', version: '1.20.2' +} + +dependencies { + minecraft 'net.minecraftforge:forge:1.20.2-48.1.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3' + testImplementation 'org.assertj:assertj-core:3.26.3' +} + +test { + useJUnitPlatform() +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.release = 17 +} + +jar { + archiveBaseName = 'dissolver-criterion-compat-1.20.2' +} diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java new file mode 100644 index 0000000..21034fe --- /dev/null +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java @@ -0,0 +1,45 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; + +import java.math.BigInteger; +import java.util.Locale; + +final class CriterionValueCompat { + private CriterionValueCompat() { + } + + static String baseItemId(String key) { + int componentIndex = key.indexOf("|"); + return componentIndex == -1 ? key : key.substring(0, componentIndex); + } + + static BigInteger nonNegative(BigInteger value) { + return value == null || value.signum() < 0 ? BigInteger.ZERO : value; + } + + static BigInteger parse(String value) { + if (value == null) return BigInteger.ZERO; + + String normalized = value.trim().replace(",", "").replace("_", "").toLowerCase(Locale.ROOT); + if (normalized.isEmpty()) return BigInteger.ZERO; + + BigInteger multiplier = BigInteger.ONE; + String numeric = normalized; + if (normalized.endsWith("k")) { + multiplier = BigInteger.TEN.pow(3); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("m")) { + multiplier = BigInteger.TEN.pow(6); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("b")) { + multiplier = BigInteger.TEN.pow(9); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("t")) { + multiplier = BigInteger.TEN.pow(12); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("q")) { + multiplier = BigInteger.TEN.pow(15); + numeric = normalized.substring(0, normalized.length() - 1); + } + return nonNegative(new BigInteger(numeric).multiply(multiplier)); + } +} diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java new file mode 100644 index 0000000..ea57f4c --- /dev/null +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java @@ -0,0 +1,40 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; + +import com.google.gson.JsonObject; + +import java.math.BigInteger; +import java.util.Optional; + +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.DeserializationContext; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +public class EmcBalanceCriterion1202 extends SimpleCriterionTrigger { + public static final ResourceLocation ID = new ResourceLocation("dissolver_enhanced", "emc_balance"); + + @Override + protected Conditions createInstance(JsonObject jsonObject, Optional player, DeserializationContext context) { + String minEmc = jsonObject.has("min_emc") ? jsonObject.get("min_emc").getAsString() : "0"; + return new Conditions(player, minEmc); + } + + public void trigger(ServerPlayer player, BigInteger emc) { + trigger(player, conditions -> conditions.matches(emc)); + } + + public static class Conditions extends AbstractCriterionTriggerInstance { + private final String minEmc; + + public Conditions(Optional player, String minEmc) { + super(player); + this.minEmc = minEmc; + } + + public boolean matches(BigInteger emc) { + return CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) >= 0; + } + } +} diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java new file mode 100644 index 0000000..87181e2 --- /dev/null +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java @@ -0,0 +1,44 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; + +import com.google.gson.JsonObject; + +import java.math.BigInteger; +import java.util.Optional; + +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.DeserializationContext; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +public class EmcOrbCriterion1202 extends SimpleCriterionTrigger { + public static final ResourceLocation ID = new ResourceLocation("dissolver_enhanced", "emc_orb"); + + @Override + protected Conditions createInstance(JsonObject jsonObject, Optional player, DeserializationContext context) { + String minEmc = jsonObject.has("min_emc") ? jsonObject.get("min_emc").getAsString() : "0"; + String action = jsonObject.has("action") ? jsonObject.get("action").getAsString() : null; + return new Conditions(player, minEmc, action); + } + + public void trigger(ServerPlayer player, BigInteger emc, String action) { + trigger(player, conditions -> conditions.matches(emc, action)); + } + + public static class Conditions extends AbstractCriterionTriggerInstance { + private final String minEmc; + private final String action; + + public Conditions(Optional player, String minEmc, String action) { + super(player); + this.minEmc = minEmc; + this.action = action; + } + + public boolean matches(BigInteger emc, String currentAction) { + if (CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) < 0) return false; + return action == null || action.equals(currentAction); + } + } +} diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedCountCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedCountCriterion1202.java new file mode 100644 index 0000000..a366c35 --- /dev/null +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedCountCriterion1202.java @@ -0,0 +1,38 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; + +import com.google.gson.JsonObject; +import java.util.Optional; + +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.DeserializationContext; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +public class LearnedCountCriterion1202 extends SimpleCriterionTrigger { + public static final ResourceLocation ID = new ResourceLocation("dissolver_enhanced", "learned_count"); + + @Override + protected Conditions createInstance(JsonObject jsonObject, Optional player, DeserializationContext context) { + int minItems = jsonObject.has("min_items") ? jsonObject.get("min_items").getAsInt() : 0; + return new Conditions(player, minItems); + } + + public void trigger(ServerPlayer player, int learnedItems) { + trigger(player, conditions -> conditions.matches(learnedItems)); + } + + public static class Conditions extends AbstractCriterionTriggerInstance { + private final int minItems; + + public Conditions(Optional player, int minItems) { + super(player); + this.minItems = minItems; + } + + public boolean matches(int learnedItems) { + return learnedItems >= minItems; + } + } +} diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java new file mode 100644 index 0000000..49630e3 --- /dev/null +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java @@ -0,0 +1,55 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; + +import com.google.gson.JsonObject; +import java.util.Optional; +import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.DeserializationContext; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +public class LearnedItemCriterion1202 extends SimpleCriterionTrigger { + public static final ResourceLocation ID = new ResourceLocation("dissolver_enhanced", "learned_item"); + + @Override + protected Conditions createInstance(JsonObject jsonObject, Optional player, DeserializationContext context) { + String item = jsonObject.has("item") ? jsonObject.get("item").getAsString() : null; + boolean hasExternalNamespace = jsonObject.has("external_namespace"); + Boolean externalNamespace = hasExternalNamespace ? jsonObject.get("external_namespace").getAsBoolean() : null; + return new Conditions(player, item, externalNamespace); + } + + public void trigger(ServerPlayer player, String itemId) { + String baseItemId = CriterionValueCompat.baseItemId(itemId); + trigger(player, conditions -> conditions.matches(baseItemId)); + } + + public static class Conditions extends AbstractCriterionTriggerInstance { + private final String item; + private final Boolean externalNamespace; + + public Conditions(Optional player, String item, Boolean externalNamespace) { + super(player); + this.item = item; + this.externalNamespace = externalNamespace; + } + + public boolean matches(String itemId) { + if (item != null && !item.equals(itemId)) { + return false; + } + + if (externalNamespace != null && externalNamespace != isExternalNamespace(itemId)) { + return false; + } + + return true; + } + + private static boolean isExternalNamespace(String itemId) { + int namespaceEnd = itemId.indexOf(":"); + return namespaceEnd > 0 && !"minecraft".equals(itemId.substring(0, namespaceEnd)); + } + } +} diff --git a/compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java b/compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java new file mode 100644 index 0000000..a068806 --- /dev/null +++ b/compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java @@ -0,0 +1,32 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; + +import java.math.BigInteger; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CriterionValueCompatTest { + @Test + void parsesCanonicalEmcFormats() { + assertThat(CriterionValueCompat.parse(null)).isEqualTo(BigInteger.ZERO); + assertThat(CriterionValueCompat.parse(" ")).isEqualTo(BigInteger.ZERO); + assertThat(CriterionValueCompat.parse(" 1,000 ")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValueCompat.parse("1_000")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValueCompat.parse("1k")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValueCompat.parse("2M")).isEqualTo(BigInteger.valueOf(2_000_000)); + assertThat(CriterionValueCompat.parse("3b")).isEqualTo(BigInteger.valueOf(3_000_000_000L)); + assertThat(CriterionValueCompat.parse("4t")).isEqualTo(BigInteger.TEN.pow(12).multiply(BigInteger.valueOf(4))); + assertThat(CriterionValueCompat.parse("5q")).isEqualTo(BigInteger.TEN.pow(15).multiply(BigInteger.valueOf(5))); + assertThat(CriterionValueCompat.parse("-1k")).isEqualTo(BigInteger.ZERO); + } + + @Test + void rejectsMalformedValuesLikeCanonicalParser() { + assertThatThrownBy(() -> CriterionValueCompat.parse("not-emc")) + .isInstanceOf(NumberFormatException.class); + assertThatThrownBy(() -> CriterionValueCompat.parse("k")) + .isInstanceOf(NumberFormatException.class); + } +} diff --git a/compat/criterion-1.20.3/build.gradle b/compat/criterion-1.20.3/build.gradle new file mode 100644 index 0000000..4e443ae --- /dev/null +++ b/compat/criterion-1.20.3/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'net.minecraftforge.gradle' version '6.0.53' +} + +group = 'net.exohayvan.dissolver_enhanced.compat' +version = '1.20.3' + +java { + toolchain.languageVersion = JavaLanguageVersion.of(17) +} + +minecraft { + mappings channel: 'official', version: '1.20.3' +} + +dependencies { + minecraft 'net.minecraftforge:forge:1.20.3-49.0.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3' + testImplementation 'org.assertj:assertj-core:3.26.3' +} + +test { + useJUnitPlatform() +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.release = 17 +} + +jar { + archiveBaseName = 'dissolver-criterion-compat-1.20.3' +} diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java new file mode 100644 index 0000000..ded5bfe --- /dev/null +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java @@ -0,0 +1,45 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; + +import java.math.BigInteger; +import java.util.Locale; + +final class CriterionValueCompat { + private CriterionValueCompat() { + } + + static String baseItemId(String key) { + int componentIndex = key.indexOf("|"); + return componentIndex == -1 ? key : key.substring(0, componentIndex); + } + + static BigInteger nonNegative(BigInteger value) { + return value == null || value.signum() < 0 ? BigInteger.ZERO : value; + } + + static BigInteger parse(String value) { + if (value == null) return BigInteger.ZERO; + + String normalized = value.trim().replace(",", "").replace("_", "").toLowerCase(Locale.ROOT); + if (normalized.isEmpty()) return BigInteger.ZERO; + + BigInteger multiplier = BigInteger.ONE; + String numeric = normalized; + if (normalized.endsWith("k")) { + multiplier = BigInteger.TEN.pow(3); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("m")) { + multiplier = BigInteger.TEN.pow(6); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("b")) { + multiplier = BigInteger.TEN.pow(9); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("t")) { + multiplier = BigInteger.TEN.pow(12); + numeric = normalized.substring(0, normalized.length() - 1); + } else if (normalized.endsWith("q")) { + multiplier = BigInteger.TEN.pow(15); + numeric = normalized.substring(0, normalized.length() - 1); + } + return nonNegative(new BigInteger(numeric).multiply(multiplier)); + } +} diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java new file mode 100644 index 0000000..bc5efc1 --- /dev/null +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java @@ -0,0 +1,34 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; + +import java.math.BigInteger; +import java.util.Optional; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.EntityPredicate; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.server.level.ServerPlayer; + +public class EmcBalanceCriterion1203 extends SimpleCriterionTrigger { + @Override + public Codec codec() { + return Conditions.CODEC; + } + + public void trigger(ServerPlayer player, BigInteger emc) { + trigger(player, conditions -> conditions.matches(emc)); + } + + public record Conditions(Optional player, String minEmc) implements SimpleInstance { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(Conditions::player), + Codec.STRING.optionalFieldOf("min_emc", "0").forGetter(Conditions::minEmc) + ).apply(instance, Conditions::new)); + + public boolean matches(BigInteger emc) { + return CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) >= 0; + } + } +} diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java new file mode 100644 index 0000000..9e16f74 --- /dev/null +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java @@ -0,0 +1,40 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; + +import java.math.BigInteger; +import java.util.Optional; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.EntityPredicate; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.server.level.ServerPlayer; + +public class EmcOrbCriterion1203 extends SimpleCriterionTrigger { + @Override + public Codec codec() { + return Conditions.CODEC; + } + + public void trigger(ServerPlayer player, BigInteger emc, String action) { + trigger(player, conditions -> conditions.matches(emc, action)); + } + + public record Conditions( + Optional player, + String minEmc, + Optional action + ) implements SimpleInstance { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(Conditions::player), + Codec.STRING.optionalFieldOf("min_emc", "0").forGetter(Conditions::minEmc), + Codec.STRING.optionalFieldOf("action").forGetter(Conditions::action) + ).apply(instance, Conditions::new)); + + public boolean matches(BigInteger emc, String currentAction) { + if (CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) < 0) return false; + return action.isEmpty() || action.get().equals(currentAction); + } + } +} diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedCountCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedCountCriterion1203.java new file mode 100644 index 0000000..a1f7f32 --- /dev/null +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedCountCriterion1203.java @@ -0,0 +1,33 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; + +import java.util.Optional; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.EntityPredicate; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.server.level.ServerPlayer; + +public class LearnedCountCriterion1203 extends SimpleCriterionTrigger { + @Override + public Codec codec() { + return Conditions.CODEC; + } + + public void trigger(ServerPlayer player, int learnedItems) { + trigger(player, conditions -> conditions.matches(learnedItems)); + } + + public record Conditions(Optional player, int minItems) implements SimpleInstance { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(Conditions::player), + Codec.INT.optionalFieldOf("min_items", 0).forGetter(Conditions::minItems) + ).apply(instance, Conditions::new)); + + public boolean matches(int learnedItems) { + return learnedItems >= minItems; + } + } +} diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java new file mode 100644 index 0000000..fc38d27 --- /dev/null +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java @@ -0,0 +1,48 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; + +import java.util.Optional; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import net.minecraft.advancements.critereon.ContextAwarePredicate; +import net.minecraft.advancements.critereon.EntityPredicate; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.server.level.ServerPlayer; + +public class LearnedItemCriterion1203 extends SimpleCriterionTrigger { + @Override + public Codec codec() { + return Conditions.CODEC; + } + + public void trigger(ServerPlayer player, String itemId) { + String baseItemId = CriterionValueCompat.baseItemId(itemId); + trigger(player, conditions -> conditions.matches(baseItemId)); + } + + public record Conditions( + Optional player, + Optional item, + Optional externalNamespace + ) implements SimpleInstance { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(Conditions::player), + Codec.STRING.optionalFieldOf("item").forGetter(Conditions::item), + Codec.BOOL.optionalFieldOf("external_namespace").forGetter(Conditions::externalNamespace) + ).apply(instance, Conditions::new)); + + public boolean matches(String itemId) { + if (item.isPresent() && !item.get().equals(itemId)) { + return false; + } + + return externalNamespace.isEmpty() || externalNamespace.get() == isExternalNamespace(itemId); + } + + private static boolean isExternalNamespace(String itemId) { + int namespaceEnd = itemId.indexOf(":"); + return namespaceEnd > 0 && !"minecraft".equals(itemId.substring(0, namespaceEnd)); + } + } +} diff --git a/compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java b/compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java new file mode 100644 index 0000000..45f832e --- /dev/null +++ b/compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java @@ -0,0 +1,32 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; + +import java.math.BigInteger; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CriterionValueCompatTest { + @Test + void parsesCanonicalEmcFormats() { + assertThat(CriterionValueCompat.parse(null)).isEqualTo(BigInteger.ZERO); + assertThat(CriterionValueCompat.parse(" ")).isEqualTo(BigInteger.ZERO); + assertThat(CriterionValueCompat.parse(" 1,000 ")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValueCompat.parse("1_000")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValueCompat.parse("1k")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValueCompat.parse("2M")).isEqualTo(BigInteger.valueOf(2_000_000)); + assertThat(CriterionValueCompat.parse("3b")).isEqualTo(BigInteger.valueOf(3_000_000_000L)); + assertThat(CriterionValueCompat.parse("4t")).isEqualTo(BigInteger.TEN.pow(12).multiply(BigInteger.valueOf(4))); + assertThat(CriterionValueCompat.parse("5q")).isEqualTo(BigInteger.TEN.pow(15).multiply(BigInteger.valueOf(5))); + assertThat(CriterionValueCompat.parse("-1k")).isEqualTo(BigInteger.ZERO); + } + + @Test + void rejectsMalformedValuesLikeCanonicalParser() { + assertThatThrownBy(() -> CriterionValueCompat.parse("not-emc")) + .isInstanceOf(NumberFormatException.class); + assertThatThrownBy(() -> CriterionValueCompat.parse("k")) + .isInstanceOf(NumberFormatException.class); + } +} diff --git a/gradle.properties b/gradle.properties index 998be0f..427d5a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,8 +5,8 @@ org.gradle.parallel=true # Forge Properties minecraft_version=1.20.1 forge_version=47.4.20 -loader_version_range=[47,) -minecraft_version_range=[1.20.1,1.21) +loader_version_range=[46,) +minecraft_version_range=[1.20,1.20.5) # Mod Properties java_version=17 diff --git a/settings.gradle b/settings.gradle index d67ba76..3cf049c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,3 +10,8 @@ pluginManagement { } rootProject.name = 'dissolver-enhanced-forge-1.20.1' + +include 'criterion-compat-1.20.2' +include 'criterion-compat-1.20.3' +project(':criterion-compat-1.20.2').projectDir = file('compat/criterion-1.20.2') +project(':criterion-compat-1.20.3').projectDir = file('compat/criterion-1.20.3') diff --git a/src/client/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlay.java b/src/client/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlay.java index d765366..d9e69d5 100644 --- a/src/client/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlay.java +++ b/src/client/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlay.java @@ -7,11 +7,12 @@ import net.exohayvan.dissolver_enhanced.config.ModConfig; import net.exohayvan.dissolver_enhanced.data.PlayerDataClient; import net.exohayvan.dissolver_enhanced.helpers.NumberHelpers; +import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderGuiEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -@Mod.EventBusSubscriber(modid = DissolverEnhanced.MOD_ID) +@Mod.EventBusSubscriber(modid = DissolverEnhanced.MOD_ID, value = Dist.CLIENT) public class EMCOverlay { private static final int WHITE_COLOR = 0xFFFFFF; diff --git a/src/client/java/net/exohayvan/dissolver_enhanced/packets/DataSenderClient.java b/src/client/java/net/exohayvan/dissolver_enhanced/packets/DataSenderClient.java index 0cae233..3d10c1d 100644 --- a/src/client/java/net/exohayvan/dissolver_enhanced/packets/DataSenderClient.java +++ b/src/client/java/net/exohayvan/dissolver_enhanced/packets/DataSenderClient.java @@ -8,7 +8,7 @@ public class DataSenderClient { public static void sendDataToServer(String messageId, String data) { ClientPayload payload = new ClientPayload(messageId, data); - Packets.CHANNEL.sendToServer(payload); + Packets.sendToServer(payload); } // HELPERS diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/DissolverEnhanced.java b/src/main/java/net/exohayvan/dissolver_enhanced/DissolverEnhanced.java index 6e272d2..1df17d6 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/DissolverEnhanced.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/DissolverEnhanced.java @@ -52,7 +52,7 @@ public DissolverEnhanced() { LOGGER::info, (message, exception) -> LOGGER.warn(message, exception) ); - ModCriteria.init(); + ModCriteria.init(modEventBus); RecipeGenerator.init(); EMCValues.init(); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompat.java new file mode 100644 index 0000000..dc34883 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompat.java @@ -0,0 +1,197 @@ +package net.exohayvan.dissolver_enhanced.advancement; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.advancements.CriterionTrigger; +import net.minecraft.advancements.critereon.SimpleCriterionTrigger; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; + +public final class CriterionCompat { + private static final String ADAPTER_PACKAGE = + "net.exohayvan.dissolver_enhanced.advancement.compat."; + private static final List DEFERRED_CRITERIA = new ArrayList<>(); + + private CriterionCompat() { + } + + public static Object createAndRegister(String id, String criterionClass) { + String adapterGeneration = adapterGeneration(); + Object trigger = create(criterionClass, adapterGeneration); + if ("1203".equals(adapterGeneration)) { + DEFERRED_CRITERIA.add(new PendingCriterion(id, trigger)); + return trigger; + } + return register(id, trigger); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void registerDeferred(IEventBus eventBus) { + if (DEFERRED_CRITERIA.isEmpty()) { + return; + } + ResourceKey registryKey = createRegistryKey(resourceLocation("minecraft", "trigger_type")); + DeferredRegister deferredRegister = DeferredRegister.create(registryKey, "dissolver_enhanced"); + for (PendingCriterion pending : DEFERRED_CRITERIA) { + String path = pending.id().substring(pending.id().indexOf(':') + 1); + deferredRegister.register(path, pending::trigger); + } + deferredRegister.register(eventBus); + } + + public static void trigger(Object criterion, ServerPlayer player, Object... values) { + Object[] arguments = new Object[values.length + 1]; + arguments[0] = player; + System.arraycopy(values, 0, arguments, 1, values.length); + + Method method = findCompatibleMethod(criterion.getClass(), "trigger", arguments); + if (method == null) { + throw new IllegalStateException("Criterion " + criterion.getClass().getName() + " has no compatible trigger method."); + } + invoke(method, criterion, arguments); + } + + private static Object create(String criterionClass, String adapterGeneration) { + String className = adapterGeneration == null + ? "net.exohayvan.dissolver_enhanced.advancement." + criterionClass + : ADAPTER_PACKAGE + 'v' + adapterGeneration + '.' + criterionClass + adapterGeneration; + + try { + return Class.forName(className).getConstructor().newInstance(); + } catch (ReflectiveOperationException | LinkageError exception) { + throw new IllegalStateException("Could not load criterion compatibility adapter " + className, exception); + } + } + + private static String adapterGeneration() { + for (Method method : CriterionTrigger.class.getMethods()) { + if (method.getParameterCount() == 0 && + method.getReturnType().getName().equals("com.mojang.serialization.Codec")) { + return "1203"; + } + } + for (Method method : CriteriaTriggers.class.getDeclaredMethods()) { + Class[] parameters = method.getParameterTypes(); + if (Modifier.isStatic(method.getModifiers()) && + parameters.length == 2 && + parameters[0] == String.class) { + return "1202"; + } + } + return null; + } + + @SuppressWarnings("unchecked") + private static T register(String id, T trigger) { + for (Method method : CriteriaTriggers.class.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers()) || + !method.getReturnType().isInstance(trigger)) { + continue; + } + + Object[] arguments = registrationArguments(method, id, trigger); + if (arguments == null) { + continue; + } + return (T) invoke(method, null, arguments); + } + + throw new IllegalStateException("CriteriaTriggers has no compatible registration method for " + id); + } + + private static Object[] registrationArguments(Method method, String id, Object trigger) { + Class[] parameters = method.getParameterTypes(); + if (parameters.length == 1 && parameters[0].isInstance(trigger)) { + return new Object[] {trigger}; + } + if (parameters.length == 2 && + parameters[0] == String.class && + parameters[1].isInstance(trigger)) { + return new Object[] {id, trigger}; + } + return null; + } + + private static Method findCompatibleMethod(Class type, String name, Object[] arguments) { + for (Method method : type.getMethods()) { + if (!method.getName().equals(name) || method.getParameterCount() != arguments.length) { + continue; + } + Class[] parameters = method.getParameterTypes(); + boolean compatible = true; + for (int index = 0; index < parameters.length; index++) { + if (!isCompatible(parameters[index], arguments[index])) { + compatible = false; + break; + } + } + if (compatible) { + return method; + } + } + return null; + } + + private static boolean isCompatible(Class parameter, Object argument) { + if (argument == null) { + return !parameter.isPrimitive(); + } + if (!parameter.isPrimitive()) { + return parameter.isInstance(argument); + } + return (parameter == int.class && argument instanceof Integer) || + (parameter == boolean.class && argument instanceof Boolean); + } + + private static ResourceLocation resourceLocation(String namespace, String path) { + try { + Constructor constructor = ResourceLocation.class.getDeclaredConstructor(String.class, String.class); + constructor.setAccessible(true); + return constructor.newInstance(namespace, path); + } catch (ReflectiveOperationException ignored) { + for (Method method : ResourceLocation.class.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers()) || method.getReturnType() != ResourceLocation.class) { + continue; + } + Class[] parameters = method.getParameterTypes(); + if (parameters.length == 2 && parameters[0] == String.class && parameters[1] == String.class) { + return (ResourceLocation) invoke(method, null, namespace, path); + } + } + throw new IllegalStateException("Could not construct a ResourceLocation."); + } + } + + @SuppressWarnings("unchecked") + private static ResourceKey>> createRegistryKey(ResourceLocation location) { + for (Method method : ResourceKey.class.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 1 && + method.getParameterTypes()[0] == ResourceLocation.class && method.getReturnType() == ResourceKey.class) { + return (ResourceKey>>) invoke(method, null, location); + } + } + throw new IllegalStateException("Could not construct the criterion registry key."); + } + + private record PendingCriterion(String id, Object trigger) { + } + + private static Object invoke(Method method, Object target, Object... arguments) { + try { + method.setAccessible(true); + return method.invoke(target, arguments); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not invoke " + method.getDeclaringClass().getName() + '.' + method.getName(), exception); + } + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/ModCriteria.java b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/ModCriteria.java index 9e0b3ea..c3eeb8c 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/ModCriteria.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/ModCriteria.java @@ -5,22 +5,31 @@ import java.util.Map; import net.exohayvan.dissolver_enhanced.analytics.ModAnalytics; -import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.IEventBus; public class ModCriteria { - public static final LearnedItemCriterion LEARNED_ITEM = CriteriaTriggers.register(new LearnedItemCriterion()); - public static final EmcBalanceCriterion EMC_BALANCE = CriteriaTriggers.register(new EmcBalanceCriterion()); - public static final LearnedCountCriterion LEARNED_COUNT = CriteriaTriggers.register(new LearnedCountCriterion()); - public static final EmcOrbCriterion EMC_ORB = CriteriaTriggers.register(new EmcOrbCriterion()); + public static final Object LEARNED_ITEM = CriterionCompat.createAndRegister( + "dissolver_enhanced:learned_item", "LearnedItemCriterion" + ); + public static final Object EMC_BALANCE = CriterionCompat.createAndRegister( + "dissolver_enhanced:emc_balance", "EmcBalanceCriterion" + ); + public static final Object LEARNED_COUNT = CriterionCompat.createAndRegister( + "dissolver_enhanced:learned_count", "LearnedCountCriterion" + ); + public static final Object EMC_ORB = CriterionCompat.createAndRegister( + "dissolver_enhanced:emc_orb", "EmcOrbCriterion" + ); - public static void init() { + public static void init(IEventBus eventBus) { + CriterionCompat.registerDeferred(eventBus); } public static void triggerLearnedItem(Player player, String itemId) { if (player instanceof ServerPlayer serverPlayer) { - LEARNED_ITEM.trigger(serverPlayer, itemId); + CriterionCompat.trigger(LEARNED_ITEM, serverPlayer, itemId); Map properties = achievementProperties("learned_item"); properties.put("item_id", itemId); ModAnalytics.captureAchievementEarned("learned_item", properties); @@ -29,7 +38,7 @@ public static void triggerLearnedItem(Player player, String itemId) { public static void triggerEmcBalance(Player player, BigInteger emc) { if (player instanceof ServerPlayer serverPlayer) { - EMC_BALANCE.trigger(serverPlayer, emc); + CriterionCompat.trigger(EMC_BALANCE, serverPlayer, emc); Map properties = achievementProperties("emc_balance"); properties.put("emc_value", emc.toString()); ModAnalytics.captureAchievementEarned("emc_balance", properties); @@ -38,7 +47,7 @@ public static void triggerEmcBalance(Player player, BigInteger emc) { public static void triggerLearnedCount(Player player, int learnedItems) { if (player instanceof ServerPlayer serverPlayer) { - LEARNED_COUNT.trigger(serverPlayer, learnedItems); + CriterionCompat.trigger(LEARNED_COUNT, serverPlayer, learnedItems); Map properties = achievementProperties("learned_count"); properties.put("learned_items", learnedItems); ModAnalytics.captureAchievementEarned("learned_count", properties); @@ -47,7 +56,7 @@ public static void triggerLearnedCount(Player player, int learnedItems) { public static void triggerEmcOrb(Player player, BigInteger emc, String action) { if (player instanceof ServerPlayer serverPlayer) { - EMC_ORB.trigger(serverPlayer, emc, action); + CriterionCompat.trigger(EMC_ORB, serverPlayer, emc, action); Map properties = achievementProperties("emc_orb"); properties.put("emc_value", emc.toString()); properties.put("action", action); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java b/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java index 53e744c..7800819 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java @@ -9,6 +9,7 @@ import net.exohayvan.dissolver_enhanced.config.SimpleConfig; import net.exohayvan.dissolver_enhanced.data.PlayerData; import net.exohayvan.dissolver_enhanced.data.StateSaverAndLoader; +import net.exohayvan.dissolver_enhanced.helpers.MinecraftServerCompat; import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; @@ -266,7 +267,7 @@ private static void captureServerHeartbeat(MinecraftServer server) { properties.put("world_info", "server"); properties.put("game_mode", "server"); properties.put("session_location", "server"); - int connectedPlayers = server.getPlayerCount(); + int connectedPlayers = MinecraftServerCompat.connectedPlayerCount(server); properties.put("player_count", connectedPlayers); properties.put("connected_players", connectedPlayers); properties.put("emc_storage_mode", ModConfig.PRIVATE_EMC ? "private_total" : "shared"); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/ModBlocks.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/ModBlocks.java index 6487ea2..81efc32 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/block/ModBlocks.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/ModBlocks.java @@ -3,6 +3,7 @@ import java.util.function.ToIntFunction; import net.exohayvan.dissolver_enhanced.DissolverEnhanced; import net.exohayvan.dissolver_enhanced.item.DissolverBlockItem; +import net.exohayvan.dissolver_enhanced.helpers.BlockPropertiesCompat; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.Rarity; @@ -32,15 +33,15 @@ public class ModBlocks { // HELPERS private static Block createDissolverBlock() { - return new DissolverBlock(BlockBehaviour.Properties.copy(Blocks.NETHERITE_BLOCK).sound(SoundType.AMETHYST).lightLevel(getLuminance(12)).noOcclusion()); + return new DissolverBlock(BlockPropertiesCompat.copy(Blocks.NETHERITE_BLOCK).sound(SoundType.AMETHYST).lightLevel(getLuminance(12)).noOcclusion()); } private static Block createCondenserBlock() { - return new CondenserBlock(BlockBehaviour.Properties.copy(Blocks.NETHERITE_BLOCK).sound(SoundType.AMETHYST).lightLevel(getLuminance(8)).noOcclusion()); + return new CondenserBlock(BlockPropertiesCompat.copy(Blocks.NETHERITE_BLOCK).sound(SoundType.AMETHYST).lightLevel(getLuminance(8)).noOcclusion()); } private static Block createMaterializerBlock() { - return new MaterializerBlock(BlockBehaviour.Properties.copy(Blocks.NETHERITE_BLOCK).sound(SoundType.AMETHYST).lightLevel(getLuminance(8)).noOcclusion()); + return new MaterializerBlock(BlockPropertiesCompat.copy(Blocks.NETHERITE_BLOCK).sound(SoundType.AMETHYST).lightLevel(getLuminance(8)).noOcclusion()); } private static ToIntFunction getLuminance(int luminance) { diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/data/StateSaverAndLoader.java b/src/main/java/net/exohayvan/dissolver_enhanced/data/StateSaverAndLoader.java index dfa157e..20d3a35 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/data/StateSaverAndLoader.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/data/StateSaverAndLoader.java @@ -17,6 +17,7 @@ import net.exohayvan.dissolver_enhanced.common.values.EmcNumber; import net.exohayvan.dissolver_enhanced.config.ModConfig; import net.exohayvan.dissolver_enhanced.helpers.EMCHelper; +import net.exohayvan.dissolver_enhanced.helpers.SavedDataCompat; import net.exohayvan.dissolver_enhanced.migration.LegacyNamespaceMigration; // https://fabricmc.net/wiki/tutorial:persistent_states#player_specific_persistent_state @@ -210,9 +211,15 @@ public static StateSaverAndLoader getServerState(MinecraftServer server) { // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'. - StateSaverAndLoader state = persistentStateManager.get(StateSaverAndLoader::createFromNbt, DissolverEnhanced.MOD_ID); + StateSaverAndLoader state = SavedDataCompat.get( + persistentStateManager, DissolverEnhanced.MOD_ID, + StateSaverAndLoader::new, StateSaverAndLoader::createFromNbt + ); if (state == null) { - state = persistentStateManager.get(StateSaverAndLoader::createFromNbt, DissolverEnhanced.OLD_MOD_ID); + state = SavedDataCompat.get( + persistentStateManager, DissolverEnhanced.OLD_MOD_ID, + StateSaverAndLoader::new, StateSaverAndLoader::createFromNbt + ); if (state != null) { DissolverEnhanced.LOGGER.info("Migrating player EMC state from {} to {}.", DissolverEnhanced.OLD_MOD_ID, DissolverEnhanced.MOD_ID); persistentStateManager.set(DissolverEnhanced.MOD_ID, state); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeEmcValueLoader.java b/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeEmcValueLoader.java index 57d852f..a8ba3ed 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeEmcValueLoader.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeEmcValueLoader.java @@ -8,6 +8,7 @@ import net.exohayvan.dissolver_enhanced.DissolverEnhanced; import net.exohayvan.dissolver_enhanced.data.EMCValues; import net.exohayvan.dissolver_enhanced.helpers.ItemHelper; +import net.exohayvan.dissolver_enhanced.helpers.RecipeCompat; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.world.item.ItemStack; @@ -39,23 +40,25 @@ private static void loadRecipes(MinecraftServer server) { RECIPE_JSON.clear(); STONE_CUTTER_LIST.clear(); - List> recipes = new ArrayList<>(server.getRecipeManager().getRecipes()); + List recipes = new ArrayList<>(server.getRecipeManager().getRecipes()); EMCValues.beginStartup(recipes.size()); - for (Recipe recipe : recipes) { + for (Object value : recipes) { + ResourceLocation recipeId = null; try { - addRecipe(server, recipe); + RecipeCompat.Entry entry = RecipeCompat.unwrap(value); + recipeId = entry.id(); + addRecipe(server, recipeId, entry.recipe()); } catch (RuntimeException exception) { EMCValues.incrementRecipesNotUnderstood(); - DissolverEnhanced.LOGGER.debug("Could not read recipe {} for EMC calculation.", recipe.getId(), exception); + DissolverEnhanced.LOGGER.debug("Could not read recipe {} for EMC calculation.", recipeId, exception); } } EMCValues.recipesLoaded(RECIPES, RECIPE_SOURCES, RECIPE_JSON, STONE_CUTTER_LIST); } - private static void addRecipe(MinecraftServer server, Recipe recipe) { - ResourceLocation recipeId = recipe.getId(); + private static void addRecipe(MinecraftServer server, ResourceLocation recipeId, Recipe recipe) { RecipeType recipeType = recipe.getType(); ItemStack resultItem = recipe.getResultItem(server.registryAccess()); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/BlockPropertiesCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/BlockPropertiesCompat.java new file mode 100644 index 0000000..fcb2fb6 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/BlockPropertiesCompat.java @@ -0,0 +1,29 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +public final class BlockPropertiesCompat { + private BlockPropertiesCompat() { + } + + public static BlockBehaviour.Properties copy(Block block) { + for (Method method : BlockBehaviour.Properties.class.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 1 && + method.getParameterTypes()[0] == BlockBehaviour.class && + method.getReturnType() == BlockBehaviour.Properties.class) { + try { + method.setAccessible(true); + return (BlockBehaviour.Properties) method.invoke(null, block); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not copy block properties.", exception); + } + } + } + throw new IllegalStateException("BlockBehaviour.Properties has no compatible block-copy factory."); + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/MinecraftServerCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/MinecraftServerCompat.java new file mode 100644 index 0000000..5652e38 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/MinecraftServerCompat.java @@ -0,0 +1,43 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +import net.minecraft.server.MinecraftServer; + +public final class MinecraftServerCompat { + private MinecraftServerCompat() { + } + + public static int connectedPlayerCount(MinecraftServer server) { + Object playerList = invokeNoArgReturning(server, "net.minecraft.server.players.PlayerList"); + for (Method method : playerList.getClass().getMethods()) { + if (method.getParameterCount() == 0 && List.class.isAssignableFrom(method.getReturnType())) { + Object value = invoke(method, playerList); + if (value instanceof List players) { + return players.size(); + } + } + } + throw new IllegalStateException("Could not find the connected-player collection."); + } + + private static Object invokeNoArgReturning(Object target, String returnTypeName) { + for (Method method : target.getClass().getMethods()) { + if (method.getParameterCount() == 0 && method.getReturnType().getName().equals(returnTypeName)) { + return invoke(method, target); + } + } + throw new IllegalStateException("Could not find method returning " + returnTypeName); + } + + private static Object invoke(Method method, Object target) { + try { + method.setAccessible(true); + return method.invoke(target); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not inspect connected players.", exception); + } + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RecipeCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RecipeCompat.java new file mode 100644 index 0000000..91628ce --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RecipeCompat.java @@ -0,0 +1,47 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Recipe; + +public final class RecipeCompat { + private RecipeCompat() { + } + + public static Entry unwrap(Object value) { + if (value instanceof Recipe recipe) { + return new Entry(recipe.getId(), recipe); + } + + ResourceLocation id = null; + Recipe recipe = null; + for (Method method : value.getClass().getMethods()) { + if (method.getParameterCount() != 0) { + continue; + } + if (method.getReturnType() == ResourceLocation.class) { + id = (ResourceLocation) invoke(method, value); + } else if (Recipe.class.isAssignableFrom(method.getReturnType())) { + recipe = (Recipe) invoke(method, value); + } + } + if (id == null || recipe == null) { + throw new IllegalStateException("Unsupported recipe entry " + value.getClass().getName()); + } + return new Entry(id, recipe); + } + + private static Object invoke(Method method, Object target) { + try { + method.setAccessible(true); + return method.invoke(target); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not unwrap recipe entry.", exception); + } + } + + public record Entry(ResourceLocation id, Recipe recipe) { + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java new file mode 100644 index 0000000..942a0aa --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java @@ -0,0 +1,54 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; + +public final class RegistryKeyCompat { + private RegistryKeyCompat() { + } + + @SuppressWarnings("unchecked") + public static ResourceKey> creativeModeTab() { + ResourceLocation location = resourceLocation("minecraft", "creative_mode_tab"); + for (Method method : ResourceKey.class.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 1 && + method.getParameterTypes()[0] == ResourceLocation.class && method.getReturnType() == ResourceKey.class) { + return (ResourceKey>) invoke(method, null, location); + } + } + throw new IllegalStateException("Could not create the creative-mode-tab registry key."); + } + + private static ResourceLocation resourceLocation(String namespace, String path) { + try { + Constructor constructor = ResourceLocation.class.getDeclaredConstructor(String.class, String.class); + constructor.setAccessible(true); + return constructor.newInstance(namespace, path); + } catch (ReflectiveOperationException ignored) { + for (Method method : ResourceLocation.class.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers()) && method.getReturnType() == ResourceLocation.class && + method.getParameterCount() == 2 && method.getParameterTypes()[0] == String.class && + method.getParameterTypes()[1] == String.class) { + return (ResourceLocation) invoke(method, null, namespace, path); + } + } + throw new IllegalStateException("Could not construct a ResourceLocation."); + } + } + + private static Object invoke(Method method, Object target, Object... arguments) { + try { + method.setAccessible(true); + return method.invoke(target, arguments); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not invoke registry compatibility method.", exception); + } + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompat.java new file mode 100644 index 0000000..3468a6e --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompat.java @@ -0,0 +1,90 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.function.Function; +import java.util.function.Supplier; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.saveddata.SavedData; +import net.minecraft.world.level.storage.DimensionDataStorage; + +public final class SavedDataCompat { + private SavedDataCompat() { + } + + @SuppressWarnings("unchecked") + public static T get( + DimensionDataStorage storage, + String id, + Supplier supplier, + Function loader + ) { + for (Method method : storage.getClass().getMethods()) { + Class[] parameters = method.getParameterTypes(); + if (parameters.length != 2 || parameters[1] != String.class || + !SavedData.class.isAssignableFrom(method.getReturnType())) { + continue; + } + + Object firstArgument; + if (parameters[0].isInstance(loader)) { + firstArgument = loader; + } else if (parameters[0].getName().equals("net.minecraft.world.level.saveddata.SavedData$Factory") && + isExistingLookupName(method.getName())) { + firstArgument = createFactory(parameters[0], supplier, loader); + } else { + continue; + } + return (T) invoke(method, storage, firstArgument, id); + } + throw new IllegalStateException("DimensionDataStorage has no compatible saved-data lookup method."); + } + + static boolean isExistingLookupName(String methodName) { + return methodName.equals("get") || methodName.equals("m_164858_"); + } + + private static Object createFactory(Class factoryType, Supplier supplier, Function loader) { + for (Constructor constructor : factoryType.getDeclaredConstructors()) { + Class[] parameters = constructor.getParameterTypes(); + if (parameters.length != 3 || !parameters[0].isInstance(supplier) || !parameters[1].isInstance(loader)) { + continue; + } + Object dataFixType = staticFieldAssignableTo(parameters[2], "LEVEL"); + try { + constructor.setAccessible(true); + return constructor.newInstance(supplier, loader, dataFixType); + } catch (ReflectiveOperationException | RuntimeException exception) { + throw new IllegalStateException("Could not construct SavedData.Factory.", exception); + } + } + throw new IllegalStateException("SavedData.Factory has no compatible constructor."); + } + + private static Object staticFieldAssignableTo(Class type, String preferredName) { + for (Field field : type.getFields()) { + if (Modifier.isStatic(field.getModifiers()) && type.isAssignableFrom(field.getType()) && + field.getName().equals(preferredName)) { + try { + return field.get(null); + } catch (IllegalAccessException exception) { + throw new IllegalStateException("Could not access " + type.getName() + '.' + preferredName, exception); + } + } + } + return null; + } + + private static Object invoke(Method method, Object target, Object... arguments) { + try { + method.setAccessible(true); + return method.invoke(target, arguments); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not load saved data.", exception); + } + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/item/ModItemGroups.java b/src/main/java/net/exohayvan/dissolver_enhanced/item/ModItemGroups.java index 6994090..3ece3f6 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/item/ModItemGroups.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/item/ModItemGroups.java @@ -2,7 +2,7 @@ import net.exohayvan.dissolver_enhanced.DissolverEnhanced; import net.exohayvan.dissolver_enhanced.block.ModBlocks; -import net.minecraft.core.registries.Registries; +import net.exohayvan.dissolver_enhanced.helpers.RegistryKeyCompat; import net.minecraft.network.chat.Component; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTab.ItemDisplayParameters; @@ -14,7 +14,7 @@ public class ModItemGroups { public static final DeferredRegister ITEM_GROUPS = - DeferredRegister.create(Registries.CREATIVE_MODE_TAB, DissolverEnhanced.MOD_ID); + DeferredRegister.create(RegistryKeyCompat.creativeModeTab(), DissolverEnhanced.MOD_ID); public static final RegistryObject VANILLAEMC_GROUP = ITEM_GROUPS.register( "dissolver_enhanced_group", diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/DataSender.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/DataSender.java index 496aba3..52e2822 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/packets/DataSender.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/DataSender.java @@ -11,7 +11,6 @@ import net.exohayvan.dissolver_enhanced.data.PlayerData; import net.exohayvan.dissolver_enhanced.packets.clientbound.EMCValuesPayload; import net.exohayvan.dissolver_enhanced.packets.clientbound.PlayerDataPayload; -import net.minecraftforge.network.PacketDistributor; public class DataSender { private static final Map EMC_SYNC_VERSIONS = new HashMap<>(); @@ -27,9 +26,9 @@ public static void sendPlayerData(Player player, PlayerData data) { server.execute(() -> { if (emcValuesToSend != null) { - Packets.CHANNEL.send(PacketDistributor.PLAYER.with(() -> playerEntity), emcValuesToSend); + Packets.sendToPlayer(playerEntity, emcValuesToSend); } - Packets.CHANNEL.send(PacketDistributor.PLAYER.with(() -> playerEntity), dataToSend); + Packets.sendToPlayer(playerEntity, dataToSend); }); } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java new file mode 100644 index 0000000..7cb22fd --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java @@ -0,0 +1,220 @@ +package net.exohayvan.dissolver_enhanced.packets; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +public final class NetworkCompat { + private static final String PROTOCOL_VERSION = "1"; + private static final int NUMERIC_PROTOCOL_VERSION = 1; + + private NetworkCompat() { + } + + public static Object createChannel(ResourceLocation name) { + try { + Class builderClass = Class.forName("net.minecraftforge.network.ChannelBuilder"); + Object builder = invokeStatic(builderClass, "named", name); + builder = invoke(builder, "networkProtocolVersion", NUMERIC_PROTOCOL_VERSION); + return invoke(builder, "simpleChannel"); + } catch (ClassNotFoundException modernApiUnavailable) { + try { + Class registryClass = Class.forName("net.minecraftforge.network.NetworkRegistry"); + Supplier version = () -> PROTOCOL_VERSION; + Predicate accepted = PROTOCOL_VERSION::equals; + return invokeStatic(registryClass, "newSimpleChannel", name, version, accepted, accepted); + } catch (ClassNotFoundException impossible) { + throw new IllegalStateException("Forge networking API is unavailable.", impossible); + } + } + } + + public static void register( + Object channel, + int id, + Class messageType, + BiConsumer encoder, + Function decoder, + BiConsumer handler, + String direction + ) { + Method legacy = findCompatibleMethod( + channel.getClass(), + "registerMessage", + id, + messageType, + encoder, + decoder, + handler, + Optional.empty() + ); + if (legacy != null) { + invokeMethod(legacy, channel, id, messageType, encoder, decoder, handler, Optional.empty()); + return; + } + + try { + Class directionClass = Class.forName("net.minecraftforge.network.NetworkDirection"); + Object networkDirection; + if (directionClass.isEnum()) { + @SuppressWarnings({"rawtypes", "unchecked"}) + Object enumDirection = Enum.valueOf((Class) directionClass.asSubclass(Enum.class), direction); + networkDirection = enumDirection; + } else { + networkDirection = directionClass.getField(direction).get(null); + } + Object builder = invoke(channel, "messageBuilder", messageType, networkDirection); + builder = invoke(builder, "encoder", encoder); + builder = invoke(builder, "decoder", decoder); + builder = invoke(builder, "consumerNetworkThread", handler); + invoke(builder, "add"); + } catch (ReflectiveOperationException exception) { + throw new IllegalStateException("Forge has no compatible packet registration API.", exception); + } + } + + public static void sendToPlayer(Object channel, ServerPlayer player, Object payload) { + Object target = packetTarget("PLAYER", player, (Supplier) () -> player); + invokeEitherOrder(channel, "send", payload, target); + } + + public static void sendToServer(Object channel, Object payload) { + Method legacy = findCompatibleMethod(channel.getClass(), "sendToServer", payload); + if (legacy != null) { + invokeMethod(legacy, channel, payload); + return; + } + + Object target = packetTarget("SERVER", null, null); + invokeEitherOrder(channel, "send", payload, target); + } + + public static void enqueueWork(Object contextOrSupplier, Runnable task) { + invoke(context(contextOrSupplier), "enqueueWork", task); + } + + public static void setPacketHandled(Object contextOrSupplier) { + invoke(context(contextOrSupplier), "setPacketHandled", true); + } + + public static ServerPlayer sender(Object contextOrSupplier) { + Object sender = invoke(context(contextOrSupplier), "getSender"); + return sender instanceof ServerPlayer serverPlayer ? serverPlayer : null; + } + + private static Object context(Object contextOrSupplier) { + if (contextOrSupplier instanceof Supplier supplier) { + return supplier.get(); + } + return contextOrSupplier; + } + + private static Object packetTarget(String fieldName, Object directArgument, Object legacyArgument) { + try { + Class distributorClass = Class.forName("net.minecraftforge.network.PacketDistributor"); + Field field = distributorClass.getField(fieldName); + Object distributor = field.get(null); + + if (directArgument != null) { + Method direct = findCompatibleMethod(distributor.getClass(), "with", directArgument); + if (direct != null) { + return invokeMethod(direct, distributor, directArgument); + } + } + if (legacyArgument != null) { + Method legacy = findCompatibleMethod(distributor.getClass(), "with", legacyArgument); + if (legacy != null) { + return invokeMethod(legacy, distributor, legacyArgument); + } + } + + Method noArg = findCompatibleMethod(distributor.getClass(), "noArg"); + if (noArg != null) { + return invokeMethod(noArg, distributor); + } + throw new IllegalStateException("PacketDistributor." + fieldName + " has no compatible target factory."); + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException exception) { + throw new IllegalStateException("Could not create Forge packet target " + fieldName + '.', exception); + } + } + + private static void invokeEitherOrder(Object target, String methodName, Object first, Object second) { + Method method = findCompatibleMethod(target.getClass(), methodName, first, second); + if (method != null) { + invokeMethod(method, target, first, second); + return; + } + method = findCompatibleMethod(target.getClass(), methodName, second, first); + if (method != null) { + invokeMethod(method, target, second, first); + return; + } + throw new IllegalStateException(target.getClass().getName() + " has no compatible " + methodName + " method."); + } + + private static Object invokeStatic(Class type, String methodName, Object... arguments) { + Method method = findCompatibleMethod(type, methodName, arguments); + if (method == null || !Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException(type.getName() + " has no compatible static " + methodName + " method."); + } + return invokeMethod(method, null, arguments); + } + + private static Object invoke(Object target, String methodName, Object... arguments) { + Method method = findCompatibleMethod(target.getClass(), methodName, arguments); + if (method == null) { + throw new IllegalStateException(target.getClass().getName() + " has no compatible " + methodName + " method."); + } + return invokeMethod(method, target, arguments); + } + + private static Object invokeMethod(Method method, Object target, Object... arguments) { + try { + method.setAccessible(true); + return method.invoke(target, arguments); + } catch (IllegalAccessException | InvocationTargetException | RuntimeException exception) { + throw new IllegalStateException("Could not invoke " + method.getDeclaringClass().getName() + '.' + method.getName() + '.', exception); + } + } + + private static Method findCompatibleMethod(Class type, String methodName, Object... arguments) { + for (Method method : type.getMethods()) { + if (!method.getName().equals(methodName) || method.getParameterCount() != arguments.length) { + continue; + } + Class[] parameters = method.getParameterTypes(); + boolean compatible = true; + for (int index = 0; index < parameters.length; index++) { + if (!isCompatible(parameters[index], arguments[index])) { + compatible = false; + break; + } + } + if (compatible) { + return method; + } + } + return null; + } + + private static boolean isCompatible(Class parameter, Object argument) { + if (argument == null) { + return !parameter.isPrimitive(); + } + if (!parameter.isPrimitive()) { + return parameter.isInstance(argument); + } + return (parameter == int.class && argument instanceof Integer) || + (parameter == boolean.class && argument instanceof Boolean); + } +} diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/Packets.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/Packets.java index 4ab24dd..e04eb19 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/packets/Packets.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/Packets.java @@ -1,29 +1,30 @@ package net.exohayvan.dissolver_enhanced.packets; -import java.util.Optional; - import net.exohayvan.dissolver_enhanced.DissolverEnhanced; import net.exohayvan.dissolver_enhanced.packets.clientbound.EMCValuesPayload; import net.exohayvan.dissolver_enhanced.packets.clientbound.PlayerDataPayload; import net.exohayvan.dissolver_enhanced.packets.serverbound.ClientPayload; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.network.NetworkRegistry; -import net.minecraftforge.network.simple.SimpleChannel; +import net.minecraft.server.level.ServerPlayer; public class Packets { - private static final String PROTOCOL_VERSION = "1"; - public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( - new ResourceLocation(DissolverEnhanced.MOD_ID, "main"), - () -> PROTOCOL_VERSION, - PROTOCOL_VERSION::equals, - PROTOCOL_VERSION::equals - ); + private static final Object CHANNEL = NetworkCompat.createChannel( + new ResourceLocation(DissolverEnhanced.MOD_ID, "main") + ); + + public static void init() { + DissolverEnhanced.LOGGER.info("Registering packet payloads."); + int id = 0; + NetworkCompat.register(CHANNEL, id++, EMCValuesPayload.class, EMCValuesPayload::encode, EMCValuesPayload::decode, EMCValuesPayload::handle, "PLAY_TO_CLIENT"); + NetworkCompat.register(CHANNEL, id++, PlayerDataPayload.class, PlayerDataPayload::encode, PlayerDataPayload::decode, PlayerDataPayload::handle, "PLAY_TO_CLIENT"); + NetworkCompat.register(CHANNEL, id++, ClientPayload.class, ClientPayload::encode, ClientPayload::decode, ClientPayload::handle, "PLAY_TO_SERVER"); + } + + public static void sendToPlayer(ServerPlayer player, Object payload) { + NetworkCompat.sendToPlayer(CHANNEL, player, payload); + } - public static void init() { - DissolverEnhanced.LOGGER.info("Registering packet payloads."); - int id = 0; - CHANNEL.registerMessage(id++, EMCValuesPayload.class, EMCValuesPayload::encode, EMCValuesPayload::decode, EMCValuesPayload::handle, Optional.empty()); - CHANNEL.registerMessage(id++, PlayerDataPayload.class, PlayerDataPayload::encode, PlayerDataPayload::decode, PlayerDataPayload::handle, Optional.empty()); - CHANNEL.registerMessage(id++, ClientPayload.class, ClientPayload::encode, ClientPayload::decode, ClientPayload::handle, Optional.empty()); - } + public static void sendToServer(Object payload) { + NetworkCompat.sendToServer(CHANNEL, payload); + } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/EMCValuesPayload.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/EMCValuesPayload.java index 3f01228..e752270 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/EMCValuesPayload.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/EMCValuesPayload.java @@ -2,11 +2,10 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; import net.exohayvan.dissolver_enhanced.data.EMCValues; +import net.exohayvan.dissolver_enhanced.packets.NetworkCompat; import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; public record EMCValuesPayload(int version, List values) { public static void encode(EMCValuesPayload payload, FriendlyByteBuf buffer) { @@ -18,9 +17,8 @@ public static EMCValuesPayload decode(FriendlyByteBuf buffer) { return new EMCValuesPayload(buffer.readInt(), buffer.readCollection(ArrayList::new, FriendlyByteBuf::readUtf)); } - public static void handle(EMCValuesPayload payload, Supplier contextSupplier) { - NetworkEvent.Context context = contextSupplier.get(); - context.enqueueWork(() -> EMCValues.applyClientSyncValues(payload.values())); - context.setPacketHandled(true); + public static void handle(EMCValuesPayload payload, Object context) { + NetworkCompat.enqueueWork(context, () -> EMCValues.applyClientSyncValues(payload.values())); + NetworkCompat.setPacketHandled(context); } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/PlayerDataPayload.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/PlayerDataPayload.java index 5c1f6d3..41c19fd 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/PlayerDataPayload.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/clientbound/PlayerDataPayload.java @@ -2,11 +2,10 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; import net.exohayvan.dissolver_enhanced.data.PlayerDataClient; +import net.exohayvan.dissolver_enhanced.packets.NetworkCompat; import net.minecraft.network.FriendlyByteBuf; -import net.minecraftforge.network.NetworkEvent; public record PlayerDataPayload(String emc, int learnedItemsSize, int learnedItemsTotalSize, String message, List learnedItems) { public static void encode(PlayerDataPayload payload, FriendlyByteBuf buffer) { @@ -27,15 +26,14 @@ public static PlayerDataPayload decode(FriendlyByteBuf buffer) { ); } - public static void handle(PlayerDataPayload payload, Supplier contextSupplier) { - NetworkEvent.Context context = contextSupplier.get(); - context.enqueueWork(() -> { + public static void handle(PlayerDataPayload payload, Object context) { + NetworkCompat.enqueueWork(context, () -> { PlayerDataClient.EMC = net.exohayvan.dissolver_enhanced.common.values.EmcNumber.parse(payload.emc()); PlayerDataClient.LEARNED_ITEMS_SIZE = payload.learnedItemsSize(); PlayerDataClient.LEARNED_ITEMS_TOTAL_SIZE = payload.learnedItemsTotalSize(); PlayerDataClient.MESSAGE = payload.message(); PlayerDataClient.LEARNED_ITEMS = payload.learnedItems(); }); - context.setPacketHandled(true); + NetworkCompat.setPacketHandled(context); } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/serverbound/ClientPayload.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/serverbound/ClientPayload.java index 1c925ec..6ff8dcd 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/packets/serverbound/ClientPayload.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/serverbound/ClientPayload.java @@ -1,12 +1,10 @@ package net.exohayvan.dissolver_enhanced.packets.serverbound; -import java.util.function.Supplier; - import net.exohayvan.dissolver_enhanced.DissolverEnhanced; +import net.exohayvan.dissolver_enhanced.packets.NetworkCompat; import net.exohayvan.dissolver_enhanced.screen.ModScreenHandlers; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.entity.player.Player; -import net.minecraftforge.network.NetworkEvent; public record ClientPayload(String messageId, String data) { public static void encode(ClientPayload payload, FriendlyByteBuf buffer) { @@ -18,10 +16,9 @@ public static ClientPayload decode(FriendlyByteBuf buffer) { return new ClientPayload(buffer.readUtf(), buffer.readUtf()); } - public static void handle(ClientPayload payload, Supplier contextSupplier) { - NetworkEvent.Context context = contextSupplier.get(); - context.enqueueWork(() -> receivedData(context.getSender(), payload.messageId(), payload.data())); - context.setPacketHandled(true); + public static void handle(ClientPayload payload, Object context) { + NetworkCompat.enqueueWork(context, () -> receivedData(NetworkCompat.sender(context), payload.messageId(), payload.data())); + NetworkCompat.setPacketHandled(context); } private static void receivedData(Player player, String messageId, String data) { diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/ForgeModMetadataTest.java b/src/test/java/net/exohayvan/dissolver_enhanced/ForgeModMetadataTest.java new file mode 100644 index 0000000..dbf03ad --- /dev/null +++ b/src/test/java/net/exohayvan/dissolver_enhanced/ForgeModMetadataTest.java @@ -0,0 +1,49 @@ +package net.exohayvan.dissolver_enhanced; + +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + +class ForgeModMetadataTest { + @Test + void supportsExactlyThePublishedForgeMinecraft20Versions() throws Exception { + VersionRange range = VersionRange.createFromVersionSpec(properties().getProperty("minecraft_version_range")); + + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.1"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.2"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.3"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.4"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.5"))).isFalse(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.6"))).isFalse(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.20.7"))).isFalse(); + assertThat(range.containsVersion(new DefaultArtifactVersion("1.21"))).isFalse(); + } + + @Test + void supportsEveryRequiredForgeLoaderGeneration() throws Exception { + VersionRange range = VersionRange.createFromVersionSpec(properties().getProperty("loader_version_range")); + + assertThat(range.containsVersion(new DefaultArtifactVersion("46.0.14"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("47.4.21"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("48.1.0"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("49.2.8"))).isTrue(); + assertThat(range.containsVersion(new DefaultArtifactVersion("50.2.9"))).isTrue(); + } + + private static Properties properties() throws IOException { + Properties properties = new Properties(); + try (InputStream input = Files.newInputStream(Path.of("gradle.properties"))) { + properties.load(input); + } + return properties; + } +} diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompatTest.java b/src/test/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompatTest.java new file mode 100644 index 0000000..6c9166f --- /dev/null +++ b/src/test/java/net/exohayvan/dissolver_enhanced/advancement/CriterionCompatTest.java @@ -0,0 +1,25 @@ +package net.exohayvan.dissolver_enhanced.advancement; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +class CriterionCompatTest { + @Test + void modCriteriaAvoidsVersionSpecificCriteriaTriggersRegisterMethod() throws IOException { + byte[] bytecode; + try (InputStream input = getClass().getClassLoader().getResourceAsStream( + "net/exohayvan/dissolver_enhanced/advancement/ModCriteria.class" + )) { + assertThat(input).isNotNull(); + bytecode = input.readAllBytes(); + } + + String constants = new String(bytecode, StandardCharsets.ISO_8859_1); + assertThat(constants).doesNotContain("net/minecraft/advancements/CriteriaTriggers"); + } +} diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java b/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java index e2e97f6..757934d 100644 --- a/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java +++ b/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java @@ -41,6 +41,7 @@ public static List common(){ new EMCRecord("minecraft:small_dripleaf", 4), new EMCRecord("minecraft:moss_block", 2), new EMCRecord("minecraft:raw_gold", 1340), + new EMCRecord("cobblemon:relic_coin", 150), new EMCRecord("minecraft:lily_pad", 2), new EMCRecord("minecraft:tube_coral", 4), new EMCRecord("minecraft:weathered_copper_trapdoor", 748), @@ -395,6 +396,7 @@ public static List skyblocks(){ new EMCRecord("minecraft:small_dripleaf", 92), new EMCRecord("minecraft:moss_block", 90), new EMCRecord("minecraft:raw_gold", 1790), + new EMCRecord("cobblemon:relic_coin", 150), new EMCRecord("minecraft:lily_pad", 90), new EMCRecord("minecraft:tube_coral", 4), new EMCRecord("minecraft:weathered_copper_trapdoor", 748), diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompatTest.java b/src/test/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompatTest.java new file mode 100644 index 0000000..fda8836 --- /dev/null +++ b/src/test/java/net/exohayvan/dissolver_enhanced/helpers/SavedDataCompatTest.java @@ -0,0 +1,15 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class SavedDataCompatTest { + @Test + void selectsOnlyExistingStateLookupMethods() { + assertThat(SavedDataCompat.isExistingLookupName("get")).isTrue(); + assertThat(SavedDataCompat.isExistingLookupName("m_164858_")).isTrue(); + assertThat(SavedDataCompat.isExistingLookupName("computeIfAbsent")).isFalse(); + assertThat(SavedDataCompat.isExistingLookupName("m_164861_")).isFalse(); + } +} diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlayDistTest.java b/src/test/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlayDistTest.java new file mode 100644 index 0000000..9d6709a --- /dev/null +++ b/src/test/java/net/exohayvan/dissolver_enhanced/overlay/EMCOverlayDistTest.java @@ -0,0 +1,26 @@ +package net.exohayvan.dissolver_enhanced.overlay; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +class EMCOverlayDistTest { + @Test + void overlaySubscriberIsRestrictedToThePhysicalClient() throws IOException { + byte[] bytecode; + try (InputStream input = getClass().getClassLoader().getResourceAsStream( + "net/exohayvan/dissolver_enhanced/overlay/EMCOverlay.class" + )) { + assertThat(input).isNotNull(); + bytecode = input.readAllBytes(); + } + + String constants = new String(bytecode, StandardCharsets.ISO_8859_1); + assertThat(constants).contains("net/minecraftforge/api/distmarker/Dist"); + assertThat(constants).contains("CLIENT"); + } +} diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompatTest.java b/src/test/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompatTest.java new file mode 100644 index 0000000..185e11e --- /dev/null +++ b/src/test/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompatTest.java @@ -0,0 +1,29 @@ +package net.exohayvan.dissolver_enhanced.packets; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; + +class NetworkCompatTest { + @Test + void packetEntryPointsDoNotLinkOneForgeNetworkingGeneration() throws IOException { + assertClassDoesNotContain("net/exohayvan/dissolver_enhanced/packets/Packets.class", "net/minecraftforge/network/NetworkRegistry"); + assertClassDoesNotContain("net/exohayvan/dissolver_enhanced/packets/Packets.class", "net/minecraftforge/network/simple/SimpleChannel"); + assertClassDoesNotContain("net/exohayvan/dissolver_enhanced/packets/clientbound/EMCValuesPayload.class", "net/minecraftforge/network/NetworkEvent"); + assertClassDoesNotContain("net/exohayvan/dissolver_enhanced/packets/clientbound/PlayerDataPayload.class", "net/minecraftforge/network/NetworkEvent"); + assertClassDoesNotContain("net/exohayvan/dissolver_enhanced/packets/serverbound/ClientPayload.class", "net/minecraftforge/network/NetworkEvent"); + } + + private void assertClassDoesNotContain(String resource, String symbol) throws IOException { + byte[] bytecode; + try (InputStream input = getClass().getClassLoader().getResourceAsStream(resource)) { + assertThat(input).isNotNull(); + bytecode = input.readAllBytes(); + } + assertThat(new String(bytecode, StandardCharsets.ISO_8859_1)).doesNotContain(symbol); + } +} From bbf04a9b102c67795736753dfe1f967f91689a6c Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Tue, 21 Jul 2026 22:02:40 -0400 Subject: [PATCH 5/6] catching workflows up to common branch --- .github/workflows/build.yml | 68 ++--------------------- .github/workflows/quality-gate.yml | 87 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 63 deletions(-) create mode 100644 .github/workflows/quality-gate.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f963f70..d21ab6f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: build +name: Builds on: push: @@ -24,33 +24,27 @@ jobs: uses: actions/checkout@v4 with: path: ${{ env.COMMON_DIR }} - - name: Validate Gradle wrapper uses: gradle/actions/wrapper-validation@v4 with: min-wrapper-count: 1 - - name: Setup Java 21 uses: actions/setup-java@v4 with: java-version: 21 distribution: microsoft - - name: Setup .NET 6 uses: actions/setup-dotnet@v4 with: dotnet-version: 6.0.x - - name: Build Minecraft Common working-directory: ${{ env.COMMON_DIR }}/minecraft run: | chmod +x ./gradlew ./gradlew build -x test --no-daemon - - name: Build Stardew Valley Common working-directory: ${{ env.COMMON_DIR }} run: dotnet build stardewvalley/src/DissolverEnhanced.StardewValley.Common.csproj --configuration Release - - name: Upload Common artifacts uses: actions/upload-artifact@v7.0.1 with: @@ -66,7 +60,6 @@ jobs: steps: - name: Checkout Common uses: actions/checkout@v4 - - name: Find build branches id: branches shell: bash @@ -88,79 +81,57 @@ jobs: fail-fast: false matrix: branch: ${{ fromJson(needs.discover-version-branches.outputs.branches) }} - steps: - name: Checkout Common uses: actions/checkout@v4 with: ref: ${{ env.COMMON_BRANCH }} path: ${{ env.COMMON_DIR }} - - name: Checkout ${{ matrix.branch }} uses: actions/checkout@v4 with: ref: ${{ matrix.branch }} path: ${{ env.TARGET_DIR }} - - name: Define artifact name id: artifact shell: bash run: | echo "name=$(printf '%s' '${{ matrix.branch }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" - - name: Validate Gradle wrappers if: ${{ startsWith(matrix.branch, 'minecraft/') }} uses: gradle/actions/wrapper-validation@v4 with: min-wrapper-count: 2 - - name: Select Java version if: ${{ startsWith(matrix.branch, 'minecraft/') }} id: java shell: bash run: | - case "${{ matrix.branch }}" in - minecraft/forge-1.20.x) - echo "version=17" >> "$GITHUB_OUTPUT" - echo "distribution=temurin" >> "$GITHUB_OUTPUT" - ;; - minecraft/fabric-26.1.x) - echo "version=25" >> "$GITHUB_OUTPUT" - echo "distribution=temurin" >> "$GITHUB_OUTPUT" - ;; - *) - echo "version=21" >> "$GITHUB_OUTPUT" - echo "distribution=microsoft" >> "$GITHUB_OUTPUT" - ;; - esac - + echo "version=${{ matrix.branch == 'minecraft/forge-1.20.x' && '17' || matrix.branch == 'minecraft/fabric-26.1.x' && '25' || '21' }}" >> "$GITHUB_OUTPUT" + echo "distribution=${{ (matrix.branch == 'minecraft/forge-1.20.x' || matrix.branch == 'minecraft/fabric-26.1.x') && 'temurin' || 'microsoft' }}" >> "$GITHUB_OUTPUT" - name: Setup Java ${{ steps.java.outputs.version }} if: ${{ startsWith(matrix.branch, 'minecraft/') }} uses: actions/setup-java@v4 with: java-version: ${{ steps.java.outputs.version }} distribution: ${{ steps.java.outputs.distribution }} - - name: Build Minecraft ${{ matrix.branch }} if: ${{ startsWith(matrix.branch, 'minecraft/') }} working-directory: ${{ env.TARGET_DIR }} run: | chmod +x ./gradlew ./gradlew build -x test --no-daemon - - name: Upload Minecraft ${{ matrix.branch }} artifacts if: ${{ startsWith(matrix.branch, 'minecraft/') }} uses: actions/upload-artifact@v7.0.1 with: name: ${{ steps.artifact.outputs.name }}-${{ github.sha }} path: ${{ env.TARGET_DIR }}/build/libs/ - - name: Setup .NET 6 if: ${{ startsWith(matrix.branch, 'stardewvalley/') }} uses: actions/setup-dotnet@v4 with: dotnet-version: 6.0.x - - name: Build Stardew Valley ${{ matrix.branch }} if: ${{ startsWith(matrix.branch, 'stardewvalley/') }} working-directory: ${{ env.TARGET_DIR }} @@ -176,7 +147,6 @@ jobs: cp build/*.dll build/manifest.json "package/DissolverEnhanced/" cd package zip -r "../${archive_name}.zip" DissolverEnhanced -x '*.pdb' '*.deps.json' 'ref/*' - - name: Upload Stardew Valley ${{ matrix.branch }} artifacts if: ${{ startsWith(matrix.branch, 'stardewvalley/') }} uses: actions/upload-artifact@v7.0.1 @@ -196,34 +166,28 @@ jobs: uses: actions/download-artifact@v8.0.1 with: path: downloaded-artifacts - - name: Collect release artifacts shell: bash run: | set -euo pipefail mkdir -p release-artifacts - while IFS= read -r -d '' jar; do relative_path="${jar#downloaded-artifacts/}" target_path="release-artifacts/${relative_path}" mkdir -p "$(dirname "${target_path}")" cp "${jar}" "${target_path}" done < <(find downloaded-artifacts -type f -iname '*.jar' ! -iname '*source*' -print0) - while IFS= read -r -d '' zip_file; do relative_path="${zip_file#downloaded-artifacts/}" target_path="release-artifacts/${relative_path}" mkdir -p "$(dirname "${target_path}")" cp "${zip_file}" "${target_path}" done < <(find downloaded-artifacts -type f -iname '*.zip' -print0) - if ! find release-artifacts -type f \( -iname '*.jar' -o -iname '*.zip' \) | grep -q .; then echo "No release artifacts were found in downloaded artifacts." >&2 exit 1 fi - zip -r "all_builds-${GITHUB_SHA}.zip" release-artifacts - - name: Upload all builds zip uses: actions/upload-artifact@v7.0.1 with: @@ -240,72 +204,51 @@ jobs: with: ref: ${{ env.COMMON_BRANCH }} path: ${{ env.COMMON_DIR }} - - name: Checkout current branch uses: actions/checkout@v4 with: ref: ${{ github.ref_name }} path: ${{ env.TARGET_DIR }} - - name: Define artifact name id: current-artifact shell: bash run: | echo "name=$(printf '%s' '${{ github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" - - name: Validate Gradle wrappers if: ${{ startsWith(github.ref_name, 'minecraft/') }} uses: gradle/actions/wrapper-validation@v4 with: min-wrapper-count: 2 - - name: Select Java version if: ${{ startsWith(github.ref_name, 'minecraft/') }} id: current-java shell: bash run: | - case "${{ github.ref_name }}" in - minecraft/forge-1.20.x) - echo "version=17" >> "$GITHUB_OUTPUT" - echo "distribution=temurin" >> "$GITHUB_OUTPUT" - ;; - minecraft/fabric-26.1.x) - echo "version=25" >> "$GITHUB_OUTPUT" - echo "distribution=temurin" >> "$GITHUB_OUTPUT" - ;; - *) - echo "version=21" >> "$GITHUB_OUTPUT" - echo "distribution=microsoft" >> "$GITHUB_OUTPUT" - ;; - esac - + echo "version=${{ github.ref_name == 'minecraft/forge-1.20.x' && '17' || github.ref_name == 'minecraft/fabric-26.1.x' && '25' || '21' }}" >> "$GITHUB_OUTPUT" + echo "distribution=${{ (github.ref_name == 'minecraft/forge-1.20.x' || github.ref_name == 'minecraft/fabric-26.1.x') && 'temurin' || 'microsoft' }}" >> "$GITHUB_OUTPUT" - name: Setup Java ${{ steps.current-java.outputs.version }} if: ${{ startsWith(github.ref_name, 'minecraft/') }} uses: actions/setup-java@v4 with: java-version: ${{ steps.current-java.outputs.version }} distribution: ${{ steps.current-java.outputs.distribution }} - - name: Build current Minecraft branch if: ${{ startsWith(github.ref_name, 'minecraft/') }} working-directory: ${{ env.TARGET_DIR }} run: | chmod +x ./gradlew ./gradlew build -x test --no-daemon - - name: Upload current Minecraft branch artifacts if: ${{ startsWith(github.ref_name, 'minecraft/') }} uses: actions/upload-artifact@v7.0.1 with: name: ${{ steps.current-artifact.outputs.name }}-${{ github.sha }} path: ${{ env.TARGET_DIR }}/build/libs/ - - name: Setup .NET 6 if: ${{ startsWith(github.ref_name, 'stardewvalley/') }} uses: actions/setup-dotnet@v4 with: dotnet-version: 6.0.x - - name: Build current Stardew Valley branch if: ${{ startsWith(github.ref_name, 'stardewvalley/') }} working-directory: ${{ env.TARGET_DIR }} @@ -321,7 +264,6 @@ jobs: cp build/*.dll build/manifest.json "package/DissolverEnhanced/" cd package zip -r "../${archive_name}.zip" DissolverEnhanced -x '*.pdb' '*.deps.json' 'ref/*' - - name: Upload current Stardew Valley branch artifacts if: ${{ startsWith(github.ref_name, 'stardewvalley/') }} uses: actions/upload-artifact@v7.0.1 diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 0000000..6cefe79 --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,87 @@ +name: Quality Gate + +on: + push: + branches: + - "**" + +permissions: + contents: read + +jobs: + duplicate-code: + name: Duplicate Code + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v7 + - name: Install jscpd + run: npm install --global jscpd@5.0.12 + - name: Check for duplicated code + id: duplicate-scan + uses: kucherenko/jscpd@v5.0.12 + with: + skip-install: true + path: . + # Locale JSON files are translation data, not duplicated implementation. + threshold: 0 + min-lines: 8 + min-tokens: 60 + ignore-pattern: '(?m)^(?:package|import)\s+[^;\r\n]+;\s*(?:\r?\n|$)' + reporters: console + upload-sarif: false + ignore: '**/.git/**,**/node_modules/**,**/.venv/**,**/vendor/**,**/dist/**,**/build/**,**/coverage/**,**/generated/**,**/src/main/resources/assets/**/lang/**' + - name: Enforce duplicate-code threshold + if: ${{ always() && steps.duplicate-scan.outputs.exit-code != '0' }} + run: | + echo "::error::Duplicate-code threshold exceeded." + exit 1 + + + workflow-integrity: + name: Workflow Tamper Check + runs-on: ubuntu-latest + steps: + - name: Checkout current branch workflow + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + path: current + persist-credentials: false + sparse-checkout: .github/workflows/quality-gate.yml + sparse-checkout-cone-mode: false + - name: Checkout canonical workflow from common + uses: actions/checkout@v7 + with: + ref: common + path: canonical + persist-credentials: false + sparse-checkout: .github/workflows/quality-gate.yml + sparse-checkout-cone-mode: false + - name: Compare Quality Gate workflows + shell: bash + run: | + set -euo pipefail + CURRENT="current/.github/workflows/quality-gate.yml" + CANONICAL="canonical/.github/workflows/quality-gate.yml" + if [[ ! -f "$CURRENT" ]]; then + echo "::error file=.github/workflows/quality-gate.yml::Quality Gate workflow is missing from the current branch." + exit 1 + fi + if [[ ! -f "$CANONICAL" ]]; then + echo "::error::Canonical Quality Gate workflow is missing from the common branch." + exit 1 + fi + if ! cmp --silent "$CANONICAL" "$CURRENT"; then + echo "::error file=.github/workflows/quality-gate.yml::Quality Gate does not match the common branch." + echo + echo "Workflow differences:" + diff \ + --unified \ + --label "common/.github/workflows/quality-gate.yml" \ + --label "current/.github/workflows/quality-gate.yml" \ + "$CANONICAL" \ + "$CURRENT" || true + exit 1 + fi + echo "Quality Gate matches the canonical workflow on common." \ No newline at end of file From 40b88f8e96a8fee6e8db82c980e5d71716cc1d2d Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Tue, 21 Jul 2026 23:43:05 -0400 Subject: [PATCH 6/6] refactor: eliminate duplicate code --- build.gradle | 1 + compat/criterion-1.20.2/build.gradle | 3 + .../compat/v1202/CriterionValueCompat.java | 45 -- .../compat/v1202/EmcBalanceCriterion1202.java | 7 +- .../compat/v1202/EmcOrbCriterion1202.java | 8 +- .../v1202/LearnedItemCriterion1202.java | 23 +- .../v1202/CriterionValueCompatTest.java | 32 -- compat/criterion-1.20.3/build.gradle | 3 + .../compat/v1203/CriterionValueCompat.java | 45 -- .../compat/v1203/EmcBalanceCriterion1203.java | 4 +- .../compat/v1203/EmcOrbCriterion1203.java | 5 +- .../v1203/LearnedItemCriterion1203.java | 3 +- .../v1203/CriterionValueCompatTest.java | 32 -- .../advancement/compat/CriterionValues.java | 61 +++ .../compat/CriterionValuesTest.java | 29 ++ .../screen/AbstractMachineScreen.java | 65 +++ .../screen/CondenserScreen.java | 54 +-- .../screen/MaterializerScreen.java | 54 +-- .../advancement/CriterionCompat.java | 57 +-- .../advancement/EmcBalanceCriterion.java | 11 +- .../advancement/EmcOrbCriterion.java | 25 +- .../advancement/LearnedCountCriterion.java | 20 +- .../advancement/LearnedItemCriterion.java | 21 +- .../analytics/ModAnalytics.java | 34 +- .../block/CondenserBlock.java | 26 +- .../block/InteractiveMachineBlock.java | 41 ++ .../block/MaterializerBlock.java | 28 +- .../block/entity/CondenserBlockEntity.java | 13 +- .../block/entity/CustomBlockEntity.java | 6 + .../block/entity/MaterializerBlockEntity.java | 13 +- .../dissolver_enhanced/command/DebugItem.java | 154 +++---- .../command/LearnItems.java | 42 +- .../event/ForgeItemTooltipEvent.java | 25 +- .../dissolver_enhanced/helpers/EMCHelper.java | 21 +- .../helpers/MachineRate.java | 16 + .../helpers/ReflectionCompat.java | 58 +++ .../helpers/RegistryKeyCompat.java | 20 +- .../helpers/TooltipHelper.java | 31 ++ .../inventory/DissolverInventory.java | 4 +- .../inventory/DissolverInventoryInput.java | 96 +--- .../mixin/ItemStackTooltipMixin.java | 25 +- .../mixin/RecipeManagerMixin.java | 34 +- .../packets/NetworkCompat.java | 49 +- .../screen/CondenserScreenHandler.java | 81 +--- .../screen/MachineScreenHandler.java | 89 ++++ .../screen/MaterializerScreenHandler.java | 90 +--- .../dissolver_enhanced/data/EMCExpected.java | 427 ++++-------------- 47 files changed, 764 insertions(+), 1267 deletions(-) delete mode 100644 compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java delete mode 100644 compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java delete mode 100644 compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java delete mode 100644 compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java create mode 100644 compat/criterion-shared/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValues.java create mode 100644 compat/criterion-shared/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValuesTest.java create mode 100644 src/client/java/net/exohayvan/dissolver_enhanced/screen/AbstractMachineScreen.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/block/InteractiveMachineBlock.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/MachineRate.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/ReflectionCompat.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/helpers/TooltipHelper.java create mode 100644 src/main/java/net/exohayvan/dissolver_enhanced/screen/MachineScreenHandler.java diff --git a/build.gradle b/build.gradle index b66d62e..62ca459 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,7 @@ sourceSets { main { java { srcDirs += ['src/client/java'] + srcDirs += ['compat/criterion-shared/src/main/java'] srcDirs += ["${commonDir}/src/main/java"] } resources { diff --git a/compat/criterion-1.20.2/build.gradle b/compat/criterion-1.20.2/build.gradle index 7630970..5a690ac 100644 --- a/compat/criterion-1.20.2/build.gradle +++ b/compat/criterion-1.20.2/build.gradle @@ -9,6 +9,9 @@ java { toolchain.languageVersion = JavaLanguageVersion.of(17) } +sourceSets.main.java.srcDir '../criterion-shared/src/main/java' +sourceSets.test.java.srcDir '../criterion-shared/src/test/java' + minecraft { mappings channel: 'official', version: '1.20.2' } diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java deleted file mode 100644 index 21034fe..0000000 --- a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompat.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; - -import java.math.BigInteger; -import java.util.Locale; - -final class CriterionValueCompat { - private CriterionValueCompat() { - } - - static String baseItemId(String key) { - int componentIndex = key.indexOf("|"); - return componentIndex == -1 ? key : key.substring(0, componentIndex); - } - - static BigInteger nonNegative(BigInteger value) { - return value == null || value.signum() < 0 ? BigInteger.ZERO : value; - } - - static BigInteger parse(String value) { - if (value == null) return BigInteger.ZERO; - - String normalized = value.trim().replace(",", "").replace("_", "").toLowerCase(Locale.ROOT); - if (normalized.isEmpty()) return BigInteger.ZERO; - - BigInteger multiplier = BigInteger.ONE; - String numeric = normalized; - if (normalized.endsWith("k")) { - multiplier = BigInteger.TEN.pow(3); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("m")) { - multiplier = BigInteger.TEN.pow(6); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("b")) { - multiplier = BigInteger.TEN.pow(9); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("t")) { - multiplier = BigInteger.TEN.pow(12); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("q")) { - multiplier = BigInteger.TEN.pow(15); - numeric = normalized.substring(0, normalized.length() - 1); - } - return nonNegative(new BigInteger(numeric).multiply(multiplier)); - } -} diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java index ea57f4c..c2cfb7c 100644 --- a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcBalanceCriterion1202.java @@ -5,6 +5,8 @@ import java.math.BigInteger; import java.util.Optional; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; + import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; import net.minecraft.advancements.critereon.DeserializationContext; @@ -17,8 +19,7 @@ public class EmcBalanceCriterion1202 extends SimpleCriterionTrigger player, DeserializationContext context) { - String minEmc = jsonObject.has("min_emc") ? jsonObject.get("min_emc").getAsString() : "0"; - return new Conditions(player, minEmc); + return new Conditions(player, CriterionValues.minimumEmc(jsonObject)); } public void trigger(ServerPlayer player, BigInteger emc) { @@ -34,7 +35,7 @@ public Conditions(Optional player, String minEmc) { } public boolean matches(BigInteger emc) { - return CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) >= 0; + return CriterionValues.meetsMinimum(emc, minEmc); } } } diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java index 87181e2..a516905 100644 --- a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/EmcOrbCriterion1202.java @@ -5,6 +5,8 @@ import java.math.BigInteger; import java.util.Optional; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; + import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; import net.minecraft.advancements.critereon.DeserializationContext; @@ -17,9 +19,8 @@ public class EmcOrbCriterion1202 extends SimpleCriterionTrigger player, DeserializationContext context) { - String minEmc = jsonObject.has("min_emc") ? jsonObject.get("min_emc").getAsString() : "0"; String action = jsonObject.has("action") ? jsonObject.get("action").getAsString() : null; - return new Conditions(player, minEmc, action); + return new Conditions(player, CriterionValues.minimumEmc(jsonObject), action); } public void trigger(ServerPlayer player, BigInteger emc, String action) { @@ -37,8 +38,7 @@ public Conditions(Optional player, String minEmc, String } public boolean matches(BigInteger emc, String currentAction) { - if (CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) < 0) return false; - return action == null || action.equals(currentAction); + return CriterionValues.meetsMinimum(emc, minEmc) && (action == null || action.equals(currentAction)); } } } diff --git a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java index 49630e3..b522399 100644 --- a/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java +++ b/compat/criterion-1.20.2/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/LearnedItemCriterion1202.java @@ -1,6 +1,7 @@ package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; import com.google.gson.JsonObject; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; import java.util.Optional; import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; @@ -14,14 +15,11 @@ public class LearnedItemCriterion1202 extends SimpleCriterionTrigger player, DeserializationContext context) { - String item = jsonObject.has("item") ? jsonObject.get("item").getAsString() : null; - boolean hasExternalNamespace = jsonObject.has("external_namespace"); - Boolean externalNamespace = hasExternalNamespace ? jsonObject.get("external_namespace").getAsBoolean() : null; - return new Conditions(player, item, externalNamespace); + return new Conditions(player, CriterionValues.optionalItem(jsonObject), CriterionValues.optionalExternalNamespace(jsonObject)); } public void trigger(ServerPlayer player, String itemId) { - String baseItemId = CriterionValueCompat.baseItemId(itemId); + String baseItemId = CriterionValues.baseItemId(itemId); trigger(player, conditions -> conditions.matches(baseItemId)); } @@ -36,20 +34,7 @@ public Conditions(Optional player, String item, Boolean e } public boolean matches(String itemId) { - if (item != null && !item.equals(itemId)) { - return false; - } - - if (externalNamespace != null && externalNamespace != isExternalNamespace(itemId)) { - return false; - } - - return true; - } - - private static boolean isExternalNamespace(String itemId) { - int namespaceEnd = itemId.indexOf(":"); - return namespaceEnd > 0 && !"minecraft".equals(itemId.substring(0, namespaceEnd)); + return CriterionValues.matchesLearnedItem(item, externalNamespace, itemId); } } } diff --git a/compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java b/compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java deleted file mode 100644 index a068806..0000000 --- a/compat/criterion-1.20.2/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1202/CriterionValueCompatTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.exohayvan.dissolver_enhanced.advancement.compat.v1202; - -import java.math.BigInteger; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class CriterionValueCompatTest { - @Test - void parsesCanonicalEmcFormats() { - assertThat(CriterionValueCompat.parse(null)).isEqualTo(BigInteger.ZERO); - assertThat(CriterionValueCompat.parse(" ")).isEqualTo(BigInteger.ZERO); - assertThat(CriterionValueCompat.parse(" 1,000 ")).isEqualTo(BigInteger.valueOf(1_000)); - assertThat(CriterionValueCompat.parse("1_000")).isEqualTo(BigInteger.valueOf(1_000)); - assertThat(CriterionValueCompat.parse("1k")).isEqualTo(BigInteger.valueOf(1_000)); - assertThat(CriterionValueCompat.parse("2M")).isEqualTo(BigInteger.valueOf(2_000_000)); - assertThat(CriterionValueCompat.parse("3b")).isEqualTo(BigInteger.valueOf(3_000_000_000L)); - assertThat(CriterionValueCompat.parse("4t")).isEqualTo(BigInteger.TEN.pow(12).multiply(BigInteger.valueOf(4))); - assertThat(CriterionValueCompat.parse("5q")).isEqualTo(BigInteger.TEN.pow(15).multiply(BigInteger.valueOf(5))); - assertThat(CriterionValueCompat.parse("-1k")).isEqualTo(BigInteger.ZERO); - } - - @Test - void rejectsMalformedValuesLikeCanonicalParser() { - assertThatThrownBy(() -> CriterionValueCompat.parse("not-emc")) - .isInstanceOf(NumberFormatException.class); - assertThatThrownBy(() -> CriterionValueCompat.parse("k")) - .isInstanceOf(NumberFormatException.class); - } -} diff --git a/compat/criterion-1.20.3/build.gradle b/compat/criterion-1.20.3/build.gradle index 4e443ae..db51f6d 100644 --- a/compat/criterion-1.20.3/build.gradle +++ b/compat/criterion-1.20.3/build.gradle @@ -9,6 +9,9 @@ java { toolchain.languageVersion = JavaLanguageVersion.of(17) } +sourceSets.main.java.srcDir '../criterion-shared/src/main/java' +sourceSets.test.java.srcDir '../criterion-shared/src/test/java' + minecraft { mappings channel: 'official', version: '1.20.3' } diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java deleted file mode 100644 index ded5bfe..0000000 --- a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompat.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; - -import java.math.BigInteger; -import java.util.Locale; - -final class CriterionValueCompat { - private CriterionValueCompat() { - } - - static String baseItemId(String key) { - int componentIndex = key.indexOf("|"); - return componentIndex == -1 ? key : key.substring(0, componentIndex); - } - - static BigInteger nonNegative(BigInteger value) { - return value == null || value.signum() < 0 ? BigInteger.ZERO : value; - } - - static BigInteger parse(String value) { - if (value == null) return BigInteger.ZERO; - - String normalized = value.trim().replace(",", "").replace("_", "").toLowerCase(Locale.ROOT); - if (normalized.isEmpty()) return BigInteger.ZERO; - - BigInteger multiplier = BigInteger.ONE; - String numeric = normalized; - if (normalized.endsWith("k")) { - multiplier = BigInteger.TEN.pow(3); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("m")) { - multiplier = BigInteger.TEN.pow(6); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("b")) { - multiplier = BigInteger.TEN.pow(9); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("t")) { - multiplier = BigInteger.TEN.pow(12); - numeric = normalized.substring(0, normalized.length() - 1); - } else if (normalized.endsWith("q")) { - multiplier = BigInteger.TEN.pow(15); - numeric = normalized.substring(0, normalized.length() - 1); - } - return nonNegative(new BigInteger(numeric).multiply(multiplier)); - } -} diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java index bc5efc1..6061cc7 100644 --- a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcBalanceCriterion1203.java @@ -1,6 +1,8 @@ package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; import java.math.BigInteger; + +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; import java.util.Optional; import com.mojang.serialization.Codec; @@ -28,7 +30,7 @@ public record Conditions(Optional player, String minEmc) ).apply(instance, Conditions::new)); public boolean matches(BigInteger emc) { - return CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) >= 0; + return CriterionValues.meetsMinimum(emc, minEmc); } } } diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java index 9e16f74..57fcc92 100644 --- a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/EmcOrbCriterion1203.java @@ -1,6 +1,8 @@ package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; import java.math.BigInteger; + +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; import java.util.Optional; import com.mojang.serialization.Codec; @@ -33,8 +35,7 @@ public record Conditions( ).apply(instance, Conditions::new)); public boolean matches(BigInteger emc, String currentAction) { - if (CriterionValueCompat.nonNegative(emc).compareTo(CriterionValueCompat.parse(minEmc)) < 0) return false; - return action.isEmpty() || action.get().equals(currentAction); + return CriterionValues.meetsMinimum(emc, minEmc) && (action.isEmpty() || action.get().equals(currentAction)); } } } diff --git a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java index fc38d27..fb6d831 100644 --- a/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java +++ b/compat/criterion-1.20.3/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/LearnedItemCriterion1203.java @@ -4,6 +4,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; import net.minecraft.advancements.critereon.ContextAwarePredicate; import net.minecraft.advancements.critereon.EntityPredicate; @@ -17,7 +18,7 @@ public Codec codec() { } public void trigger(ServerPlayer player, String itemId) { - String baseItemId = CriterionValueCompat.baseItemId(itemId); + String baseItemId = CriterionValues.baseItemId(itemId); trigger(player, conditions -> conditions.matches(baseItemId)); } diff --git a/compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java b/compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java deleted file mode 100644 index 45f832e..0000000 --- a/compat/criterion-1.20.3/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/v1203/CriterionValueCompatTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.exohayvan.dissolver_enhanced.advancement.compat.v1203; - -import java.math.BigInteger; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class CriterionValueCompatTest { - @Test - void parsesCanonicalEmcFormats() { - assertThat(CriterionValueCompat.parse(null)).isEqualTo(BigInteger.ZERO); - assertThat(CriterionValueCompat.parse(" ")).isEqualTo(BigInteger.ZERO); - assertThat(CriterionValueCompat.parse(" 1,000 ")).isEqualTo(BigInteger.valueOf(1_000)); - assertThat(CriterionValueCompat.parse("1_000")).isEqualTo(BigInteger.valueOf(1_000)); - assertThat(CriterionValueCompat.parse("1k")).isEqualTo(BigInteger.valueOf(1_000)); - assertThat(CriterionValueCompat.parse("2M")).isEqualTo(BigInteger.valueOf(2_000_000)); - assertThat(CriterionValueCompat.parse("3b")).isEqualTo(BigInteger.valueOf(3_000_000_000L)); - assertThat(CriterionValueCompat.parse("4t")).isEqualTo(BigInteger.TEN.pow(12).multiply(BigInteger.valueOf(4))); - assertThat(CriterionValueCompat.parse("5q")).isEqualTo(BigInteger.TEN.pow(15).multiply(BigInteger.valueOf(5))); - assertThat(CriterionValueCompat.parse("-1k")).isEqualTo(BigInteger.ZERO); - } - - @Test - void rejectsMalformedValuesLikeCanonicalParser() { - assertThatThrownBy(() -> CriterionValueCompat.parse("not-emc")) - .isInstanceOf(NumberFormatException.class); - assertThatThrownBy(() -> CriterionValueCompat.parse("k")) - .isInstanceOf(NumberFormatException.class); - } -} diff --git a/compat/criterion-shared/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValues.java b/compat/criterion-shared/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValues.java new file mode 100644 index 0000000..f37472b --- /dev/null +++ b/compat/criterion-shared/src/main/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValues.java @@ -0,0 +1,61 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat; + +import java.math.BigInteger; +import java.util.Locale; + +import com.google.gson.JsonObject; + +public final class CriterionValues { + private CriterionValues() { + } + + public static String baseItemId(String key) { + int componentIndex = key.indexOf('|'); + return componentIndex == -1 ? key : key.substring(0, componentIndex); + } + + public static BigInteger nonNegative(BigInteger value) { + return value == null || value.signum() < 0 ? BigInteger.ZERO : value; + } + + public static BigInteger parse(String value) { + if (value == null) return BigInteger.ZERO; + String normalized = value.trim().replace(",", "").replace("_", "").toLowerCase(Locale.ROOT); + if (normalized.isEmpty()) return BigInteger.ZERO; + + int exponent = switch (normalized.charAt(normalized.length() - 1)) { + case 'k' -> 3; + case 'm' -> 6; + case 'b' -> 9; + case 't' -> 12; + case 'q' -> 15; + default -> 0; + }; + String numeric = exponent == 0 ? normalized : normalized.substring(0, normalized.length() - 1); + return nonNegative(new BigInteger(numeric).multiply(BigInteger.TEN.pow(exponent))); + } + + public static String minimumEmc(JsonObject json) { + return json.has("min_emc") ? json.get("min_emc").getAsString() : "0"; + } + + public static boolean meetsMinimum(BigInteger value, String minimum) { + return nonNegative(value).compareTo(parse(minimum)) >= 0; + } + + public static String optionalItem(JsonObject json) { + return json.has("item") ? json.get("item").getAsString() : null; + } + + public static Boolean optionalExternalNamespace(JsonObject json) { + return json.has("external_namespace") ? json.get("external_namespace").getAsBoolean() : null; + } + + public static boolean matchesLearnedItem(String expectedItem, Boolean externalNamespace, String itemId) { + if (expectedItem != null && !expectedItem.equals(itemId)) return false; + if (externalNamespace == null) return true; + int namespaceEnd = itemId.indexOf(':'); + boolean external = namespaceEnd > 0 && !"minecraft".equals(itemId.substring(0, namespaceEnd)); + return externalNamespace == external; + } +} \ No newline at end of file diff --git a/compat/criterion-shared/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValuesTest.java b/compat/criterion-shared/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValuesTest.java new file mode 100644 index 0000000..3afd4ce --- /dev/null +++ b/compat/criterion-shared/src/test/java/net/exohayvan/dissolver_enhanced/advancement/compat/CriterionValuesTest.java @@ -0,0 +1,29 @@ +package net.exohayvan.dissolver_enhanced.advancement.compat; + +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CriterionValuesTest { + @Test + void parsesCanonicalEmcFormats() { + assertThat(CriterionValues.parse(null)).isEqualTo(BigInteger.ZERO); + assertThat(CriterionValues.parse(" ")).isEqualTo(BigInteger.ZERO); + assertThat(CriterionValues.parse(" 1,000 ")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValues.parse("1_000")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValues.parse("1k")).isEqualTo(BigInteger.valueOf(1_000)); + assertThat(CriterionValues.parse("2M")).isEqualTo(BigInteger.valueOf(2_000_000)); + assertThat(CriterionValues.parse("3b")).isEqualTo(BigInteger.valueOf(3_000_000_000L)); + assertThat(CriterionValues.parse("4t")).isEqualTo(BigInteger.TEN.pow(12).multiply(BigInteger.valueOf(4))); + assertThat(CriterionValues.parse("5q")).isEqualTo(BigInteger.TEN.pow(15).multiply(BigInteger.valueOf(5))); + assertThat(CriterionValues.parse("-1k")).isEqualTo(BigInteger.ZERO); + } + + @Test + void rejectsMalformedValuesLikeCanonicalParser() { + assertThatThrownBy(() -> CriterionValues.parse("not-emc")).isInstanceOf(NumberFormatException.class); + assertThatThrownBy(() -> CriterionValues.parse("k")).isInstanceOf(NumberFormatException.class); + } +} \ No newline at end of file diff --git a/src/client/java/net/exohayvan/dissolver_enhanced/screen/AbstractMachineScreen.java b/src/client/java/net/exohayvan/dissolver_enhanced/screen/AbstractMachineScreen.java new file mode 100644 index 0000000..1987af5 --- /dev/null +++ b/src/client/java/net/exohayvan/dissolver_enhanced/screen/AbstractMachineScreen.java @@ -0,0 +1,65 @@ +package net.exohayvan.dissolver_enhanced.screen; + +import java.math.BigInteger; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.exohayvan.dissolver_enhanced.common.values.EmcNumber; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; + +abstract class AbstractMachineScreen extends AbstractContainerScreen { + protected static final ResourceLocation FURNACE_TEXTURE = new ResourceLocation("minecraft", "textures/gui/container/furnace.png"); + protected static final int ARROW_X = 79; + protected static final int ARROW_Y = 34; + protected static final int ARROW_WIDTH = 24; + protected static final int ARROW_HEIGHT = 16; + + protected AbstractMachineScreen(T handler, Inventory inventory, Component title) { + super(handler, inventory, title); + this.imageWidth = 176; + this.imageHeight = 166; + } + + @Override + protected void init() { + super.init(); + this.titleLabelX = 8; + this.titleLabelY = 6; + this.inventoryLabelX = 8; + this.inventoryLabelY = 72; + } + + protected void drawProgressArrow(GuiGraphics context, int progress) { + int width = Math.min(ARROW_WIDTH, progress); + if (width > 0) { + context.blit(FURNACE_TEXTURE, this.leftPos + ARROW_X, this.topPos + ARROW_Y, 176, 14, width, ARROW_HEIGHT); + } + } + + protected void drawFurnaceBackground(GuiGraphics context) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + RenderSystem.setShaderTexture(0, FURNACE_TEXTURE); + context.blit(FURNACE_TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); + } + + protected static String formatEmc(BigInteger value) { + return EmcNumber.format(value); + } + + protected static String formatEmc(int value) { + return formatEmc(BigInteger.valueOf(value)); + } + + @Override + public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { + renderBackground(context); + super.render(context, mouseX, mouseY, delta); + renderTooltip(context, mouseX, mouseY); + } +} \ No newline at end of file diff --git a/src/client/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreen.java b/src/client/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreen.java index c44de75..fd67726 100644 --- a/src/client/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreen.java +++ b/src/client/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreen.java @@ -1,27 +1,15 @@ package net.exohayvan.dissolver_enhanced.screen; -import java.math.BigInteger; - -import com.mojang.blaze3d.systems.RenderSystem; - -import net.exohayvan.dissolver_enhanced.common.values.EmcNumber; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; -public class CondenserScreen extends AbstractContainerScreen { - private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft", "textures/gui/container/furnace.png"); +public class CondenserScreen extends AbstractMachineScreen { private static final int FLAME_X = 56; private static final int FLAME_Y = 36; private static final int FLAME_WIDTH = 14; private static final int FLAME_HEIGHT = 14; - private static final int ARROW_X = 79; - private static final int ARROW_Y = 34; - private static final int ARROW_WIDTH = 24; - private static final int ARROW_HEIGHT = 16; + private static final int GUI_BACKGROUND = 0xFFC6C6C6; private static final int STATUS_X = 78; private static final int RATE_Y = 54; @@ -30,54 +18,24 @@ public class CondenserScreen extends AbstractContainerScreen { - private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft", "textures/gui/container/furnace.png"); +public class MaterializerScreen extends AbstractMachineScreen { private static final int FLAME_X = 56; private static final int FLAME_Y = 36; private static final int FLAME_WIDTH = 14; private static final int FLAME_HEIGHT = 14; - private static final int ARROW_X = 79; - private static final int ARROW_Y = 34; - private static final int ARROW_WIDTH = 24; - private static final int ARROW_HEIGHT = 16; + private static final int GUI_BACKGROUND = 0xFFC6C6C6; private static final int SLOT_DARK = 0xFF373737; private static final int SLOT_LIGHT = 0xFFFFFFFF; @@ -33,29 +21,15 @@ public class MaterializerScreen extends AbstractContainerScreen type, String name, Object[] arguments) { - for (Method method : type.getMethods()) { - if (!method.getName().equals(name) || method.getParameterCount() != arguments.length) { - continue; - } - Class[] parameters = method.getParameterTypes(); - boolean compatible = true; - for (int index = 0; index < parameters.length; index++) { - if (!isCompatible(parameters[index], arguments[index])) { - compatible = false; - break; - } - } - if (compatible) { - return method; - } - } - return null; - } - - private static boolean isCompatible(Class parameter, Object argument) { - if (argument == null) { - return !parameter.isPrimitive(); - } - if (!parameter.isPrimitive()) { - return parameter.isInstance(argument); - } - return (parameter == int.class && argument instanceof Integer) || - (parameter == boolean.class && argument instanceof Boolean); - } - private static ResourceLocation resourceLocation(String namespace, String path) { - try { - Constructor constructor = ResourceLocation.class.getDeclaredConstructor(String.class, String.class); - constructor.setAccessible(true); - return constructor.newInstance(namespace, path); - } catch (ReflectiveOperationException ignored) { - for (Method method : ResourceLocation.class.getDeclaredMethods()) { - if (!Modifier.isStatic(method.getModifiers()) || method.getReturnType() != ResourceLocation.class) { - continue; - } - Class[] parameters = method.getParameterTypes(); - if (parameters.length == 2 && parameters[0] == String.class && parameters[1] == String.class) { - return (ResourceLocation) invoke(method, null, namespace, path); - } - } - throw new IllegalStateException("Could not construct a ResourceLocation."); - } + return ReflectionCompat.resourceLocation( + namespace, + path, + method -> (ResourceLocation) invoke(method, null, namespace, path) + ); } @SuppressWarnings("unchecked") diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcBalanceCriterion.java b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcBalanceCriterion.java index b51487c..e5aa6ef 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcBalanceCriterion.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcBalanceCriterion.java @@ -3,6 +3,7 @@ import com.google.gson.JsonObject; import java.math.BigInteger; +import java.util.function.Predicate; import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; @@ -11,6 +12,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.exohayvan.dissolver_enhanced.common.values.EmcNumber; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; public class EmcBalanceCriterion extends SimpleCriterionTrigger { public static final ResourceLocation ID = new ResourceLocation("dissolver_enhanced", "emc_balance"); @@ -22,8 +24,7 @@ public ResourceLocation getId() { @Override protected Conditions createInstance(JsonObject jsonObject, ContextAwarePredicate player, DeserializationContext context) { - String minEmc = jsonObject.has("min_emc") ? jsonObject.get("min_emc").getAsString() : "0"; - return new Conditions(player, minEmc); + return new Conditions(player, CriterionValues.minimumEmc(jsonObject)); } public void trigger(ServerPlayer player, BigInteger emc) { @@ -31,15 +32,15 @@ public void trigger(ServerPlayer player, BigInteger emc) { } public static class Conditions extends AbstractCriterionTriggerInstance { - private final String minEmc; + private final Predicate matchesMinimum; public Conditions(ContextAwarePredicate player, String minEmc) { super(ID, player); - this.minEmc = minEmc; + this.matchesMinimum = emc -> CriterionValues.meetsMinimum(emc, minEmc); } public boolean matches(BigInteger emc) { - return EmcNumber.nonNegative(emc).compareTo(EmcNumber.parse(minEmc)) >= 0; + return matchesMinimum.test(emc); } } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcOrbCriterion.java b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcOrbCriterion.java index 0490635..0f2c96a 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcOrbCriterion.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/EmcOrbCriterion.java @@ -3,8 +3,10 @@ import com.google.gson.JsonObject; import java.math.BigInteger; +import java.util.function.BiPredicate; import net.exohayvan.dissolver_enhanced.common.values.EmcNumber; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; import net.minecraft.advancements.critereon.DeserializationContext; @@ -20,30 +22,31 @@ public ResourceLocation getId() { return ID; } + public void trigger(ServerPlayer player, BigInteger emc, String action) { + trigger(player, conditions -> conditions.matches(emc, action)); + } + @Override protected Conditions createInstance(JsonObject jsonObject, ContextAwarePredicate player, DeserializationContext context) { - String minEmc = jsonObject.has("min_emc") ? jsonObject.get("min_emc").getAsString() : "0"; - String action = jsonObject.has("action") ? jsonObject.get("action").getAsString() : null; - return new Conditions(player, minEmc, action); + return readConditions(jsonObject, player); } - public void trigger(ServerPlayer player, BigInteger emc, String action) { - trigger(player, conditions -> conditions.matches(emc, action)); + private static Conditions readConditions(JsonObject jsonObject, ContextAwarePredicate player) { + String action = jsonObject.has("action") ? jsonObject.get("action").getAsString() : null; + return new Conditions(player, CriterionValues.minimumEmc(jsonObject), action); } public static class Conditions extends AbstractCriterionTriggerInstance { - private final String minEmc; - private final String action; + private final BiPredicate matcher; public Conditions(ContextAwarePredicate player, String minEmc, String action) { super(ID, player); - this.minEmc = minEmc; - this.action = action; + this.matcher = (emc, currentAction) -> CriterionValues.meetsMinimum(emc, minEmc) && + (action == null || action.equals(currentAction)); } public boolean matches(BigInteger emc, String currentAction) { - if (EmcNumber.nonNegative(emc).compareTo(EmcNumber.parse(minEmc)) < 0) return false; - return action == null || action.equals(currentAction); + return matcher.test(emc, currentAction); } } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedCountCriterion.java b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedCountCriterion.java index 2f6c5b5..50a99fa 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedCountCriterion.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedCountCriterion.java @@ -2,6 +2,8 @@ import com.google.gson.JsonObject; +import java.util.function.IntPredicate; + import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; import net.minecraft.advancements.critereon.DeserializationContext; @@ -17,26 +19,30 @@ public ResourceLocation getId() { return ID; } + public void trigger(ServerPlayer player, int learnedItems) { + trigger(player, conditions -> conditions.matches(learnedItems)); + } + @Override protected Conditions createInstance(JsonObject jsonObject, ContextAwarePredicate player, DeserializationContext context) { - int minItems = jsonObject.has("min_items") ? jsonObject.get("min_items").getAsInt() : 0; - return new Conditions(player, minItems); + return readConditions(jsonObject, player); } - public void trigger(ServerPlayer player, int learnedItems) { - trigger(player, conditions -> conditions.matches(learnedItems)); + private static Conditions readConditions(JsonObject jsonObject, ContextAwarePredicate player) { + int minItems = jsonObject.has("min_items") ? jsonObject.get("min_items").getAsInt() : 0; + return new Conditions(player, minItems); } public static class Conditions extends AbstractCriterionTriggerInstance { - private final int minItems; + private final IntPredicate matcher; public Conditions(ContextAwarePredicate player, int minItems) { super(ID, player); - this.minItems = minItems; + this.matcher = learnedItems -> learnedItems >= minItems; } public boolean matches(int learnedItems) { - return learnedItems >= minItems; + return matcher.test(learnedItems); } } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedItemCriterion.java b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedItemCriterion.java index 5800d59..4e0d13f 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedItemCriterion.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/advancement/LearnedItemCriterion.java @@ -2,6 +2,7 @@ import com.google.gson.JsonObject; import net.exohayvan.dissolver_enhanced.helpers.EMCKey; +import net.exohayvan.dissolver_enhanced.advancement.compat.CriterionValues; import net.minecraft.advancements.critereon.AbstractCriterionTriggerInstance; import net.minecraft.advancements.critereon.ContextAwarePredicate; import net.minecraft.advancements.critereon.DeserializationContext; @@ -19,10 +20,7 @@ public ResourceLocation getId() { @Override protected Conditions createInstance(JsonObject jsonObject, ContextAwarePredicate player, DeserializationContext context) { - String item = jsonObject.has("item") ? jsonObject.get("item").getAsString() : null; - boolean hasExternalNamespace = jsonObject.has("external_namespace"); - Boolean externalNamespace = hasExternalNamespace ? jsonObject.get("external_namespace").getAsBoolean() : null; - return new Conditions(player, item, externalNamespace); + return new Conditions(player, CriterionValues.optionalItem(jsonObject), CriterionValues.optionalExternalNamespace(jsonObject)); } public void trigger(ServerPlayer player, String itemId) { @@ -41,20 +39,7 @@ public Conditions(ContextAwarePredicate player, String item, Boolean externalNam } public boolean matches(String itemId) { - if (item != null && !item.equals(itemId)) { - return false; - } - - if (externalNamespace != null && externalNamespace != isExternalNamespace(itemId)) { - return false; - } - - return true; - } - - private static boolean isExternalNamespace(String itemId) { - int namespaceEnd = itemId.indexOf(":"); - return namespaceEnd > 0 && !"minecraft".equals(itemId.substring(0, namespaceEnd)); + return CriterionValues.matchesLearnedItem(item, externalNamespace, itemId); } } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java b/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java index 7800819..6bcf9f5 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/analytics/ModAnalytics.java @@ -144,16 +144,7 @@ public static void captureDissolverItemLearned( BigInteger totalValue, boolean creativeItem ) { - if (!enabled()) { - return; - } - - Map properties = itemProperties(namespace, item, itemId); - properties.put("stack_count", stackCount); - properties.put("single_value", singleValue.toString()); - properties.put("total_value", totalValue.toString()); - properties.put("creative_item", creativeItem); - client.capture(ITEM_LEARNED_EVENT, distinctId, properties); + captureDissolverItem(ITEM_LEARNED_EVENT, namespace, item, itemId, stackCount, singleValue, totalValue, creativeItem); } public static void captureDissolverItemDissolved( @@ -165,16 +156,7 @@ public static void captureDissolverItemDissolved( BigInteger totalValue, boolean creativeItem ) { - if (!enabled()) { - return; - } - - Map properties = itemProperties(namespace, item, itemId); - properties.put("stack_count", stackCount); - properties.put("single_value", singleValue.toString()); - properties.put("total_value", totalValue.toString()); - properties.put("creative_item", creativeItem); - client.capture(ITEM_DISSOLVED_EVENT, distinctId, properties); + captureDissolverItem(ITEM_DISSOLVED_EVENT, namespace, item, itemId, stackCount, singleValue, totalValue, creativeItem); } public static void captureDissolverItemExtracted( @@ -186,16 +168,20 @@ public static void captureDissolverItemExtracted( BigInteger totalValue, boolean creativeItem ) { - if (!enabled()) { - return; - } + captureDissolverItem(ITEM_EXTRACTED_EVENT, namespace, item, itemId, stackCount, singleValue, totalValue, creativeItem); + } + + private static void captureDissolverItem(String event, String namespace, String item, String itemId, + int stackCount, BigInteger singleValue, BigInteger totalValue, + boolean creativeItem) { + if (!enabled()) return; Map properties = itemProperties(namespace, item, itemId); properties.put("stack_count", stackCount); properties.put("single_value", singleValue.toString()); properties.put("total_value", totalValue.toString()); properties.put("creative_item", creativeItem); - client.capture(ITEM_EXTRACTED_EVENT, distinctId, properties); + client.capture(event, distinctId, properties); } public static void captureDissolverItemRejected(String namespace, String item, String itemId, String reason) { diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/CondenserBlock.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/CondenserBlock.java index 0ab560f..fda001d 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/block/CondenserBlock.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/CondenserBlock.java @@ -3,22 +3,19 @@ import org.jetbrains.annotations.Nullable; -import net.exohayvan.dissolver_enhanced.analytics.ModAnalytics; import net.exohayvan.dissolver_enhanced.block.entity.CondenserBlockEntity; import net.exohayvan.dissolver_enhanced.block.entity.ModBlockEntities; -import net.minecraft.world.level.block.RenderShape; + import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.MenuProvider; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; -public class CondenserBlock extends BaseEntityBlock { +public class CondenserBlock extends InteractiveMachineBlock { public CondenserBlock(Properties settings) { super(settings); } @@ -28,22 +25,13 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new CondenserBlockEntity(pos, state); } - @Override - public RenderShape getRenderShape(BlockState state) { - return RenderShape.MODEL; + protected MenuProvider menuProvider(BlockEntity blockEntity) { + return blockEntity instanceof CondenserBlockEntity entity ? entity : null; } @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, net.minecraft.world.InteractionHand hand, BlockHitResult hit) { - if (world.isClientSide()) return InteractionResult.SUCCESS; - - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof CondenserBlockEntity condenserBlockEntity) { - ModAnalytics.captureBlockUse("condenser_block"); - player.openMenu(condenserBlockEntity); - } - - return InteractionResult.CONSUME; + protected String analyticsId() { + return "condenser_block"; } @Nullable diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/InteractiveMachineBlock.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/InteractiveMachineBlock.java new file mode 100644 index 0000000..ef7e239 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/InteractiveMachineBlock.java @@ -0,0 +1,41 @@ +package net.exohayvan.dissolver_enhanced.block; + +import net.exohayvan.dissolver_enhanced.analytics.ModAnalytics; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +abstract class InteractiveMachineBlock extends BaseEntityBlock { + protected InteractiveMachineBlock(Properties settings) { + super(settings); + } + + @Override + public RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } + + protected abstract MenuProvider menuProvider(BlockEntity blockEntity); + + protected abstract String analyticsId(); + + @Override + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (world.isClientSide()) return InteractionResult.SUCCESS; + + MenuProvider provider = menuProvider(world.getBlockEntity(pos)); + if (provider != null) { + ModAnalytics.captureBlockUse(analyticsId()); + player.openMenu(provider); + } + return InteractionResult.CONSUME; + } +} \ No newline at end of file diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/MaterializerBlock.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/MaterializerBlock.java index 18da3d3..9fa123d 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/block/MaterializerBlock.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/MaterializerBlock.java @@ -5,24 +5,23 @@ import org.jetbrains.annotations.Nullable; -import net.exohayvan.dissolver_enhanced.analytics.ModAnalytics; + import net.exohayvan.dissolver_enhanced.block.entity.MaterializerBlockEntity; import net.exohayvan.dissolver_enhanced.block.entity.ModBlockEntities; import net.exohayvan.dissolver_enhanced.item.EMCOrbItem; -import net.minecraft.world.level.block.RenderShape; + import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; import net.minecraft.world.Containers; -import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; -public class MaterializerBlock extends BaseEntityBlock { +public class MaterializerBlock extends InteractiveMachineBlock { public MaterializerBlock(Properties settings) { super(settings); } @@ -32,22 +31,13 @@ public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new MaterializerBlockEntity(pos, state); } - @Override - public RenderShape getRenderShape(BlockState state) { - return RenderShape.MODEL; + protected MenuProvider menuProvider(BlockEntity blockEntity) { + return blockEntity instanceof MaterializerBlockEntity entity ? entity : null; } @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, net.minecraft.world.InteractionHand hand, BlockHitResult hit) { - if (world.isClientSide()) return InteractionResult.SUCCESS; - - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof MaterializerBlockEntity materializerBlockEntity) { - ModAnalytics.captureBlockUse("materializer_block"); - player.openMenu(materializerBlockEntity); - } - - return InteractionResult.CONSUME; + protected String analyticsId() { + return "materializer_block"; } @Override diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CondenserBlockEntity.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CondenserBlockEntity.java index c761cb5..1b86f2f 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CondenserBlockEntity.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CondenserBlockEntity.java @@ -5,6 +5,7 @@ import net.exohayvan.dissolver_enhanced.data.EMCValues; import net.exohayvan.dissolver_enhanced.helpers.EMCKey; import net.exohayvan.dissolver_enhanced.helpers.ItemHelper; +import net.exohayvan.dissolver_enhanced.helpers.MachineRate; import net.exohayvan.dissolver_enhanced.item.EMCOrbItem; import net.exohayvan.dissolver_enhanced.item.EmcCoreItem; import net.exohayvan.dissolver_enhanced.screen.CondenserScreenHandler; @@ -135,19 +136,13 @@ private BigInteger condenseValueBig(ItemStack stack) { private int getConversionTime() { int emc = condenseValue(this.stacks.get(INPUT_SLOT)); - return ticksForRate(emc, getEmcPerSecond()); + return MachineRate.ticksForRate(emc, getEmcPerSecond(), CONVERSION_TICKS_PER_EMC); } private int getEmcPerSecond() { return EmcCoreItem.getEmcPerSecond(this.stacks.get(CORE_SLOT)); } - private int ticksForRate(int emc, int emcPerSecond) { - if (emc <= 0) return CONVERSION_TICKS_PER_EMC; - - long ticks = ((long) emc * MachineTiming.TICKS_PER_SECOND + Math.max(1, emcPerSecond) - 1L) / Math.max(1, emcPerSecond); - return (int) Math.max(1L, Math.min(Integer.MAX_VALUE, ticks)); - } private void resetProgress() { this.progress = 0; @@ -195,9 +190,7 @@ protected AbstractContainerMenu createScreenHandler(int syncId, Inventory player @Override public int[] getSlotsForFace(Direction side) { - if (side == Direction.UP) return TOP_SLOTS; - if (side == Direction.DOWN) return BOTTOM_SLOTS; - return SIDE_SLOTS; + return slotsForFace(side, TOP_SLOTS, BOTTOM_SLOTS, SIDE_SLOTS); } @Override diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CustomBlockEntity.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CustomBlockEntity.java index 3e3ca9e..c5ad341 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CustomBlockEntity.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/CustomBlockEntity.java @@ -152,4 +152,10 @@ public void removeComponentsFromTag(CompoundTag nbt) { public int[] getSlotsForFace(Direction side) { return new int[0]; } + + protected static int[] slotsForFace(Direction side, int[] top, int[] bottom, int[] sides) { + if (side == Direction.UP) return top; + if (side == Direction.DOWN) return bottom; + return sides; + } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/MaterializerBlockEntity.java b/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/MaterializerBlockEntity.java index 473e30e..4b59d22 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/MaterializerBlockEntity.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/block/entity/MaterializerBlockEntity.java @@ -5,6 +5,7 @@ import net.exohayvan.dissolver_enhanced.data.EMCValues; import net.exohayvan.dissolver_enhanced.helpers.EMCKey; import net.exohayvan.dissolver_enhanced.helpers.ItemHelper; +import net.exohayvan.dissolver_enhanced.helpers.MachineRate; import net.exohayvan.dissolver_enhanced.item.EMCOrbItem; import net.exohayvan.dissolver_enhanced.item.EmcCoreItem; import net.exohayvan.dissolver_enhanced.screen.MaterializerScreenHandler; @@ -153,19 +154,13 @@ private int getInputValue() { } private int getConversionTime() { - return ticksForRate(getInputValue(), getEmcPerSecond()); + return MachineRate.ticksForRate(getInputValue(), getEmcPerSecond(), CONVERSION_TICKS_PER_EMC); } private int getEmcPerSecond() { return EmcCoreItem.getEmcPerSecond(this.stacks.get(CORE_SLOT)); } - private int ticksForRate(int emc, int emcPerSecond) { - if (emc <= 0) return CONVERSION_TICKS_PER_EMC; - - long ticks = ((long) emc * MachineTiming.TICKS_PER_SECOND + Math.max(1, emcPerSecond) - 1L) / Math.max(1, emcPerSecond); - return (int) Math.max(1L, Math.min(Integer.MAX_VALUE, ticks)); - } private int stackValue(ItemStack stack, boolean allowOrb) { if (stack.isEmpty()) return 0; @@ -238,9 +233,7 @@ protected AbstractContainerMenu createScreenHandler(int syncId, Inventory player @Override public int[] getSlotsForFace(Direction side) { - if (side == Direction.UP) return TOP_SLOTS; - if (side == Direction.DOWN) return BOTTOM_SLOTS; - return SIDE_SLOTS; + return slotsForFace(side, TOP_SLOTS, BOTTOM_SLOTS, SIDE_SLOTS); } @Override diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/command/DebugItem.java b/src/main/java/net/exohayvan/dissolver_enhanced/command/DebugItem.java index 6d8097e..28dc405 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/command/DebugItem.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/command/DebugItem.java @@ -37,26 +37,12 @@ public static int summary(CommandContext context, String com .toList(); int totalItems = items.size(); - int itemsWithEMC = 0; - int itemsWithoutEMC = 0; + int itemsWithEMC = (int) items.stream().filter(DebugItem::hasEmc).count(); + int itemsWithoutEMC = totalItems - itemsWithEMC; HashMap missingTagCounts = new HashMap<>(); for (Item item : items) { - String itemId = ItemHelper.getId(item); - int emc = EMCValues.get(itemId); - - if (emc > 0) { - itemsWithEMC++; - continue; - } - - itemsWithoutEMC++; - item.getDefaultInstance() - .getTags() - .map(TagKey::location) - .map(ResourceLocation::toString) - .filter(tagId -> !EMCValues.EMC_TAG_VALUES.containsKey(tagId)) - .forEach(tagId -> missingTagCounts.put(tagId, missingTagCounts.getOrDefault(tagId, 0) + 1)); + if (!hasEmc(item)) countMissingTags(item, missingTagCounts); } List> tagsWithoutValues = missingTagCounts @@ -157,12 +143,7 @@ private static int namespaceSummary(CommandContext context, missingItems.add(itemId); missingItemsByNamespace.put(itemNamespace, missingItems); - item.getDefaultInstance() - .getTags() - .map(TagKey::location) - .map(ResourceLocation::toString) - .filter(tagId -> !EMCValues.EMC_TAG_VALUES.containsKey(tagId)) - .forEach(tagId -> missingTagCounts.put(tagId, missingTagCounts.getOrDefault(tagId, 0) + 1)); + countMissingTags(item, missingTagCounts); } List> namespacesWithMissingItems = missingNamespaceCounts @@ -234,17 +215,8 @@ private static int namespaceSummary(CommandContext context, } public static int item(CommandContext context, String command) { - ServerPlayer player = context.getSource().getPlayer(); - if (player == null) { - ModCommands.feedback(context, "This command must be run by a player."); - return 0; - } - - ItemStack stack = player.getMainHandItem(); - if (stack.isEmpty()) { - ModCommands.feedback(context, "Hold an item to debug it."); - return 0; - } + ItemStack stack = heldItem(context, "Hold an item to debug it."); + if (stack == null) return 0; String itemId = EMCKey.fromStack(stack); String emcKey = EMCKey.fromStack(stack); @@ -284,17 +256,8 @@ public static int item(CommandContext context, String comman } public static int recipe(CommandContext context, String command) { - ServerPlayer player = context.getSource().getPlayer(); - if (player == null) { - ModCommands.feedback(context, "This command must be run by a player."); - return 0; - } - - ItemStack stack = player.getMainHandItem(); - if (stack.isEmpty()) { - ModCommands.feedback(context, "Hold an item to debug its recipes."); - return 0; - } + ItemStack stack = heldItem(context, "Hold an item to debug its recipes."); + if (stack == null) return 0; String itemId = EMCKey.fromStack(stack); List recipeLines = EMCValues.getRecipeDebugLines(itemId); @@ -342,6 +305,32 @@ private static String formatPercent(double value) { return String.format("%.2f%%", value); } + private static void countMissingTags(Item item, Map counts) { + item.getDefaultInstance().getTags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .filter(tagId -> !EMCValues.EMC_TAG_VALUES.containsKey(tagId)) + .forEach(tagId -> counts.merge(tagId, 1, Integer::sum)); + } + + private static boolean hasEmc(Item item) { + return EMCValues.get(ItemHelper.getId(item)) > 0; + } + + private static ItemStack heldItem(CommandContext context, String emptyMessage) { + ServerPlayer player = context.getSource().getPlayer(); + if (player == null) { + ModCommands.feedback(context, "This command must be run by a player."); + return null; + } + ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty()) { + ModCommands.feedback(context, emptyMessage); + return null; + } + return stack; + } + private static Path writeReport( List lines, List> tagsWithoutValues, @@ -352,28 +341,8 @@ private static Path writeReport( String fileName = "dissolver-debug-report-" + LocalDateTime.now().format(REPORT_DATE_FORMAT); Path reportPath = reportDir.resolve(fileName); List reportLines = new ArrayList<>(lines); - reportLines.add(""); - reportLines.add("All tags without values:"); - - if (tagsWithoutValues.isEmpty()) { - reportLines.add("None"); - } else { - for (Map.Entry tag : tagsWithoutValues) { - double percentOfMissingItems = itemsWithoutEMC == 0 ? 0 : (tag.getValue() * 100.0) / itemsWithoutEMC; - reportLines.add("#" + tag.getKey() + " - " + formatPercent(percentOfMissingItems)); - } - } - - reportLines.add(""); - reportLines.add("All missing recipe ingredients:"); - - if (recipeUnlockItems.isEmpty()) { - reportLines.add("None"); - } else { - for (Map.Entry item : recipeUnlockItems) { - reportLines.add(formatRecipeUnlockItem(item, itemsWithoutEMC)); - } - } + appendTagReport(reportLines, tagsWithoutValues, itemsWithoutEMC); + appendRecipeReport(reportLines, recipeUnlockItems, itemsWithoutEMC); try { Files.createDirectories(reportDir); @@ -434,26 +403,8 @@ private static Path writeNamespaceReport( } if (namespace != null) { - reportLines.add(""); - reportLines.add("All tags without values:"); - if (tagsWithoutValues.isEmpty()) { - reportLines.add("None"); - } else { - for (Map.Entry tag : tagsWithoutValues) { - double percentOfMissingItems = itemsWithoutEMC == 0 ? 0 : (tag.getValue() * 100.0) / itemsWithoutEMC; - reportLines.add("#" + tag.getKey() + " - " + formatPercent(percentOfMissingItems)); - } - } - - reportLines.add(""); - reportLines.add("All missing recipe ingredients:"); - if (recipeUnlockItems.isEmpty()) { - reportLines.add("None"); - } else { - for (Map.Entry item : recipeUnlockItems) { - reportLines.add(formatRecipeUnlockItem(item, itemsWithoutEMC)); - } - } + appendTagReport(reportLines, tagsWithoutValues, itemsWithoutEMC); + appendRecipeReport(reportLines, recipeUnlockItems, itemsWithoutEMC); } try { @@ -489,6 +440,35 @@ private static long countRecipes(List recipeLines) { .count(); } + private static void appendTagReport(List lines, List> tags, int missingItems) { + lines.add(""); + lines.add("All tags without values:"); + if (tags.isEmpty()) { + lines.add("None"); + return; + } + for (Map.Entry tag : tags) { + double percent = missingItems == 0 ? 0 : (tag.getValue() * 100.0) / missingItems; + lines.add("#" + tag.getKey() + " - " + formatPercent(percent)); + } + } + + private static void appendRecipeReport( + List lines, + List> recipes, + int missingItems + ) { + lines.add(""); + lines.add("All missing recipe ingredients:"); + if (recipes.isEmpty()) { + lines.add("None"); + return; + } + for (Map.Entry item : recipes) { + lines.add(formatRecipeUnlockItem(item, missingItems)); + } + } + private static String formatRecipeUnlockItem(Map.Entry item, int itemsWithoutEMC) { double percentOfMissingItems = itemsWithoutEMC == 0 ? 0 : (item.getValue().count() * 100.0) / itemsWithoutEMC; return item.getKey() + " - " + formatPercent(percentOfMissingItems) + " (" + item.getValue().count() + diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/command/LearnItems.java b/src/main/java/net/exohayvan/dissolver_enhanced/command/LearnItems.java index 6b2ba5f..a8f87be 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/command/LearnItems.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/command/LearnItems.java @@ -54,13 +54,7 @@ public static int forgetPlayer(CommandContext context, Strin } public static int add(CommandContext context, String command) { - Player player = context.getSource().getPlayer(); - boolean learned = EMCHelper.learnItem(player, getItemId(context)); - - if (learned) ModCommands.feedback(context, Component.translatable("command.feedback.memory.add", getItemName(context)).getString()); - else ModCommands.feedback(context, Component.translatable("command.feedback.memory.add.fail").getString()); - - return 1; + return changeItem(context, context.getSource().getPlayer(), true); } public static int addPlayer(CommandContext context, String command, Player player) { @@ -69,22 +63,11 @@ public static int addPlayer(CommandContext context, String c return 1; } - boolean learned = EMCHelper.learnItem(player, getItemId(context)); - - if (learned) ModCommands.feedback(context, Component.translatable("command.feedback.memory.add", getItemName(context)).getString()); - else ModCommands.feedback(context, Component.translatable("command.feedback.memory.add.fail").getString()); - - return 1; + return changeItem(context, player, true); } public static int remove(CommandContext context, String command) { - Player player = context.getSource().getPlayer(); - boolean removed = EMCHelper.forgetItem(player, getItemId(context)); - - if (removed) ModCommands.feedback(context, Component.translatable("command.feedback.memory.remove", getItemName(context)).getString()); - else ModCommands.feedback(context, Component.translatable("command.feedback.memory.remove.fail").getString()); - - return 1; + return changeItem(context, context.getSource().getPlayer(), false); } public static int removePlayer(CommandContext context, String command, Player player) { @@ -93,16 +76,23 @@ public static int removePlayer(CommandContext context, Strin return 1; } - boolean removed = EMCHelper.forgetItem(player, getItemId(context)); - - if (removed) ModCommands.feedback(context, Component.translatable("command.feedback.memory.remove", getItemName(context)).getString()); - else ModCommands.feedback(context, Component.translatable("command.feedback.memory.remove.fail").getString()); - - return 1; + return changeItem(context, player, false); } // HELPERS + private static int changeItem(CommandContext context, Player player, boolean add) { + boolean changed = add + ? EMCHelper.learnItem(player, getItemId(context)) + : EMCHelper.forgetItem(player, getItemId(context)); + String action = add ? "add" : "remove"; + Component feedback = changed + ? Component.translatable("command.feedback.memory." + action, getItemName(context)) + : Component.translatable("command.feedback.memory." + action + ".fail"); + ModCommands.feedback(context, feedback.getString()); + return 1; + } + private static String getItemId(CommandContext context) { final Item item = ItemArgument.getItem(context, "item").getItem(); return ItemHelper.getId(item); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeItemTooltipEvent.java b/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeItemTooltipEvent.java index ae39f24..e4545a3 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeItemTooltipEvent.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/event/ForgeItemTooltipEvent.java @@ -6,13 +6,13 @@ import net.exohayvan.dissolver_enhanced.helpers.EMCKey; import net.exohayvan.dissolver_enhanced.helpers.EMCHelper; import net.exohayvan.dissolver_enhanced.helpers.ItemHelper; +import net.exohayvan.dissolver_enhanced.helpers.TooltipHelper; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.event.entity.player.ItemTooltipEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.ModList; + import net.minecraftforge.fml.common.Mod; @Mod.EventBusSubscriber(modid = DissolverEnhanced.MOD_ID, value = Dist.CLIENT) @@ -28,25 +28,6 @@ public static void onItemTooltip(ItemTooltipEvent event) { if (formattedText.getString().isEmpty()) return; List tooltip = event.getToolTip(); - tooltip.add(getInsertIndexAfterModName(stack, tooltip), formattedText); - } - - private static int getInsertIndexAfterModName(ItemStack stack, List tooltip) { - ResourceLocation id = ResourceLocation.tryParse(EMCKey.baseItemId(EMCKey.fromStack(stack))); - if (id == null) return tooltip.size(); - - String modName = ModList.get() - .getModContainerById(id.getNamespace()) - .map(container -> container.getModInfo().getDisplayName()) - .orElse(null); - if (modName == null) return tooltip.size(); - - for (int i = tooltip.size() - 1; i >= 0; i--) { - if (tooltip.get(i).getString().equals(modName)) { - return i + 1; - } - } - - return tooltip.size(); + tooltip.add(TooltipHelper.insertIndexAfterModName(stack, tooltip), formattedText); } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/EMCHelper.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/EMCHelper.java index 76bf61b..2441358 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/EMCHelper.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/EMCHelper.java @@ -157,9 +157,7 @@ public static boolean canAddItem(ItemStack itemStack, Player player) { BigInteger emcValue = EMCValues.getBig(itemId); if (!checkValidEMC(emcValue, itemId, Action.ADD)) { - captureDissolverItemRejected(itemId, rejectionReason(itemId)); - reportMissingItemValue(player, itemStack, itemId); - return false; + return rejectItem(itemId, () -> reportMissingItemValue(player, itemStack, itemId)); } return true; @@ -173,8 +171,7 @@ public static boolean addItem(ItemStack itemStack, Level world) { BigInteger emcValue = EMCValues.getBig(itemId); if (!checkValidEMC(emcValue, itemId, Action.ADD)) { - captureDissolverItemRejected(itemId, rejectionReason(itemId)); - return false; + return rejectItem(itemId, null); } int itemCount = itemStack.getCount(); @@ -193,9 +190,7 @@ public static boolean addItem(ItemStack itemStack, Player player, DissolverScree BigInteger emcValue = EMCValues.getBig(itemId); if (!checkValidEMC(emcValue, itemId, Action.ADD)) { - captureDissolverItemRejected(itemId, rejectionReason(itemId)); - reportMissingItemValue(player, itemStack, itemId); - return false; + return rejectItem(itemId, () -> reportMissingItemValue(player, itemStack, itemId)); } // calculated new EMC (from DissolverInventoryInput) @@ -455,7 +450,7 @@ private static void captureDissolverItemRejected(String itemId, String reason) { ); } - private static boolean isCreativeItem(String itemId) { + public static boolean isCreativeItem(String itemId) { String baseItemId = EMCKey.baseItemId(itemId); return baseItemId.contains("spawn_egg") || baseItemId.contains("command_block") @@ -470,7 +465,13 @@ private static boolean isCreativeItem(String itemId) { || baseItemId.contains("reinforced_deepslate"); } - private static String rejectionReason(String itemId) { + private static boolean rejectItem(String itemId, Runnable missingValueReporter) { + captureDissolverItemRejected(itemId, rejectionReason(itemId)); + if (missingValueReporter != null) missingValueReporter.run(); + return false; + } + + public static String rejectionReason(String itemId) { if (isCreativeItem(itemId) && !ModConfig.CREATIVE_ITEMS) { return "creative_disabled"; } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/MachineRate.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/MachineRate.java new file mode 100644 index 0000000..32c9882 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/MachineRate.java @@ -0,0 +1,16 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import net.exohayvan.dissolver_enhanced.common.machine.MachineTiming; + +public final class MachineRate { + private MachineRate() { + } + + public static int ticksForRate(int emc, int emcPerSecond, int fallback) { + if (emc <= 0) return fallback; + + int safeRate = Math.max(1, emcPerSecond); + long ticks = ((long) emc * MachineTiming.TICKS_PER_SECOND + safeRate - 1L) / safeRate; + return (int) Math.max(1L, Math.min(Integer.MAX_VALUE, ticks)); + } +} \ No newline at end of file diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/ReflectionCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/ReflectionCompat.java new file mode 100644 index 0000000..e1f1168 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/ReflectionCompat.java @@ -0,0 +1,58 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.function.Function; + +import net.minecraft.resources.ResourceLocation; + +public final class ReflectionCompat { + private ReflectionCompat() { + } + + public static Method findCompatibleMethod(Class type, String name, Object... arguments) { + for (Method method : type.getMethods()) { + if (!method.getName().equals(name) || method.getParameterCount() != arguments.length) continue; + + Class[] parameters = method.getParameterTypes(); + boolean compatible = true; + for (int index = 0; index < parameters.length; index++) { + if (!isCompatible(parameters[index], arguments[index])) { + compatible = false; + break; + } + } + if (compatible) return method; + } + return null; + } + + public static ResourceLocation resourceLocation( + String namespace, + String path, + Function staticFactoryInvoker + ) { + try { + Constructor constructor = ResourceLocation.class.getDeclaredConstructor(String.class, String.class); + constructor.setAccessible(true); + return constructor.newInstance(namespace, path); + } catch (ReflectiveOperationException ignored) { + for (Method method : ResourceLocation.class.getDeclaredMethods()) { + Class[] parameters = method.getParameterTypes(); + if (Modifier.isStatic(method.getModifiers()) && method.getReturnType() == ResourceLocation.class && + parameters.length == 2 && parameters[0] == String.class && parameters[1] == String.class) { + return staticFactoryInvoker.apply(method); + } + } + throw new IllegalStateException("Could not construct a ResourceLocation."); + } + } + + private static boolean isCompatible(Class parameter, Object argument) { + if (argument == null) return !parameter.isPrimitive(); + if (!parameter.isPrimitive()) return parameter.isInstance(argument); + return (parameter == int.class && argument instanceof Integer) || + (parameter == boolean.class && argument instanceof Boolean); + } +} \ No newline at end of file diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java index 942a0aa..619aba6 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/RegistryKeyCompat.java @@ -1,6 +1,5 @@ package net.exohayvan.dissolver_enhanced.helpers; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -27,20 +26,11 @@ public static ResourceKey> creativeModeTab() { } private static ResourceLocation resourceLocation(String namespace, String path) { - try { - Constructor constructor = ResourceLocation.class.getDeclaredConstructor(String.class, String.class); - constructor.setAccessible(true); - return constructor.newInstance(namespace, path); - } catch (ReflectiveOperationException ignored) { - for (Method method : ResourceLocation.class.getDeclaredMethods()) { - if (Modifier.isStatic(method.getModifiers()) && method.getReturnType() == ResourceLocation.class && - method.getParameterCount() == 2 && method.getParameterTypes()[0] == String.class && - method.getParameterTypes()[1] == String.class) { - return (ResourceLocation) invoke(method, null, namespace, path); - } - } - throw new IllegalStateException("Could not construct a ResourceLocation."); - } + return ReflectionCompat.resourceLocation( + namespace, + path, + method -> (ResourceLocation) invoke(method, null, namespace, path) + ); } private static Object invoke(Method method, Object target, Object... arguments) { diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/helpers/TooltipHelper.java b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/TooltipHelper.java new file mode 100644 index 0000000..ed39ea3 --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/helpers/TooltipHelper.java @@ -0,0 +1,31 @@ +package net.exohayvan.dissolver_enhanced.helpers; + +import java.util.List; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fml.ModList; + +public final class TooltipHelper { + private TooltipHelper() { + } + + public static int insertIndexAfterModName(ItemStack stack, List tooltip) { + ResourceLocation id = ResourceLocation.tryParse(EMCKey.baseItemId(EMCKey.fromStack(stack))); + if (id == null) return tooltip.size(); + + String modName = ModList.get() + .getModContainerById(id.getNamespace()) + .map(container -> container.getModInfo().getDisplayName()) + .orElse(null); + if (modName == null) return tooltip.size(); + + for (int i = tooltip.size() - 1; i >= 0; i--) { + if (tooltip.get(i).getString().equals(modName)) { + return i + 1; + } + } + return tooltip.size(); + } +} \ No newline at end of file diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventory.java b/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventory.java index c469684..d374f11 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventory.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventory.java @@ -10,10 +10,10 @@ import net.minecraft.world.item.ItemStack; public class DissolverInventory implements Container { - private final NonNullList stacks; + protected final NonNullList stacks; private final int width; private final int height; - private final AbstractContainerMenu handler; + protected final AbstractContainerMenu handler; public DissolverInventory(AbstractContainerMenu handler, int width, int height) { this(handler, width, height, NonNullList.withSize(width * height, ItemStack.EMPTY)); diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventoryInput.java b/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventoryInput.java index 1b5b05e..3950c2b 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventoryInput.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/inventory/DissolverInventoryInput.java @@ -1,8 +1,7 @@ package net.exohayvan.dissolver_enhanced.inventory; import java.math.BigInteger; -import java.util.Iterator; -import java.util.List; + import net.exohayvan.dissolver_enhanced.advancement.ModCriteria; import net.exohayvan.dissolver_enhanced.analytics.ModAnalytics; import net.exohayvan.dissolver_enhanced.config.ModConfig; @@ -11,29 +10,22 @@ import net.exohayvan.dissolver_enhanced.helpers.EMCKey; import net.exohayvan.dissolver_enhanced.item.EMCOrbItem; import net.exohayvan.dissolver_enhanced.screen.DissolverScreenHandler; -import net.minecraft.core.NonNullList; -import net.minecraft.world.Container; -import net.minecraft.world.ContainerHelper; + import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -public class DissolverInventoryInput implements Container { - private final NonNullList stacks; - private final int width; - private final int height; +public class DissolverInventoryInput extends DissolverInventory { private final DissolverScreenHandler handler; private Player player; private int SLOTS = 3; public DissolverInventoryInput(DissolverScreenHandler handler, Player player) { - this.stacks = NonNullList.withSize(SLOTS, ItemStack.EMPTY); + super(handler, 3, 1); this.handler = handler; this.player = player; - this.width = SLOTS; - this.height = 1; } public DissolverSlotInput getInputSlot() { @@ -52,42 +44,7 @@ public int slots() { return this.SLOTS; } - public int getContainerSize() { - return this.stacks.size(); - } - - public boolean isEmpty() { - Iterator var1 = this.stacks.iterator(); - - ItemStack itemStack; - do { - if (!var1.hasNext()) { - return true; - } - - itemStack = (ItemStack)var1.next(); - } while(itemStack.isEmpty()); - - return false; - } - - public ItemStack getItem(int slot) { - return slot >= this.getContainerSize() ? ItemStack.EMPTY : (ItemStack)this.stacks.get(slot); - } - - public ItemStack removeItemNoUpdate(int slot) { - return ContainerHelper.takeItem(this.stacks, slot); - } - - public ItemStack removeItem(int slot, int amount) { - ItemStack itemStack = ContainerHelper.removeItem(this.stacks, slot, amount); - if (!itemStack.isEmpty()) { - this.handler.slotsChanged(this); - } - - return itemStack; - } - + @Override public void setItem(int slot, ItemStack stack) { if (player == null) return; @@ -144,28 +101,6 @@ public void setItem(int slot, ItemStack stack) { this.handler.slotsChanged(this); } - public void setChanged() { - } - - public boolean stillValid(Player player) { - return true; - } - - public void clearContent() { - this.stacks.clear(); - } - - public int getHeight() { - return this.height; - } - - public int getWidth() { - return this.width; - } - - public List getHeldStacks() { - return List.copyOf(this.stacks); - } private static String baseItemId(String itemId) { return EMCKey.baseItemId(itemId); @@ -183,26 +118,7 @@ private static String itemName(String itemId) { return namespaceEnd == -1 ? baseItemId : baseItemId.substring(namespaceEnd + 1); } - private static boolean isCreativeItem(String itemId) { - String baseItemId = baseItemId(itemId); - return baseItemId.contains("spawn_egg") - || baseItemId.contains("command_block") - || baseItemId.contains("bedrock") - || baseItemId.contains("barrier") - || baseItemId.contains("structure_block") - || baseItemId.contains("jigsaw") - || baseItemId.contains("spawner") - || baseItemId.contains("vault") - || baseItemId.contains("end_portal_frame") - || baseItemId.contains("budding_amethyst") - || baseItemId.contains("reinforced_deepslate"); - } - private static String rejectionReason(String itemId) { - if (isCreativeItem(itemId) && !ModConfig.CREATIVE_ITEMS) { - return "creative_disabled"; - } - - return "no_emc"; + return EMCHelper.rejectionReason(itemId); } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/mixin/ItemStackTooltipMixin.java b/src/main/java/net/exohayvan/dissolver_enhanced/mixin/ItemStackTooltipMixin.java index 7046313..22da6e9 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/mixin/ItemStackTooltipMixin.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/mixin/ItemStackTooltipMixin.java @@ -9,14 +9,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; + import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.exohayvan.dissolver_enhanced.helpers.EMCHelper; import net.exohayvan.dissolver_enhanced.helpers.EMCKey; import net.exohayvan.dissolver_enhanced.helpers.ItemHelper; -import net.minecraftforge.fml.ModList; +import net.exohayvan.dissolver_enhanced.helpers.TooltipHelper; @Mixin(value = ItemStack.class, priority = 500) public class ItemStackTooltipMixin { @@ -28,25 +28,6 @@ private void addEmcTooltip(@Nullable Player player, TooltipFlag type, CallbackIn if (formattedText.getString().isEmpty()) return; List tooltip = cir.getReturnValue(); - tooltip.add(getInsertIndexAfterModName(stack, tooltip), formattedText); - } - - private int getInsertIndexAfterModName(ItemStack stack, List tooltip) { - ResourceLocation id = ResourceLocation.tryParse(EMCKey.baseItemId(EMCKey.fromStack(stack))); - if (id == null) return tooltip.size(); - - String modName = ModList.get() - .getModContainerById(id.getNamespace()) - .map(container -> container.getModInfo().getDisplayName()) - .orElse(null); - if (modName == null) return tooltip.size(); - - for (int i = tooltip.size() - 1; i >= 0; i--) { - if (tooltip.get(i).getString().equals(modName)) { - return i + 1; - } - } - - return tooltip.size(); + tooltip.add(TooltipHelper.insertIndexAfterModName(stack, tooltip), formattedText); } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/mixin/RecipeManagerMixin.java b/src/main/java/net/exohayvan/dissolver_enhanced/mixin/RecipeManagerMixin.java index 59a41c7..b06bf8b 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/mixin/RecipeManagerMixin.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/mixin/RecipeManagerMixin.java @@ -107,12 +107,8 @@ private void getRecipe(Map.Entry entry) { String itemId = ItemHelper.getId(Item.byId(rawId)); index++; - if (index == 0) { - INGREDIENTS.add(itemId); - } else if (recipe.getType() == RecipeType.STONECUTTING) { - List INGREDIENT = new ArrayList<>(); - INGREDIENT.add(itemId); - addRecipe(resultId + "__" + 1, 0, INGREDIENT, recipeId, entry.getValue()); + if (addDirectIngredient(index, itemId, recipe.getType(), INGREDIENTS, resultId, recipeId, entry.getValue())) { + continue; } else if (resultId.contains("bed") || resultId.contains("glass")) { // don't do anything // bed: bed colors @@ -136,13 +132,7 @@ private void getRecipe(Map.Entry entry) { for (String itemId : jsonIngredientIds) { index++; - if (index == 0) { - INGREDIENTS.add(itemId); - } else if (recipe.getType() == RecipeType.STONECUTTING) { - List INGREDIENT = new ArrayList<>(); - INGREDIENT.add(itemId); - addRecipe(resultId + "__" + 1, 0, INGREDIENT, recipeId, entry.getValue()); - } else { + if (!addDirectIngredient(index, itemId, recipe.getType(), INGREDIENTS, resultId, recipeId, entry.getValue())) { String rootItemId = getJsonIngredientItemIds(entry.getValue(), ingredientIndex).get(0); List REPLACE = new ArrayList<>(); if (REPLACE_INGREDIENTS.containsKey(rootItemId)) REPLACE = REPLACE_INGREDIENTS.get(rootItemId); @@ -254,6 +244,24 @@ private static List getJsonIngredientItemIds(JsonElement recipeJson, int return itemIds; } + private static boolean addDirectIngredient( + int index, + String itemId, + RecipeType recipeType, + List ingredients, + String resultId, + String recipeId, + JsonElement recipeJson + ) { + if (index == 0) { + ingredients.add(itemId); + return true; + } + if (recipeType != RecipeType.STONECUTTING) return false; + addRecipe(resultId + "__1", 0, new ArrayList<>(List.of(itemId)), recipeId, recipeJson); + return true; + } + private static boolean getJsonRecipe(Map.Entry entry) { if (!entry.getValue().isJsonObject()) return false; diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java b/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java index 7cb22fd..1be2cf5 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/packets/NetworkCompat.java @@ -13,6 +13,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; +import net.exohayvan.dissolver_enhanced.helpers.ReflectionCompat; public final class NetworkCompat { private static final String PROTOCOL_VERSION = "1"; @@ -48,7 +49,7 @@ public static void register( BiConsumer handler, String direction ) { - Method legacy = findCompatibleMethod( + Method legacy = ReflectionCompat.findCompatibleMethod( channel.getClass(), "registerMessage", id, @@ -89,7 +90,7 @@ public static void sendToPlayer(Object channel, ServerPlayer player, Object payl } public static void sendToServer(Object channel, Object payload) { - Method legacy = findCompatibleMethod(channel.getClass(), "sendToServer", payload); + Method legacy = ReflectionCompat.findCompatibleMethod(channel.getClass(), "sendToServer", payload); if (legacy != null) { invokeMethod(legacy, channel, payload); return; @@ -126,19 +127,19 @@ private static Object packetTarget(String fieldName, Object directArgument, Obje Object distributor = field.get(null); if (directArgument != null) { - Method direct = findCompatibleMethod(distributor.getClass(), "with", directArgument); + Method direct = ReflectionCompat.findCompatibleMethod(distributor.getClass(), "with", directArgument); if (direct != null) { return invokeMethod(direct, distributor, directArgument); } } if (legacyArgument != null) { - Method legacy = findCompatibleMethod(distributor.getClass(), "with", legacyArgument); + Method legacy = ReflectionCompat.findCompatibleMethod(distributor.getClass(), "with", legacyArgument); if (legacy != null) { return invokeMethod(legacy, distributor, legacyArgument); } } - Method noArg = findCompatibleMethod(distributor.getClass(), "noArg"); + Method noArg = ReflectionCompat.findCompatibleMethod(distributor.getClass(), "noArg"); if (noArg != null) { return invokeMethod(noArg, distributor); } @@ -149,12 +150,12 @@ private static Object packetTarget(String fieldName, Object directArgument, Obje } private static void invokeEitherOrder(Object target, String methodName, Object first, Object second) { - Method method = findCompatibleMethod(target.getClass(), methodName, first, second); + Method method = ReflectionCompat.findCompatibleMethod(target.getClass(), methodName, first, second); if (method != null) { invokeMethod(method, target, first, second); return; } - method = findCompatibleMethod(target.getClass(), methodName, second, first); + method = ReflectionCompat.findCompatibleMethod(target.getClass(), methodName, second, first); if (method != null) { invokeMethod(method, target, second, first); return; @@ -163,7 +164,7 @@ private static void invokeEitherOrder(Object target, String methodName, Object f } private static Object invokeStatic(Class type, String methodName, Object... arguments) { - Method method = findCompatibleMethod(type, methodName, arguments); + Method method = ReflectionCompat.findCompatibleMethod(type, methodName, arguments); if (method == null || !Modifier.isStatic(method.getModifiers())) { throw new IllegalStateException(type.getName() + " has no compatible static " + methodName + " method."); } @@ -171,7 +172,7 @@ private static Object invokeStatic(Class type, String methodName, Object... a } private static Object invoke(Object target, String methodName, Object... arguments) { - Method method = findCompatibleMethod(target.getClass(), methodName, arguments); + Method method = ReflectionCompat.findCompatibleMethod(target.getClass(), methodName, arguments); if (method == null) { throw new IllegalStateException(target.getClass().getName() + " has no compatible " + methodName + " method."); } @@ -187,34 +188,4 @@ private static Object invokeMethod(Method method, Object target, Object... argum } } - private static Method findCompatibleMethod(Class type, String methodName, Object... arguments) { - for (Method method : type.getMethods()) { - if (!method.getName().equals(methodName) || method.getParameterCount() != arguments.length) { - continue; - } - Class[] parameters = method.getParameterTypes(); - boolean compatible = true; - for (int index = 0; index < parameters.length; index++) { - if (!isCompatible(parameters[index], arguments[index])) { - compatible = false; - break; - } - } - if (compatible) { - return method; - } - } - return null; - } - - private static boolean isCompatible(Class parameter, Object argument) { - if (argument == null) { - return !parameter.isPrimitive(); - } - if (!parameter.isPrimitive()) { - return parameter.isInstance(argument); - } - return (parameter == int.class && argument instanceof Integer) || - (parameter == boolean.class && argument instanceof Boolean); - } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreenHandler.java b/src/main/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreenHandler.java index ebe78f5..1e76ed3 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreenHandler.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/screen/CondenserScreenHandler.java @@ -15,32 +15,21 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.inventory.SimpleContainerData; import net.minecraft.world.inventory.ContainerData; -import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.inventory.Slot; import java.math.BigInteger; -public class CondenserScreenHandler extends AbstractContainerMenu { +public class CondenserScreenHandler extends MachineScreenHandler { public static final int INPUT_SLOT = 0; public static final int CORE_SLOT = 1; public static final int OUTPUT_SLOT = 2; - private static final int INVENTORY_START = 3; - private static final int INVENTORY_END = INVENTORY_START + 27; - private static final int HOTBAR_START = INVENTORY_END; - private static final int HOTBAR_END = HOTBAR_START + 9; - - private final Container inventory; - private final ContainerData propertyDelegate; public CondenserScreenHandler(int syncId, Inventory playerInventory) { this(syncId, playerInventory, new SimpleContainer(3), new SimpleContainerData(2)); } public CondenserScreenHandler(int syncId, Inventory playerInventory, Container inventory, ContainerData propertyDelegate) { - super(ModScreenHandlers.CONDENSER_SCREEN_HANDLER_TYPE.get(), syncId); - checkContainerSize(inventory, 3); - this.inventory = inventory; - this.propertyDelegate = propertyDelegate; - inventory.startOpen(playerInventory.player); + super(ModScreenHandlers.CONDENSER_SCREEN_HANDLER_TYPE.get(), syncId, playerInventory, inventory, propertyDelegate, 3); this.addSlot(new CondenserInputSlot(inventory, INPUT_SLOT, 56, 17)); this.addSlot(new CondenserCoreSlot(inventory, CORE_SLOT, 56, 53)); @@ -52,9 +41,7 @@ public void onTake(Player player, ItemStack stack) { } }); - addInventory(playerInventory); - addPlayerHotbar(playerInventory); - addDataSlots(propertyDelegate); + finishSetup(playerInventory); } public int getScaledProgress() { @@ -77,51 +64,20 @@ public BigInteger getStoredEmc() { return EMCOrbItem.isEMCOrb(output) ? EMCOrbItem.getEmcBig(output) : BigInteger.ZERO; } - @Override - public ItemStack quickMoveStack(Player player, int invSlot) { - ItemStack newStack = ItemStack.EMPTY; - Slot slot = this.slots.get(invSlot); - if (slot == null || !slot.hasItem()) return newStack; - - ItemStack originalStack = slot.getItem(); - newStack = originalStack.copy(); - - if (invSlot == OUTPUT_SLOT) { - triggerOrbAdvancement(player, originalStack); - if (!this.moveItemStackTo(originalStack, INVENTORY_START, HOTBAR_END, true)) return ItemStack.EMPTY; - slot.onQuickCraft(originalStack, newStack); - } else if (invSlot >= INVENTORY_START && invSlot < HOTBAR_END) { - if (EmcCoreItem.isEmcCore(originalStack)) { - if (!this.moveItemStackTo(originalStack, CORE_SLOT, CORE_SLOT + 1, false)) return ItemStack.EMPTY; - } else if (isCondensable(originalStack)) { - if (!this.moveItemStackTo(originalStack, INPUT_SLOT, INPUT_SLOT + 1, false)) return ItemStack.EMPTY; - } else if (invSlot < INVENTORY_END) { - if (!this.moveItemStackTo(originalStack, HOTBAR_START, HOTBAR_END, false)) return ItemStack.EMPTY; - } else if (!this.moveItemStackTo(originalStack, INVENTORY_START, INVENTORY_END, false)) { - return ItemStack.EMPTY; - } - } else if (!this.moveItemStackTo(originalStack, INVENTORY_START, HOTBAR_END, false)) { - return ItemStack.EMPTY; - } - - if (originalStack.isEmpty()) { - slot.setByPlayer(ItemStack.EMPTY); - } else { - slot.setChanged(); - } - - return newStack; + protected int outputSlot() { + return OUTPUT_SLOT; } @Override - public boolean stillValid(Player player) { - return this.inventory.stillValid(player); + protected void beforeOutputQuickMove(Player player, ItemStack stack) { + triggerOrbAdvancement(player, stack); } @Override - public void removed(Player player) { - super.removed(player); - this.inventory.stopOpen(player); + protected boolean movePlayerStack(ItemStack stack, int slotIndex) { + if (EmcCoreItem.isEmcCore(stack)) return moveItemStackTo(stack, CORE_SLOT, CORE_SLOT + 1, false); + if (isCondensable(stack)) return moveItemStackTo(stack, INPUT_SLOT, INPUT_SLOT + 1, false); + return moveBetweenPlayerInventoryAndHotbar(stack, slotIndex); } private boolean isCondensable(ItemStack stack) { @@ -142,17 +98,4 @@ private static void triggerOrbAdvancement(Player player, ItemStack stack) { } } - private void addInventory(Inventory playerInventory) { - for (int i = 0; i < 3; ++i) { - for (int l = 0; l < 9; ++l) { - this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18)); - } - } - } - - private void addPlayerHotbar(Inventory playerInventory) { - for (int i = 0; i < 9; ++i) { - this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); - } - } } diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/screen/MachineScreenHandler.java b/src/main/java/net/exohayvan/dissolver_enhanced/screen/MachineScreenHandler.java new file mode 100644 index 0000000..aff41aa --- /dev/null +++ b/src/main/java/net/exohayvan/dissolver_enhanced/screen/MachineScreenHandler.java @@ -0,0 +1,89 @@ +package net.exohayvan.dissolver_enhanced.screen; + +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +abstract class MachineScreenHandler extends AbstractContainerMenu { + protected final Container inventory; + protected final ContainerData propertyDelegate; + protected final int inventoryStart; + protected final int inventoryEnd; + protected final int hotbarStart; + protected final int hotbarEnd; + + protected MachineScreenHandler(MenuType type, int syncId, Inventory playerInventory, + Container inventory, ContainerData propertyDelegate, int machineSlots) { + super(type, syncId); + checkContainerSize(inventory, machineSlots); + this.inventory = inventory; + this.propertyDelegate = propertyDelegate; + this.inventoryStart = machineSlots; + this.inventoryEnd = machineSlots + 27; + this.hotbarStart = this.inventoryEnd; + this.hotbarEnd = this.hotbarStart + 9; + inventory.startOpen(playerInventory.player); + } + + protected final void finishSetup(Inventory playerInventory) { + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 9; column++) { + this.addSlot(new Slot(playerInventory, column + row * 9 + 9, 8 + column * 18, 84 + row * 18)); + } + } + for (int column = 0; column < 9; column++) { + this.addSlot(new Slot(playerInventory, column, 8 + column * 18, 142)); + } + addDataSlots(propertyDelegate); + } + + protected abstract int outputSlot(); + + protected abstract boolean movePlayerStack(ItemStack stack, int slotIndex); + + protected void beforeOutputQuickMove(Player player, ItemStack stack) { + } + + @Override + public final ItemStack quickMoveStack(Player player, int slotIndex) { + Slot slot = this.slots.get(slotIndex); + if (slot == null || !slot.hasItem()) return ItemStack.EMPTY; + + ItemStack original = slot.getItem(); + ItemStack copy = original.copy(); + if (slotIndex == outputSlot()) { + beforeOutputQuickMove(player, original); + if (!moveItemStackTo(original, inventoryStart, hotbarEnd, true)) return ItemStack.EMPTY; + slot.onQuickCraft(original, copy); + } else if (slotIndex >= inventoryStart && slotIndex < hotbarEnd) { + if (!movePlayerStack(original, slotIndex)) return ItemStack.EMPTY; + } else if (!moveItemStackTo(original, inventoryStart, hotbarEnd, false)) { + return ItemStack.EMPTY; + } + + if (original.isEmpty()) slot.setByPlayer(ItemStack.EMPTY); + else slot.setChanged(); + return copy; + } + + protected final boolean moveBetweenPlayerInventoryAndHotbar(ItemStack stack, int slotIndex) { + if (slotIndex < inventoryEnd) return moveItemStackTo(stack, hotbarStart, hotbarEnd, false); + return moveItemStackTo(stack, inventoryStart, inventoryEnd, false); + } + + @Override + public boolean stillValid(Player player) { + return inventory.stillValid(player); + } + + @Override + public void removed(Player player) { + super.removed(player); + inventory.stopOpen(player); + } +} \ No newline at end of file diff --git a/src/main/java/net/exohayvan/dissolver_enhanced/screen/MaterializerScreenHandler.java b/src/main/java/net/exohayvan/dissolver_enhanced/screen/MaterializerScreenHandler.java index f75115e..1c18326 100644 --- a/src/main/java/net/exohayvan/dissolver_enhanced/screen/MaterializerScreenHandler.java +++ b/src/main/java/net/exohayvan/dissolver_enhanced/screen/MaterializerScreenHandler.java @@ -14,42 +14,29 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.inventory.SimpleContainerData; import net.minecraft.world.inventory.ContainerData; -import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.inventory.Slot; -public class MaterializerScreenHandler extends AbstractContainerMenu { +public class MaterializerScreenHandler extends MachineScreenHandler { public static final int TARGET_SLOT = 0; public static final int EMC_INPUT_SLOT = 1; public static final int CORE_SLOT = 2; public static final int OUTPUT_SLOT = 3; private static final int MATERIALIZER_SLOT_COUNT = 4; - private static final int INVENTORY_START = MATERIALIZER_SLOT_COUNT; - private static final int INVENTORY_END = INVENTORY_START + 27; - private static final int HOTBAR_START = INVENTORY_END; - private static final int HOTBAR_END = HOTBAR_START + 9; - - private final Container inventory; - private final ContainerData propertyDelegate; public MaterializerScreenHandler(int syncId, Inventory playerInventory) { this(syncId, playerInventory, new SimpleContainer(MATERIALIZER_SLOT_COUNT), new SimpleContainerData(5)); } public MaterializerScreenHandler(int syncId, Inventory playerInventory, Container inventory, ContainerData propertyDelegate) { - super(ModScreenHandlers.MATERIALIZER_SCREEN_HANDLER_TYPE.get(), syncId); - checkContainerSize(inventory, MATERIALIZER_SLOT_COUNT); - this.inventory = inventory; - this.propertyDelegate = propertyDelegate; - inventory.startOpen(playerInventory.player); + super(ModScreenHandlers.MATERIALIZER_SCREEN_HANDLER_TYPE.get(), syncId, playerInventory, inventory, propertyDelegate, MATERIALIZER_SLOT_COUNT); this.addSlot(new MaterializerTemplateSlot(inventory, TARGET_SLOT, 56, 17)); this.addSlot(new MaterializerTemplateSlot(inventory, EMC_INPUT_SLOT, 38, 53)); this.addSlot(new CondenserCoreSlot(inventory, CORE_SLOT, 56, 53)); this.addSlot(new OutputOnlySlot(inventory, OUTPUT_SLOT, 116, 35)); - addInventory(playerInventory); - addPlayerHotbar(playerInventory); - addDataSlots(propertyDelegate); + finishSetup(playerInventory); } public int getScaledProgress() { @@ -74,55 +61,19 @@ public int getMaterializingRatePerSecond() { return getTargetValue() > 0 && getInputValue() > 0 ? EmcCoreItem.getEmcPerSecond(this.inventory.getItem(CORE_SLOT)) : 0; } - @Override - public ItemStack quickMoveStack(Player player, int invSlot) { - ItemStack newStack = ItemStack.EMPTY; - Slot slot = this.slots.get(invSlot); - if (slot == null || !slot.hasItem()) return newStack; - - ItemStack originalStack = slot.getItem(); - newStack = originalStack.copy(); - - if (invSlot == OUTPUT_SLOT) { - if (!this.moveItemStackTo(originalStack, INVENTORY_START, HOTBAR_END, true)) return ItemStack.EMPTY; - slot.onQuickCraft(originalStack, newStack); - } else if (invSlot >= INVENTORY_START && invSlot < HOTBAR_END) { - if (EmcCoreItem.isEmcCore(originalStack)) { - if (!this.moveItemStackTo(originalStack, CORE_SLOT, CORE_SLOT + 1, false)) return ItemStack.EMPTY; - } else if (isMaterializableTarget(originalStack)) { - if (!this.moveItemStackTo(originalStack, TARGET_SLOT, TARGET_SLOT + 1, false) - && !this.moveItemStackTo(originalStack, EMC_INPUT_SLOT, EMC_INPUT_SLOT + 1, false)) { - return ItemStack.EMPTY; - } - } else if (isEmcInput(originalStack)) { - if (!this.moveItemStackTo(originalStack, EMC_INPUT_SLOT, EMC_INPUT_SLOT + 1, false)) return ItemStack.EMPTY; - } else if (invSlot < INVENTORY_END) { - if (!this.moveItemStackTo(originalStack, HOTBAR_START, HOTBAR_END, false)) return ItemStack.EMPTY; - } else if (!this.moveItemStackTo(originalStack, INVENTORY_START, INVENTORY_END, false)) { - return ItemStack.EMPTY; - } - } else if (!this.moveItemStackTo(originalStack, INVENTORY_START, HOTBAR_END, false)) { - return ItemStack.EMPTY; - } - - if (originalStack.isEmpty()) { - slot.setByPlayer(ItemStack.EMPTY); - } else { - slot.setChanged(); - } - - return newStack; - } - - @Override - public boolean stillValid(Player player) { - return this.inventory.stillValid(player); + protected int outputSlot() { + return OUTPUT_SLOT; } @Override - public void removed(Player player) { - super.removed(player); - this.inventory.stopOpen(player); + protected boolean movePlayerStack(ItemStack stack, int slotIndex) { + if (EmcCoreItem.isEmcCore(stack)) return moveItemStackTo(stack, CORE_SLOT, CORE_SLOT + 1, false); + if (isMaterializableTarget(stack)) { + return moveItemStackTo(stack, TARGET_SLOT, TARGET_SLOT + 1, false) + || moveItemStackTo(stack, EMC_INPUT_SLOT, EMC_INPUT_SLOT + 1, false); + } + if (isEmcInput(stack)) return moveItemStackTo(stack, EMC_INPUT_SLOT, EMC_INPUT_SLOT + 1, false); + return moveBetweenPlayerInventoryAndHotbar(stack, slotIndex); } private boolean isMaterializableTarget(ItemStack stack) { @@ -133,17 +84,4 @@ private boolean isEmcInput(ItemStack stack) { return EMCOrbItem.isEMCOrb(stack) ? EMCOrbItem.getEMC(stack) > 0 : EMCValues.get(EMCKey.fromStack(stack)) > 0; } - private void addInventory(Inventory playerInventory) { - for (int i = 0; i < 3; ++i) { - for (int l = 0; l < 9; ++l) { - this.addSlot(new Slot(playerInventory, l + i * 9 + 9, 8 + l * 18, 84 + i * 18)); - } - } - } - - private void addPlayerHotbar(Inventory playerInventory) { - for (int i = 0; i < 9; ++i) { - this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142)); - } - } } diff --git a/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java b/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java index 757934d..cc44fa9 100644 --- a/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java +++ b/src/test/java/net/exohayvan/dissolver_enhanced/data/EMCExpected.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; public class EMCExpected { @@ -367,350 +368,88 @@ public static List standard() { } public static List skyblocks(){ - List list = new ArrayList<>(Arrays.asList( - new EMCRecord("minecraft:burn_pottery_sherd", 2500), - new EMCRecord("minecraft:music_disc_stal", 8000), - new EMCRecord("minecraft:name_tag", 250), - new EMCRecord("minecraft:music_disc_chirp", 8000), - new EMCRecord("minecraft:hanging_roots", 80), - new EMCRecord("minecraft:archer_pottery_sherd", 2500), - new EMCRecord("minecraft:scrape_pottery_sherd", 2500), - new EMCRecord("minecraft:wheat_seeds", 90), - new EMCRecord("minecraft:chainmail_helmet", 2000), - new EMCRecord("minecraft:golden_horse_armor", 2160), - new EMCRecord("minecraft:experience_bottle", 600), - new EMCRecord("minecraft:dead_tube_coral", 2), - new EMCRecord("minecraft:black_shulker_box", 2032), - new EMCRecord("minecraft:coast_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:rabbit_hide", 20), - new EMCRecord("minecraft:pearlescent_froglight", 400), - new EMCRecord("minecraft:powder_snow_bucket", 1580), - new EMCRecord("minecraft:explorer_pottery_sherd", 2500), - new EMCRecord("minecraft:beetroot", 20), - new EMCRecord("minecraft:chainmail_leggings", 2800), - new EMCRecord("minecraft:gray_dye", 8), - new EMCRecord("minecraft:cocoa_beans", 12), - new EMCRecord("minecraft:warped_wart_block", 5), - new EMCRecord("minecraft:saddle", 250), - new EMCRecord("minecraft:ochre_froglight", 400), - new EMCRecord("minecraft:small_dripleaf", 92), - new EMCRecord("minecraft:moss_block", 90), - new EMCRecord("minecraft:raw_gold", 1790), - new EMCRecord("cobblemon:relic_coin", 150), - new EMCRecord("minecraft:lily_pad", 90), - new EMCRecord("minecraft:tube_coral", 4), - new EMCRecord("minecraft:weathered_copper_trapdoor", 748), - new EMCRecord("minecraft:friend_pottery_sherd", 2500), - new EMCRecord("minecraft:apple", 30), - new EMCRecord("minecraft:heart_of_the_sea", 500), - new EMCRecord("minecraft:music_disc_precipice", 8000), - new EMCRecord("minecraft:beef", 50), - new EMCRecord("minecraft:bubble_coral_fan", 4), - new EMCRecord("minecraft:blue_dye", 8), - new EMCRecord("minecraft:snout_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:sentry_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:brewer_pottery_sherd", 2500), - new EMCRecord("minecraft:light_gray_shulker_box", 2032), - new EMCRecord("minecraft:netherite_upgrade_smithing_template", 9700), - new EMCRecord("minecraft:white_shulker_box", 2032), - new EMCRecord("minecraft:music_disc_ward", 8000), - new EMCRecord("minecraft:seagrass", 90), - new EMCRecord("minecraft:sculk", 600), - new EMCRecord("minecraft:jungle_sapling", 3), - new EMCRecord("minecraft:turtle_egg", 70), - new EMCRecord("minecraft:spire_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:big_dripleaf", 92), - new EMCRecord("minecraft:mushroom_stem", 20), - new EMCRecord("minecraft:globe_banner_pattern", 200), - new EMCRecord("minecraft:fire_coral_block", 8), - new EMCRecord("minecraft:piglin_head", 10000), - new EMCRecord("minecraft:netherite_pickaxe", 27568), - new EMCRecord("minecraft:music_disc_otherside", 8000), - new EMCRecord("minecraft:wheat", 20), - new EMCRecord("minecraft:dead_horn_coral_fan", 2), - new EMCRecord("minecraft:verdant_froglight", 400), - new EMCRecord("minecraft:dead_brain_coral_fan", 2), - new EMCRecord("minecraft:heavy_core", 2200), - new EMCRecord("minecraft:lime_shulker_box", 2032), - new EMCRecord("minecraft:ancient_debris", 800), - new EMCRecord("minecraft:ice", 90), - new EMCRecord("minecraft:armadillo_scute", 180), - new EMCRecord("minecraft:waxed_exposed_chiseled_copper", 907), - new EMCRecord("minecraft:honey_bottle", 75), - new EMCRecord("minecraft:dead_brain_coral", 2), - new EMCRecord("minecraft:lava_bucket", 1700), - new EMCRecord("minecraft:music_disc_mall", 8000), - new EMCRecord("minecraft:gunpowder", 56), - new EMCRecord("minecraft:sweet_berries", 18), - new EMCRecord("minecraft:dragon_breath", 150), - new EMCRecord("minecraft:zombie_head", 10000), - new EMCRecord("minecraft:ghast_tear", 220), - new EMCRecord("minecraft:skeleton_skull", 10000), - new EMCRecord("minecraft:dirt_path", 80), - new EMCRecord("minecraft:white_dye", 8), - new EMCRecord("minecraft:elytra", 1500), - new EMCRecord("minecraft:horn_coral_fan", 4), - new EMCRecord("minecraft:oxidized_copper", 2244), - new EMCRecord("minecraft:nether_quartz_ore", 120), - new EMCRecord("minecraft:azalea", 5), - new EMCRecord("minecraft:chainmail_chestplate", 3200), - new EMCRecord("minecraft:netherite_hoe", 19568), - new EMCRecord("minecraft:cyan_dye", 8), - new EMCRecord("minecraft:wayfinder_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:green_shulker_box", 2032), - new EMCRecord("minecraft:sculk_catalyst", 800), - new EMCRecord("minecraft:podzol", 90), - new EMCRecord("minecraft:netherite_helmet", 43564), - new EMCRecord("minecraft:blade_pottery_sherd", 2500), - new EMCRecord("minecraft:magenta_dye", 8), - new EMCRecord("minecraft:orange_shulker_box", 2032), - new EMCRecord("minecraft:creeper_head", 10000), - new EMCRecord("minecraft:milk_bucket", 1550), - new EMCRecord("minecraft:iron_horse_armor", 600), - new EMCRecord("minecraft:brown_mushroom_block", 20), - new EMCRecord("minecraft:warped_nylium", 90), - new EMCRecord("minecraft:music_disc_creator_music_box", 8000), - new EMCRecord("minecraft:feather", 8), - new EMCRecord("minecraft:light_blue_dye", 8), - new EMCRecord("minecraft:ender_pearl", 80), - new EMCRecord("minecraft:warped_fungus", 90), - new EMCRecord("minecraft:rib_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:bubble_coral", 4), - new EMCRecord("minecraft:pumpkin", 36), - new EMCRecord("minecraft:wither_skeleton_skull", 4000), - new EMCRecord("minecraft:oak_sapling", 3), - new EMCRecord("minecraft:suspicious_gravel", 80), - new EMCRecord("minecraft:netherite_boots", 35564), - new EMCRecord("minecraft:dragon_head", 1500), - new EMCRecord("minecraft:dead_fire_coral_fan", 2), - new EMCRecord("minecraft:netherite_leggings", 59564), - new EMCRecord("minecraft:magma_cream", 150), - new EMCRecord("minecraft:lime_dye", 8), - new EMCRecord("minecraft:sheaf_pottery_sherd", 2500), - new EMCRecord("minecraft:music_disc_far", 8000), - new EMCRecord("minecraft:bolt_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:dune_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:dead_tube_coral_block", 4), - new EMCRecord("minecraft:string", 12), - new EMCRecord("minecraft:dead_fire_coral_block", 4), - new EMCRecord("minecraft:red_dye", 8), - new EMCRecord("minecraft:rabbit_foot", 22), - new EMCRecord("minecraft:crimson_fungus", 90), - new EMCRecord("minecraft:purple_dye", 8), - new EMCRecord("minecraft:arms_up_pottery_sherd", 2500), - new EMCRecord("minecraft:dirt", 80), - new EMCRecord("minecraft:sea_pickle", 5), - new EMCRecord("minecraft:weathered_copper", 2144), - new EMCRecord("minecraft:exposed_chiseled_copper", 864), - new EMCRecord("minecraft:oxidized_copper_trapdoor", 798), - new EMCRecord("minecraft:weathered_copper_door", 498), - new EMCRecord("minecraft:green_dye", 8), - new EMCRecord("minecraft:raw_copper", 206), - new EMCRecord("minecraft:nether_sprouts", 90), - new EMCRecord("minecraft:exposed_copper_trapdoor", 698), - new EMCRecord("minecraft:music_disc_relic", 8000), - new EMCRecord("minecraft:mutton", 40), - new EMCRecord("minecraft:amethyst_cluster", 4800), - new EMCRecord("minecraft:pufferfish", 38), - new EMCRecord("minecraft:music_disc_pigstep", 8000), - new EMCRecord("minecraft:porkchop", 50), - new EMCRecord("minecraft:end_stone", 12), - new EMCRecord("minecraft:vex_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:raw_iron", 490), - new EMCRecord("minecraft:flint", 4), - new EMCRecord("minecraft:ink_sac", 25), - new EMCRecord("minecraft:heartbreak_pottery_sherd", 2500), - new EMCRecord("minecraft:tube_coral_block", 8), - new EMCRecord("minecraft:orange_dye", 8), - new EMCRecord("minecraft:mangrove_roots", 5), - new EMCRecord("minecraft:ward_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:chicken", 40), - new EMCRecord("minecraft:cactus", 5), - new EMCRecord("minecraft:magenta_shulker_box", 2032), - new EMCRecord("minecraft:howl_pottery_sherd", 2500), - new EMCRecord("minecraft:pink_shulker_box", 2032), - new EMCRecord("minecraft:shulker_shell", 1000), - new EMCRecord("minecraft:exposed_copper", 2044), - new EMCRecord("minecraft:gilded_blackstone", 1800), - new EMCRecord("minecraft:twisting_vines", 90), - new EMCRecord("minecraft:netherrack", 80), - new EMCRecord("minecraft:pitcher_pod", 90), - new EMCRecord("minecraft:prismarine_crystals", 400), - new EMCRecord("minecraft:dead_bubble_coral", 2), - new EMCRecord("minecraft:carved_pumpkin", 40), - new EMCRecord("minecraft:mud", 80), - new EMCRecord("minecraft:yellow_shulker_box", 2032), - new EMCRecord("minecraft:brain_coral_block", 8), - new EMCRecord("minecraft:plenty_pottery_sherd", 2500), - new EMCRecord("minecraft:cobbled_deepslate", 2), - new EMCRecord("minecraft:black_dye", 8), - new EMCRecord("minecraft:player_head", 12000), - new EMCRecord("minecraft:bee_nest", 174), - new EMCRecord("minecraft:calcite", 600), - new EMCRecord("minecraft:netherite_chestplate", 67564), - new EMCRecord("minecraft:snort_pottery_sherd", 2500), - new EMCRecord("minecraft:blackstone", 2), - new EMCRecord("minecraft:large_fern", 90), - new EMCRecord("minecraft:fern", 90), - new EMCRecord("minecraft:nether_star", 120000), - new EMCRecord("minecraft:dead_bubble_coral_fan", 2), - new EMCRecord("minecraft:brain_coral", 4), - new EMCRecord("minecraft:trident", 3000), - new EMCRecord("minecraft:prismarine_shard", 160), - new EMCRecord("minecraft:chorus_plant", 5), - new EMCRecord("minecraft:slime_ball", 30), - new EMCRecord("minecraft:dragon_egg", 180000), - new EMCRecord("minecraft:spruce_sapling", 3), - new EMCRecord("minecraft:soul_soil", 3), - new EMCRecord("minecraft:frogspawn", 60), - new EMCRecord("minecraft:crimson_roots", 90), - new EMCRecord("minecraft:glowstone_dust", 38), - new EMCRecord("minecraft:yellow_dye", 8), - new EMCRecord("minecraft:flow_pottery_sherd", 2500), - new EMCRecord("minecraft:stripped_bamboo_block", 9), - new EMCRecord("minecraft:nautilus_shell", 200), - new EMCRecord("minecraft:music_disc_wait", 8000), - new EMCRecord("minecraft:basalt", 4), - new EMCRecord("minecraft:short_grass", 80), - new EMCRecord("minecraft:sculk_sensor", 1400), - new EMCRecord("minecraft:silence_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:dead_tube_coral_fan", 2), - new EMCRecord("minecraft:cyan_shulker_box", 2032), - new EMCRecord("minecraft:crimson_nylium", 90), - new EMCRecord("minecraft:clay_ball", 5), - new EMCRecord("minecraft:cod", 35), - new EMCRecord("minecraft:totem_of_undying", 60000), - new EMCRecord("minecraft:birch_sapling", 3), - new EMCRecord("minecraft:pink_dye", 8), - new EMCRecord("minecraft:dead_bush", 80), - new EMCRecord("minecraft:farmland", 80), - new EMCRecord("minecraft:purple_shulker_box", 2032), - new EMCRecord("minecraft:glow_ink_sac", 120), - new EMCRecord("minecraft:enchanted_golden_apple", 50000), - new EMCRecord("minecraft:bell", 800), - new EMCRecord("minecraft:netherite_axe", 27568), - new EMCRecord("minecraft:sculk_vein", 400), - new EMCRecord("minecraft:sculk_shrieker", 2800), - new EMCRecord("minecraft:guster_banner_pattern", 200), - new EMCRecord("minecraft:gravel", 80), - new EMCRecord("minecraft:nether_wart", 40), - new EMCRecord("minecraft:music_disc_strad", 8000), - new EMCRecord("minecraft:music_disc_creator", 8000), - new EMCRecord("minecraft:vine", 90), - new EMCRecord("minecraft:soul_sand", 3), - new EMCRecord("minecraft:dead_fire_coral", 2), - new EMCRecord("minecraft:red_mushroom_block", 20), - new EMCRecord("minecraft:piglin_banner_pattern", 200), - new EMCRecord("minecraft:tube_coral_fan", 4), - new EMCRecord("minecraft:flow_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:light_blue_shulker_box", 2032), - new EMCRecord("minecraft:music_disc_blocks", 8000), - new EMCRecord("minecraft:eye_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:glow_lichen", 90), - new EMCRecord("minecraft:heart_pottery_sherd", 2500), - new EMCRecord("minecraft:salmon", 35), - new EMCRecord("minecraft:dark_oak_sapling", 3), - new EMCRecord("minecraft:brain_coral_fan", 4), - new EMCRecord("minecraft:angler_pottery_sherd", 2500), - new EMCRecord("minecraft:music_disc_cat", 8000), - new EMCRecord("minecraft:flow_banner_pattern", 200), - new EMCRecord("minecraft:brown_mushroom", 5), - new EMCRecord("minecraft:blue_shulker_box", 2032), - new EMCRecord("minecraft:wild_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:chorus_fruit", 50), - new EMCRecord("minecraft:cherry_sapling", 3), - new EMCRecord("minecraft:rabbit", 40), - new EMCRecord("minecraft:fire_coral", 4), - new EMCRecord("minecraft:brown_dye", 8), - new EMCRecord("minecraft:skull_pottery_sherd", 2500), - new EMCRecord("minecraft:tide_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:bone", 27), - new EMCRecord("minecraft:grass_block", 90), - new EMCRecord("minecraft:dead_brain_coral_block", 4), - new EMCRecord("minecraft:acacia_sapling", 3), - new EMCRecord("minecraft:rotten_flesh", 80), - new EMCRecord("minecraft:danger_pottery_sherd", 2500), - new EMCRecord("minecraft:carrot", 24), - new EMCRecord("minecraft:goat_horn", 1200), - new EMCRecord("minecraft:guster_pottery_sherd", 2500), - new EMCRecord("minecraft:wet_sponge", 90), - new EMCRecord("minecraft:diamond_horse_armor", 9600), - new EMCRecord("minecraft:fire_coral_fan", 4), - new EMCRecord("minecraft:shroomlight", 80), - new EMCRecord("minecraft:blaze_rod", 60), - new EMCRecord("minecraft:dead_horn_coral", 2), - new EMCRecord("minecraft:oxidized_copper_door", 532), - new EMCRecord("minecraft:cobblestone", 2), - new EMCRecord("minecraft:glow_berries", 24), - new EMCRecord("minecraft:music_disc_5", 8000), - new EMCRecord("minecraft:cobweb", 12), - new EMCRecord("minecraft:weeping_vines", 90), - new EMCRecord("minecraft:exposed_copper_door", 465), - new EMCRecord("minecraft:echo_shard", 1800), - new EMCRecord("minecraft:raiser_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:snowball", 80), - new EMCRecord("minecraft:dead_horn_coral_block", 4), - new EMCRecord("minecraft:chainmail_boots", 1600), - new EMCRecord("minecraft:trial_key", 1200), - new EMCRecord("minecraft:pointed_dripstone", 5), - new EMCRecord("minecraft:horn_coral", 4), - new EMCRecord("minecraft:shelter_pottery_sherd", 2500), - new EMCRecord("minecraft:netherite_shovel", 11568), - new EMCRecord("minecraft:amethyst_shard", 1800), - new EMCRecord("minecraft:breeze_rod", 90), - new EMCRecord("minecraft:potato", 24), - new EMCRecord("minecraft:miner_pottery_sherd", 2500), - new EMCRecord("minecraft:water_bucket", 1520), - new EMCRecord("minecraft:turtle_scute", 180), - new EMCRecord("minecraft:melon_slice", 4), - new EMCRecord("minecraft:ominous_trial_key", 1300), - new EMCRecord("minecraft:bamboo", 1), - new EMCRecord("minecraft:mycelium", 5), - new EMCRecord("minecraft:phantom_membrane", 150), - new EMCRecord("minecraft:egg", 12), - new EMCRecord("minecraft:gray_shulker_box", 2032), - new EMCRecord("minecraft:mourner_pottery_sherd", 2500), - new EMCRecord("minecraft:obsidian", 140), - new EMCRecord("minecraft:sugar_cane", 25), - new EMCRecord("minecraft:red_shulker_box", 2032), - new EMCRecord("minecraft:poisonous_potato", 80), - new EMCRecord("minecraft:brown_shulker_box", 2032), - new EMCRecord("minecraft:red_mushroom", 5), - new EMCRecord("minecraft:shaper_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:torchflower_seeds", 90), - new EMCRecord("minecraft:honeycomb", 50), - new EMCRecord("minecraft:music_disc_mellohi", 8000), - new EMCRecord("minecraft:light_gray_dye", 8), - new EMCRecord("minecraft:rooted_dirt", 5), - new EMCRecord("minecraft:spider_eye", 25), - new EMCRecord("minecraft:music_disc_11", 8000), - new EMCRecord("minecraft:tropical_fish", 35), - new EMCRecord("minecraft:tall_grass", 80), - new EMCRecord("minecraft:music_disc_13", 8000), - new EMCRecord("minecraft:charcoal", 40), - new EMCRecord("carved_wood:pale_oak_chest", 32), - new EMCRecord("minecraft:bubble_coral_block", 8), - new EMCRecord("minecraft:horn_coral_block", 8), - new EMCRecord("minecraft:sniffer_egg", 80), - new EMCRecord("minecraft:kelp", 90), - new EMCRecord("minecraft:beetroot_seeds", 90), - new EMCRecord("minecraft:warped_roots", 90), - new EMCRecord("minecraft:prize_pottery_sherd", 2500), - new EMCRecord("minecraft:crying_obsidian", 140), - new EMCRecord("minecraft:dead_bubble_coral_block", 4), - new EMCRecord("minecraft:netherite_sword", 19566), - new EMCRecord("minecraft:host_armor_trim_smithing_template", 9200), - new EMCRecord("minecraft:grass", 1), - new EMCRecord("minecraft:chest", 32), - new EMCRecord("minecraft:spore_blossom", 16), - new EMCRecord("minecraft:tuff", 2), - new EMCRecord("minecraft:scute", 180), - new EMCRecord("minecraft:chorus_flower", 80), - new EMCRecord("minecraft:firework_rocket", 81), - new EMCRecord("minecraft:turtle_helmet", 900) - )); - list.addAll(eternalStarlightSkyblocks()); - list.addAll(simpleHats()); + List list = new ArrayList<>(common()); + Map overrides = Map.ofEntries( + Map.entry("minecraft:hanging_roots", 80), + Map.entry("minecraft:wheat_seeds", 90), + Map.entry("minecraft:chainmail_helmet", 2000), + Map.entry("minecraft:golden_horse_armor", 2160), + Map.entry("minecraft:powder_snow_bucket", 1580), + Map.entry("minecraft:chainmail_leggings", 2800), + Map.entry("minecraft:small_dripleaf", 92), + Map.entry("minecraft:moss_block", 90), + Map.entry("minecraft:raw_gold", 1790), + Map.entry("minecraft:lily_pad", 90), + Map.entry("minecraft:seagrass", 90), + Map.entry("minecraft:big_dripleaf", 92), + Map.entry("minecraft:netherite_pickaxe", 27568), + Map.entry("minecraft:ice", 90), + Map.entry("minecraft:lava_bucket", 1700), + Map.entry("minecraft:dirt_path", 80), + Map.entry("minecraft:chainmail_chestplate", 3200), + Map.entry("minecraft:netherite_hoe", 19568), + Map.entry("minecraft:podzol", 90), + Map.entry("minecraft:netherite_helmet", 43564), + Map.entry("minecraft:milk_bucket", 1550), + Map.entry("minecraft:iron_horse_armor", 600), + Map.entry("minecraft:warped_nylium", 90), + Map.entry("minecraft:warped_fungus", 90), + Map.entry("minecraft:suspicious_gravel", 80), + Map.entry("minecraft:netherite_boots", 35564), + Map.entry("minecraft:netherite_leggings", 59564), + Map.entry("minecraft:crimson_fungus", 90), + Map.entry("minecraft:nether_sprouts", 90), + Map.entry("minecraft:raw_iron", 490), + Map.entry("minecraft:gilded_blackstone", 1800), + Map.entry("minecraft:twisting_vines", 90), + Map.entry("minecraft:netherrack", 80), + Map.entry("minecraft:pitcher_pod", 90), + Map.entry("minecraft:mud", 80), + Map.entry("minecraft:netherite_chestplate", 67564), + Map.entry("minecraft:large_fern", 90), + Map.entry("minecraft:fern", 90), + Map.entry("minecraft:crimson_roots", 90), + Map.entry("minecraft:short_grass", 80), + Map.entry("minecraft:crimson_nylium", 90), + Map.entry("minecraft:dead_bush", 80), + Map.entry("minecraft:farmland", 80), + Map.entry("minecraft:netherite_axe", 27568), + Map.entry("minecraft:gravel", 80), + Map.entry("minecraft:vine", 90), + Map.entry("minecraft:glow_lichen", 90), + Map.entry("minecraft:grass_block", 90), + Map.entry("minecraft:rotten_flesh", 80), + Map.entry("minecraft:diamond_horse_armor", 9600), + Map.entry("minecraft:weeping_vines", 90), + Map.entry("minecraft:snowball", 80), + Map.entry("minecraft:chainmail_boots", 1600), + Map.entry("minecraft:netherite_shovel", 11568), + Map.entry("minecraft:water_bucket", 1520), + Map.entry("minecraft:poisonous_potato", 80), + Map.entry("minecraft:torchflower_seeds", 90), + Map.entry("minecraft:tall_grass", 80), + Map.entry("minecraft:kelp", 90), + Map.entry("minecraft:beetroot_seeds", 90), + Map.entry("minecraft:warped_roots", 90), + Map.entry("minecraft:netherite_sword", 19566), + Map.entry("eternal_starlight:thermal_springstone", 490), + Map.entry("eternal_starlight:cobbled_grimstone", 80), + Map.entry("eternal_starlight:cobbled_voidstone", 80) + ); + + for (int i = 0; i < list.size(); i++) { + EMCRecord record = list.get(i); + Integer override = overrides.get(record.getBlockName()); + if (override != null) { + list.set(i, new EMCRecord(record.getBlockName(), override)); + } + } + + int dirtIndex = 0; + while (!list.get(dirtIndex).getBlockName().equals("minecraft:arms_up_pottery_sherd")) { + dirtIndex++; + } + list.add(dirtIndex + 1, new EMCRecord("minecraft:dirt", 80)); return list; }