From 64d10ad45b4232399abc7052275ca4eaedbe37d1 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 24 Jun 2025 22:58:04 +0100 Subject: [PATCH] Data fixers for turtle owners --- .../computercraft/mixin/DataFixersMixin.java | 25 ++++++++++++ .../shared/datafix/TurtleOwnerFix.java | 38 +++++++++++++++++++ .../shared/turtle/core/TurtleBrain.java | 18 +++++++-- .../computercraft/gametest/Turtle_Test.kt | 5 +++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/datafix/TurtleOwnerFix.java diff --git a/projects/common/src/main/java/dan200/computercraft/mixin/DataFixersMixin.java b/projects/common/src/main/java/dan200/computercraft/mixin/DataFixersMixin.java index bbb89ef05..2b84a2a6a 100644 --- a/projects/common/src/main/java/dan200/computercraft/mixin/DataFixersMixin.java +++ b/projects/common/src/main/java/dan200/computercraft/mixin/DataFixersMixin.java @@ -9,6 +9,7 @@ import com.mojang.datafixers.DataFixUtils; import com.mojang.datafixers.DataFixerBuilder; import com.mojang.datafixers.schemas.Schema; import dan200.computercraft.shared.datafix.RenamePocketComputerUpgradeFix; +import dan200.computercraft.shared.datafix.TurtleOwnerFix; import dan200.computercraft.shared.datafix.TurtleUpgradeComponentizationFix; import net.minecraft.util.datafix.DataFixers; import net.minecraft.util.datafix.fixes.ItemStackComponentizationFix; @@ -63,6 +64,30 @@ abstract class DataFixersMixin { return schema; } + /** + * Register a {@link TurtleOwnerFix} fix. + * + * @param schema The {@code V4424} schema. + * @param builder The current datafixer builder. + * @return The input schema. + */ + @ModifyArg( + method = "addFixers", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/datafix/fixes/FeatureFlagRemoveFix;(Lcom/mojang/datafixers/schemas/Schema;Ljava/lang/String;Ljava/util/Set;)V", + ordinal = 4 + ), + index = 0, + allow = 1 + ) + @SuppressWarnings("UnusedMethod") + private static Schema addTurtleOwnerFix(Schema schema, @Local DataFixerBuilder builder) { + assertSchemaVersion(schema, DataFixUtils.makeKey(4424, 0)); + builder.addFixer(new TurtleOwnerFix(schema)); + return schema; + } + @Unique private static void assertSchemaVersion(Schema schema, int version) { if (schema.getVersionKey() != version) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/datafix/TurtleOwnerFix.java b/projects/common/src/main/java/dan200/computercraft/shared/datafix/TurtleOwnerFix.java new file mode 100644 index 000000000..7a1e44871 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/datafix/TurtleOwnerFix.java @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.datafix; + +import com.mojang.authlib.GameProfile; +import com.mojang.datafixers.TypeRewriteRule; +import com.mojang.datafixers.schemas.Schema; +import com.mojang.serialization.Dynamic; +import net.minecraft.util.datafix.fixes.AbstractUUIDFix; +import net.minecraft.util.datafix.fixes.References; + +/** + * A {@link com.mojang.datafixers.DataFix} that updates the turtle owner's {@link GameProfile} to use one more + * consistent with the rest of the game. + */ +public final class TurtleOwnerFix extends AbstractUUIDFix { + public TurtleOwnerFix(Schema outputSchema) { + super(outputSchema, References.BLOCK_ENTITY); + } + + @Override + protected TypeRewriteRule makeRule() { + return this.fixTypeEverywhereTyped("BlockEntityUUIDFix", getInputSchema().getType(typeReference), typed -> { + typed = updateNamedChoice(typed, "computercraft:turtle_normal", TurtleOwnerFix::updateTurtle); + return updateNamedChoice(typed, "computercraft:turtle_advanced", TurtleOwnerFix::updateTurtle); + }); + } + + private static Dynamic updateTurtle(Dynamic turtle) { + return turtle.update("Owner", profile -> profile + .renameField("Name", "name") + .remove("LowerId").remove("UpperId") + .setFieldIfPresent("id", createUUIDFromLongs(profile, "UpperId", "LowerId")) + ); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index b41ba31a8..a13632892 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -5,6 +5,8 @@ package dan200.computercraft.shared.turtle.core; import com.mojang.authlib.GameProfile; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import dan200.computercraft.api.lua.ILuaCallback; import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.peripheral.IPeripheral; @@ -26,6 +28,7 @@ import dan200.computercraft.shared.util.Holiday; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; +import net.minecraft.core.UUIDUtil; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.resources.ResourceLocation; @@ -62,6 +65,16 @@ public class TurtleBrain implements TurtleAccessInternal { private static final String NBT_SLOT = "Slot"; + /** + * {@link net.minecraft.world.item.component.ResolvableProfile#CODEC}, but resolving to a {@link GameProfile} + * directly. We don't use {@link ExtraCodecs#GAME_PROFILE}, as that encodes the UUID as a string, not an int array. + */ + private static final Codec GAME_PROFILE_CODEC = RecordCodecBuilder.create(instance -> instance.group( + UUIDUtil.CODEC.fieldOf("id").forGetter(GameProfile::getId), + ExtraCodecs.PLAYER_NAME.fieldOf("name").forGetter(GameProfile::getName) + ) + .apply(instance, GameProfile::new)); + private static final int ANIM_DURATION = 8; private TurtleBlockEntity owner; @@ -160,7 +173,7 @@ public class TurtleBrain implements TurtleAccessInternal { selectedSlot = nbt.getIntOr(NBT_SLOT, 0); // Read owner - owningPlayer = nbt.read("Owner", ExtraCodecs.GAME_PROFILE).orElse(null); + owningPlayer = nbt.read("Owner", GAME_PROFILE_CODEC).orElse(null); } public void writeToNBT(ValueOutput nbt) { @@ -168,8 +181,7 @@ public class TurtleBrain implements TurtleAccessInternal { // Write state nbt.putInt(NBT_SLOT, selectedSlot); - nbt.storeNullable("Owner", ExtraCodecs.GAME_PROFILE, owningPlayer); - // TODO(1.21.6): Data fixer for this. + nbt.storeNullable("Owner", GAME_PROFILE_CODEC, owningPlayer); } public void readDescription(ValueInput nbt) { diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index ad92831af..e70322cd0 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -4,6 +4,7 @@ package dan200.computercraft.gametest +import com.mojang.authlib.GameProfile import dan200.computercraft.api.ComputerCraftAPI import dan200.computercraft.api.ComputerCraftTags import dan200.computercraft.api.detail.ComponentDetailProvider @@ -26,6 +27,7 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorBlock import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState import dan200.computercraft.shared.turtle.apis.TurtleAPI import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity +import dan200.computercraft.shared.turtle.core.TurtleBrain import dan200.computercraft.shared.turtle.core.TurtleCraftCommand import dan200.computercraft.shared.turtle.items.TurtleItem import dan200.computercraft.shared.util.WaterloggableHelpers @@ -783,6 +785,9 @@ class Turtle_Test { val turtleItem = turtleBe.getItem(0) assertEquals(overlay, TurtleItem.getOverlay(turtleItem)) assertEquals(upgrade, TurtleItem.getUpgrade(turtleItem, TurtleSide.LEFT)) + + val owner = (turtleBe.access as TurtleBrain).owningPlayer + assertEquals(GameProfile(UUID.fromString("01986ee6-ca3c-3b64-bdcd-02039da5963f"), "Player436"), owner) } }