diff --git a/build.gradle b/build.gradle index b00e95e..de557b6 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" @@ -41,6 +42,10 @@ dependencies { modCompileOnly "dev.gegy:player-roles-api:1.6.13" } +test { + useJUnitPlatform() +} + processResources { inputs.property "version", project.version diff --git a/src/main/java/xyz/nucleoid/extras/mixin/lobby/ItemStackComponentizationFixMixin.java b/src/main/java/xyz/nucleoid/extras/mixin/lobby/ItemStackComponentizationFixMixin.java new file mode 100644 index 0000000..5895a54 --- /dev/null +++ b/src/main/java/xyz/nucleoid/extras/mixin/lobby/ItemStackComponentizationFixMixin.java @@ -0,0 +1,49 @@ +package xyz.nucleoid.extras.mixin.lobby; + +import com.mojang.serialization.Dynamic; +import net.minecraft.datafixer.fix.ItemStackComponentizationFix; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Set; + +@Mixin(ItemStackComponentizationFix.class) +public class ItemStackComponentizationFixMixin { + @Unique + private static final Set TATER_BOX_ITEMS = Set.of( + "nucleoid_extras:tater_box", + "nucleoid_extras:creative_tater_box" + ); + + @Inject(method = "fixStack", at = @At("TAIL")) + private static void fixNucleoidExtrasStack(ItemStackComponentizationFix.StackData data, Dynamic dynamic, CallbackInfo ci) { + if (data.itemEquals("nucleoid_extras:game_portal_opener")) { + data.getAndRemove("GamePortal").result().ifPresent(gamePortalId -> { + var component = dynamic.emptyMap().set("game_portal_id", gamePortalId); + data.setComponent("nucleoid_extras:game_portal", component); + }); + } else if (data.itemMatches(TATER_BOX_ITEMS)) { + data.getAndRemove("SelectedTater").result().ifPresent(gamePortalId -> { + var component = dynamic.emptyMap().set("tater", gamePortalId); + data.setComponent("nucleoid_extras:tater_selection", component); + }); + } else if (data.itemEquals("nucleoid_extras:tater_guidebook")) { + data.getAndRemove("tater_positions").result().ifPresent(positions -> { + var component = dynamic.emptyMap().set("positions", positions); + data.setComponent("nucleoid_extras:tater_positions", component); + }); + } else if (data.itemEquals("nucleoid_extras:launch_feather")) { + Dynamic component = dynamic.emptyMap(); + + component = data.moveToComponent("Pitch", component, "pitch"); + component = data.moveToComponent("Power", component, "power"); + + if (!component.equals(dynamic.emptyMap())) { + data.setComponent("nucleoid_extras:launcher", component); + } + } + } +} diff --git a/src/main/resources/extras.accesswidener b/src/main/resources/extras.accesswidener index 9b77221..de88960 100644 --- a/src/main/resources/extras.accesswidener +++ b/src/main/resources/extras.accesswidener @@ -6,3 +6,5 @@ accessible method net/minecraft/block/entity/BellBlockEntity applyGlowToRaide accessible method net/minecraft/block/entity/BellBlockEntity applyParticlesToRaiders (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Ljava/util/List;)V extendable method net/minecraft/block/AbstractBlock getTranslationKey ()Ljava/lang/String; + +accessible class net/minecraft/datafixer/fix/ItemStackComponentizationFix$StackData diff --git a/src/main/resources/extras.mixins.json b/src/main/resources/extras.mixins.json index 76f9cb0..43aa981 100644 --- a/src/main/resources/extras.mixins.json +++ b/src/main/resources/extras.mixins.json @@ -11,6 +11,7 @@ "TextCodecMixin", "debug.EntityMixin", "lobby.ArmorStandEntityAccessor", + "lobby.ItemStackComponentizationFixMixin", "lobby.LivingEntityAccessor", "lobby.ServerChunkLoadingManagerAccessor", "lobby.ServerPlayerEntityMixin", diff --git a/src/test/java/xyz/nucleoid/extras/test/DataFixTests.java b/src/test/java/xyz/nucleoid/extras/test/DataFixTests.java new file mode 100644 index 0000000..5deec1c --- /dev/null +++ b/src/test/java/xyz/nucleoid/extras/test/DataFixTests.java @@ -0,0 +1,294 @@ +package xyz.nucleoid.extras.test; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.serialization.Dynamic; +import net.minecraft.Bootstrap; +import net.minecraft.SharedConstants; +import net.minecraft.datafixer.Schemas; +import net.minecraft.datafixer.TypeReferences; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringNbtReader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class DataFixTests { + private static final int DATA_VERSION_1_20_4 = 3700; + private static final int DATA_VERSION_1_21_3 = 4082; + + + @BeforeAll + public static void beforeAll() { + SharedConstants.createGameVersion(); + Bootstrap.initialize(); + } + + @Test + public void testGamePortalOpenerWithoutIdComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:game_portal_opener", + Count: 1 + } + """, """ + { + id: "nucleoid_extras:game_portal_opener", + count: 1 + } + """); + } + + @Test + public void testGamePortalOpenerComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:game_portal_opener", + Count: 1, + tag: { + GamePortal: "nucleoid:top_level/navigator" + } + } + """, """ + { + id: "nucleoid_extras:game_portal_opener", + count: 1, + components: { + "nucleoid_extras:game_portal": { + game_portal_id: "nucleoid:top_level/navigator" + } + } + } + """); + } + + @Test + public void testTaterBoxWithoutSelectionComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:tater_box", + Count: 1 + } + """, """ + { + id: "nucleoid_extras:tater_box", + count: 1 + } + """); + } + + @Test + public void testTaterBoxComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:tater_box", + Count: 1, + tag: { + SelectedTater: "nucleoid_extras:tiny_potato" + } + } + """, """ + { + id: "nucleoid_extras:tater_box", + count: 1, + components: { + "nucleoid_extras:tater_selection": { + tater: "nucleoid_extras:tiny_potato" + } + } + } + """); + } + + @Test + public void testTaterBoxWithLegacyTatersComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:tater_box", + Count: 1, + tag: { + SelectedTater: "nucleoid_extras:botanical_tater", + Taters: [ + "nucleoid_extras:tiny_potato", + "nucleoid_extras:botanical_tater" + ] + } + } + """, """ + { + id: "nucleoid_extras:tater_box", + count: 1, + components: { + "nucleoid_extras:tater_selection": { + tater: "nucleoid_extras:botanical_tater" + }, + "minecraft:custom_data": { + Taters: [ + "nucleoid_extras:tiny_potato", + "nucleoid_extras:botanical_tater" + ] + } + } + } + """); + } + + @Test + public void testCreativeTaterBoxComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:creative_tater_box", + Count: 1, + tag: { + SelectedTater: "nucleoid_extras:bedrock_tater" + } + } + """, """ + { + id: "nucleoid_extras:creative_tater_box", + count: 1, + components: { + "nucleoid_extras:tater_selection": { + tater: "nucleoid_extras:bedrock_tater" + } + } + } + """); + } + + @Test + public void testTaterGuidebookComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:tater_guidebook", + Count: 1, + tag: { + tater_positions: { + "nucleoid_extras:irritater": [ + {X: 0, Y: 1, Z: 2} + ] + } + } + } + """, """ + { + id: "nucleoid_extras:tater_guidebook", + count: 1, + components: { + "nucleoid_extras:tater_positions": { + positions: { + "nucleoid_extras:irritater": [ + {X: 0, Y: 1, Z: 2} + ] + } + } + } + } + """); + } + + @Test + public void testDefaultLaunchFeatherComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:launch_feather", + Count: 1 + } + """, """ + { + id: "nucleoid_extras:launch_feather", + count: 1 + } + """); + } + + @Test + public void testLaunchFeatherWithPitchComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:launch_feather", + Count: 1, + tag: { + Pitch: 20.1 + } + } + """, """ + { + id: "nucleoid_extras:launch_feather", + count: 1, + components: { + "nucleoid_extras:launcher": { + pitch: 20.1 + } + } + } + """); + } + + @Test + public void testLaunchFeatherWithPowerComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:launch_feather", + Count: 1, + tag: { + Power: 5.2 + } + } + """, """ + { + id: "nucleoid_extras:launch_feather", + count: 1, + components: { + "nucleoid_extras:launcher": { + power: 5.2 + } + } + } + """); + } + + @Test + public void testLaunchFeatherWithPitchAndPowerComponentization() { + assertItemStackComponentization(""" + { + id: "nucleoid_extras:launch_feather", + Count: 1, + tag: { + Pitch: 18.3, + Power: 4.6 + } + } + """, """ + { + id: "nucleoid_extras:launch_feather", + count: 1, + components: { + "nucleoid_extras:launcher": { + pitch: 18.3, + power: 4.6 + } + } + } + """); + } + + private static void assertItemStackComponentization(String oldNbtString, String newNbtString) { + var oldNbt = parseNbtString("oldNbt", oldNbtString); + var newNbt = parseNbtString("newNbt", newNbtString); + + var input = new Dynamic<>(NbtOps.INSTANCE, oldNbt); + var output = Schemas.getFixer().update(TypeReferences.ITEM_STACK, input, DATA_VERSION_1_20_4, DATA_VERSION_1_21_3); + + assertEquals(newNbt, output.getValue()); + } + + private static NbtCompound parseNbtString(String name, String string) { + try { + return StringNbtReader.parse(string); + } catch (CommandSyntaxException e) { + throw new RuntimeException("Failed to parse " + name, e); + } + } +}