mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-04 07:32:59 +00:00 
			
		
		
		
	Allow upgrades to read/write upgrade data from ItemStacks (#1465)
This commit is contained in:
		@@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			|||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
					import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
				
			||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
					import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
				
			||||||
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
import net.minecraft.resources.ResourceLocation;
 | 
					import net.minecraft.resources.ResourceLocation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
@@ -28,12 +29,27 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
				
			|||||||
     * When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
 | 
					     * When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param upgrade The upgrade that you're getting the model for.
 | 
					     * @param upgrade The upgrade that you're getting the model for.
 | 
				
			||||||
     * @param turtle  Access to the turtle that the upgrade resides on. This will be null when getting item models!
 | 
					     * @param turtle  Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
 | 
				
			||||||
 | 
					     *                {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
 | 
				
			||||||
     * @param side    Which side of the turtle (left or right) the upgrade resides on.
 | 
					     * @param side    Which side of the turtle (left or right) the upgrade resides on.
 | 
				
			||||||
     * @return The model that you wish to be used to render your upgrade.
 | 
					     * @return The model that you wish to be used to render your upgrade.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
 | 
					    TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Obtain the model to be used when rendering a turtle peripheral.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param upgrade The upgrade that you're getting the model for.
 | 
				
			||||||
 | 
					     * @param data    Upgrade data instance for current turtle side.
 | 
				
			||||||
 | 
					     * @param side    Which side of the turtle (left or right) the upgrade resides on.
 | 
				
			||||||
 | 
					     * @return The model that you wish to be used to render your upgrade.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
 | 
				
			||||||
 | 
					        return getModel(upgrade, (ITurtleAccess) null, side);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
 | 
					     * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
 | 
				
			||||||
     * crafting item}.
 | 
					     * crafting item}.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,9 +5,11 @@
 | 
				
			|||||||
package dan200.computercraft.api.pocket;
 | 
					package dan200.computercraft.api.pocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
					import dan200.computercraft.api.peripheral.IPeripheral;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
				
			||||||
import net.minecraft.nbt.CompoundTag;
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
import net.minecraft.resources.ResourceLocation;
 | 
					import net.minecraft.resources.ResourceLocation;
 | 
				
			||||||
import net.minecraft.world.entity.Entity;
 | 
					import net.minecraft.world.entity.Entity;
 | 
				
			||||||
 | 
					import net.minecraft.world.item.ItemStack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@@ -69,6 +71,8 @@ public interface IPocketAccess {
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return The upgrade's NBT.
 | 
					     * @return The upgrade's NBT.
 | 
				
			||||||
     * @see #updateUpgradeNBTData()
 | 
					     * @see #updateUpgradeNBTData()
 | 
				
			||||||
 | 
					     * @see UpgradeBase#getUpgradeItem(CompoundTag)
 | 
				
			||||||
 | 
					     * @see UpgradeBase#getUpgradeData(ItemStack)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    CompoundTag getUpgradeNBTData();
 | 
					    CompoundTag getUpgradeNBTData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,10 +8,13 @@ import com.mojang.authlib.GameProfile;
 | 
				
			|||||||
import dan200.computercraft.api.lua.ILuaCallback;
 | 
					import dan200.computercraft.api.lua.ILuaCallback;
 | 
				
			||||||
import dan200.computercraft.api.lua.MethodResult;
 | 
					import dan200.computercraft.api.lua.MethodResult;
 | 
				
			||||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
					import dan200.computercraft.api.peripheral.IPeripheral;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import net.minecraft.core.BlockPos;
 | 
					import net.minecraft.core.BlockPos;
 | 
				
			||||||
import net.minecraft.core.Direction;
 | 
					import net.minecraft.core.Direction;
 | 
				
			||||||
import net.minecraft.nbt.CompoundTag;
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
import net.minecraft.world.Container;
 | 
					import net.minecraft.world.Container;
 | 
				
			||||||
 | 
					import net.minecraft.world.item.ItemStack;
 | 
				
			||||||
import net.minecraft.world.level.Level;
 | 
					import net.minecraft.world.level.Level;
 | 
				
			||||||
import net.minecraft.world.phys.Vec3;
 | 
					import net.minecraft.world.phys.Vec3;
 | 
				
			||||||
import org.jetbrains.annotations.ApiStatus;
 | 
					import org.jetbrains.annotations.ApiStatus;
 | 
				
			||||||
@@ -245,23 +248,51 @@ public interface ITurtleAccess {
 | 
				
			|||||||
    void playAnimation(TurtleAnimation animation);
 | 
					    void playAnimation(TurtleAnimation animation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the turtle on the specified side of the turtle, if there is one.
 | 
					     * Returns the upgrade on the specified side of the turtle, if there is one.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param side The side to get the upgrade from.
 | 
					     * @param side The side to get the upgrade from.
 | 
				
			||||||
     * @return The upgrade on the specified side of the turtle, if there is one.
 | 
					     * @return The upgrade on the specified side of the turtle, if there is one.
 | 
				
			||||||
     * @see #setUpgrade(TurtleSide, ITurtleUpgrade)
 | 
					     * @see #getUpgradeWithData(TurtleSide)
 | 
				
			||||||
 | 
					     * @see #setUpgradeWithData(TurtleSide, UpgradeData)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    @Nullable
 | 
					    @Nullable
 | 
				
			||||||
    ITurtleUpgrade getUpgrade(TurtleSide side);
 | 
					    ITurtleUpgrade getUpgrade(TurtleSide side);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
 | 
				
			||||||
 | 
					     * update data}.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param side The side to get the upgrade from.
 | 
				
			||||||
 | 
					     * @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
 | 
				
			||||||
 | 
					     * @see #getUpgradeWithData(TurtleSide)
 | 
				
			||||||
 | 
					     * @see #setUpgradeWithData(TurtleSide, UpgradeData)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
 | 
				
			||||||
 | 
					        var upgrade = getUpgrade(side);
 | 
				
			||||||
 | 
					        return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
 | 
					     * Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param side    The side to set the upgrade on.
 | 
					     * @param side    The side to set the upgrade on.
 | 
				
			||||||
     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
					     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
				
			||||||
     * @see #getUpgrade(TurtleSide)
 | 
					     * @see #getUpgrade(TurtleSide)
 | 
				
			||||||
 | 
					     * @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
 | 
					    @Deprecated
 | 
				
			||||||
 | 
					    default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
 | 
				
			||||||
 | 
					        setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set the upgrade for a given side and its upgrade data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param side    The side to set the upgrade on.
 | 
				
			||||||
 | 
					     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
				
			||||||
 | 
					     * @see #getUpgradeWithData(TurtleSide)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
 | 
					     * Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
 | 
				
			||||||
@@ -281,6 +312,8 @@ public interface ITurtleAccess {
 | 
				
			|||||||
     * @param side The side to get the upgrade data for.
 | 
					     * @param side The side to get the upgrade data for.
 | 
				
			||||||
     * @return The upgrade-specific data.
 | 
					     * @return The upgrade-specific data.
 | 
				
			||||||
     * @see #updateUpgradeNBTData(TurtleSide)
 | 
					     * @see #updateUpgradeNBTData(TurtleSide)
 | 
				
			||||||
 | 
					     * @see UpgradeBase#getUpgradeItem(CompoundTag)
 | 
				
			||||||
 | 
					     * @see UpgradeBase#getUpgradeData(ItemStack)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    CompoundTag getUpgradeNBTData(TurtleSide side);
 | 
					    CompoundTag getUpgradeNBTData(TurtleSide side);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
 | 
				
			|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
					import dan200.computercraft.api.peripheral.IPeripheral;
 | 
				
			||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
					import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
				
			||||||
import net.minecraft.core.Direction;
 | 
					import net.minecraft.core.Direction;
 | 
				
			||||||
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,4 +80,17 @@ public interface ITurtleUpgrade extends UpgradeBase {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    default void update(ITurtleAccess turtle, TurtleSide side) {
 | 
					    default void update(ITurtleAccess turtle, TurtleSide side) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get upgrade data that should be persisted when the turtle was broken.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * This method should be overridden when you don't need to store all upgrade data by default. For instance, if you
 | 
				
			||||||
 | 
					     * store peripheral state in the upgrade data, which should be lost when the turtle is broken.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param upgradeData Data that currently stored for this upgrade
 | 
				
			||||||
 | 
					     * @return Filtered version of this data.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default CompoundTag getPersistedData(CompoundTag upgradeData) {
 | 
				
			||||||
 | 
					        return upgradeData;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,10 +4,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package dan200.computercraft.api.upgrades;
 | 
					package dan200.computercraft.api.upgrades;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dan200.computercraft.api.pocket.IPocketAccess;
 | 
				
			||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
					import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
				
			||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
import dan200.computercraft.impl.PlatformHelper;
 | 
					import dan200.computercraft.impl.PlatformHelper;
 | 
				
			||||||
import net.minecraft.Util;
 | 
					import net.minecraft.Util;
 | 
				
			||||||
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
import net.minecraft.resources.ResourceLocation;
 | 
					import net.minecraft.resources.ResourceLocation;
 | 
				
			||||||
import net.minecraft.world.item.ItemStack;
 | 
					import net.minecraft.world.item.ItemStack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,6 +54,42 @@ public interface UpgradeBase {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    ItemStack getCraftingItem();
 | 
					    ItemStack getCraftingItem();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the item stack representing a currently equipped turtle upgrade.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
 | 
				
			||||||
 | 
					     * {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
 | 
				
			||||||
 | 
					     * and the original item stack is returned.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * By overriding this method, you can create a new {@link ItemStack} which contains enough data to
 | 
				
			||||||
 | 
					     * {@linkplain #getUpgradeData(ItemStack) re-create the upgrade data} if the item is re-equipped.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * When overriding this, you should override {@link #getUpgradeData(ItemStack)} and {@link #isItemSuitable(ItemStack)}
 | 
				
			||||||
 | 
					     * at the same time,
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
 | 
				
			||||||
 | 
					     * @return The item stack returned when unequipping.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default ItemStack getUpgradeItem(CompoundTag upgradeData) {
 | 
				
			||||||
 | 
					        return getCraftingItem();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Extract upgrade data from an {@link ItemStack}.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
 | 
				
			||||||
 | 
					     * {@link IPocketAccess#getUpgradeNBTData()}.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
 | 
				
			||||||
 | 
					     *              {@link #getCraftingItem()}.
 | 
				
			||||||
 | 
					     * @return The upgrade data that should be set on the turtle or pocket computer.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    default CompoundTag getUpgradeData(ItemStack stack) {
 | 
				
			||||||
 | 
					        return new CompoundTag();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Determine if an item is suitable for being used for this upgrade.
 | 
					     * Determine if an item is suitable for being used for this upgrade.
 | 
				
			||||||
     * <p>
 | 
					     * <p>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: MPL-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package dan200.computercraft.api.upgrades;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
 | 
					import net.minecraft.world.item.ItemStack;
 | 
				
			||||||
 | 
					import org.jetbrains.annotations.Contract;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
 | 
				
			||||||
 | 
					 * Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param upgrade The current upgrade.
 | 
				
			||||||
 | 
					 * @param data    The upgrade's data.
 | 
				
			||||||
 | 
					 * @param <T>     The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * A utility method to construct a new {@link UpgradeData} instance.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param upgrade An upgrade.
 | 
				
			||||||
 | 
					     * @param data    The upgrade's data.
 | 
				
			||||||
 | 
					     * @param <T>     The type of upgrade.
 | 
				
			||||||
 | 
					     * @return The new {@link UpgradeData} instance.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
 | 
				
			||||||
 | 
					        return new UpgradeData<>(upgrade, data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param upgrade The upgrade instance.
 | 
				
			||||||
 | 
					     * @param <T>     The type of upgrade.
 | 
				
			||||||
 | 
					     * @return The default upgrade data.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
 | 
				
			||||||
 | 
					        return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param upgrade The copied upgrade data.
 | 
				
			||||||
 | 
					     * @param <T>     The type of upgrade.
 | 
				
			||||||
 | 
					     * @return The newly created upgrade data.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Contract("!null -> !null; null -> null")
 | 
				
			||||||
 | 
					    public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
 | 
				
			||||||
 | 
					        return upgrade == null ? null : upgrade.copy();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
 | 
				
			||||||
 | 
					     * {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return This upgrade's item.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public ItemStack getUpgradeItem() {
 | 
				
			||||||
 | 
					        return upgrade.getUpgradeItem(data).copy();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
 | 
				
			||||||
 | 
					     * the upgrade data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return A copy of the current instance.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public UpgradeData<T> copy() {
 | 
				
			||||||
 | 
					        return new UpgradeData<>(upgrade(), data().copy());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,11 +4,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package dan200.computercraft.client.model.turtle;
 | 
					package dan200.computercraft.client.model.turtle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.cache.CacheBuilder;
 | 
				
			||||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
					import com.mojang.blaze3d.vertex.PoseStack;
 | 
				
			||||||
import com.mojang.math.Transformation;
 | 
					import com.mojang.math.Transformation;
 | 
				
			||||||
import dan200.computercraft.api.client.TransformedModel;
 | 
					import dan200.computercraft.api.client.TransformedModel;
 | 
				
			||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
					import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
				
			||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
 | 
					import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
 | 
				
			||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
					import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
				
			||||||
@@ -21,9 +23,9 @@ import net.minecraft.world.item.ItemStack;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -46,12 +48,19 @@ public final class TurtleModelParts<T> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private record Combination(
 | 
					    private record Combination(
 | 
				
			||||||
        boolean colour,
 | 
					        boolean colour,
 | 
				
			||||||
        @Nullable ITurtleUpgrade leftUpgrade,
 | 
					        @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
 | 
				
			||||||
        @Nullable ITurtleUpgrade rightUpgrade,
 | 
					        @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
 | 
				
			||||||
        @Nullable ResourceLocation overlay,
 | 
					        @Nullable ResourceLocation overlay,
 | 
				
			||||||
        boolean christmas,
 | 
					        boolean christmas,
 | 
				
			||||||
        boolean flip
 | 
					        boolean flip
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
 | 
					        Combination copy() {
 | 
				
			||||||
 | 
					            if (leftUpgrade == null && rightUpgrade == null) return this;
 | 
				
			||||||
 | 
					            return new Combination(
 | 
				
			||||||
 | 
					                colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade),
 | 
				
			||||||
 | 
					                overlay, christmas, flip
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final BakedModel familyModel;
 | 
					    private final BakedModel familyModel;
 | 
				
			||||||
@@ -63,12 +72,20 @@ public final class TurtleModelParts<T> {
 | 
				
			|||||||
     * A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
 | 
					     * A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
 | 
				
			||||||
     * instances, reducing memory usage and hopefully ensuring their caches are hit more often!
 | 
					     * instances, reducing memory usage and hopefully ensuring their caches are hit more often!
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
 | 
					    private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
 | 
				
			||||||
 | 
					        .concurrencyLevel(1)
 | 
				
			||||||
 | 
					        .expireAfterAccess(30, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					        .<TransformedModel, BakedModel>build()
 | 
				
			||||||
 | 
					        .asMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * A cache of {@link Combination}s to the combined model.
 | 
					     * A cache of {@link Combination}s to the combined model.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private final Map<Combination, T> modelCache = new HashMap<>();
 | 
					    private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
 | 
				
			||||||
 | 
					        .concurrencyLevel(1)
 | 
				
			||||||
 | 
					        .expireAfterAccess(30, TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					        .<Combination, T>build()
 | 
				
			||||||
 | 
					        .asMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
 | 
					    public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
 | 
				
			||||||
        this.familyModel = familyModel;
 | 
					        this.familyModel = familyModel;
 | 
				
			||||||
@@ -78,7 +95,15 @@ public final class TurtleModelParts<T> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public T getModel(ItemStack stack) {
 | 
					    public T getModel(ItemStack stack) {
 | 
				
			||||||
        return modelCache.computeIfAbsent(getCombination(stack), buildModel);
 | 
					        var combination = getCombination(stack);
 | 
				
			||||||
 | 
					        var existing = modelCache.get(combination);
 | 
				
			||||||
 | 
					        if (existing != null) return existing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Take a defensive copy of the upgrade data, and add it to the cache.
 | 
				
			||||||
 | 
					        var newCombination = combination.copy();
 | 
				
			||||||
 | 
					        var newModel = buildModel.apply(newCombination);
 | 
				
			||||||
 | 
					        modelCache.put(newCombination, newModel);
 | 
				
			||||||
 | 
					        return newModel;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private Combination getCombination(ItemStack stack) {
 | 
					    private Combination getCombination(ItemStack stack) {
 | 
				
			||||||
@@ -89,8 +114,8 @@ public final class TurtleModelParts<T> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var colour = turtle.getColour(stack);
 | 
					        var colour = turtle.getColour(stack);
 | 
				
			||||||
        var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
 | 
					        var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
 | 
				
			||||||
        var rightUpgrade = turtle.getUpgrade(stack, TurtleSide.RIGHT);
 | 
					        var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
 | 
				
			||||||
        var overlay = turtle.getOverlay(stack);
 | 
					        var overlay = turtle.getOverlay(stack);
 | 
				
			||||||
        var label = turtle.getLabel(stack);
 | 
					        var label = turtle.getLabel(stack);
 | 
				
			||||||
        var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
 | 
					        var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
 | 
				
			||||||
@@ -110,18 +135,19 @@ public final class TurtleModelParts<T> {
 | 
				
			|||||||
        if (overlayModelLocation != null) {
 | 
					        if (overlayModelLocation != null) {
 | 
				
			||||||
            parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
 | 
					            parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (combo.leftUpgrade() != null) {
 | 
					
 | 
				
			||||||
            var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
 | 
					        addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
 | 
				
			||||||
            parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
 | 
					        addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (combo.rightUpgrade() != null) {
 | 
					 | 
				
			||||||
            var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
 | 
					 | 
				
			||||||
            parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return parts;
 | 
					        return parts;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
 | 
				
			||||||
 | 
					        if (upgrade == null) return;
 | 
				
			||||||
 | 
					        var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
 | 
				
			||||||
 | 
					        parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private BakedModel transform(BakedModel model, Transformation transformation) {
 | 
					    private BakedModel transform(BakedModel model, Transformation transformation) {
 | 
				
			||||||
        if (transformation.equals(Transformation.identity())) return model;
 | 
					        if (transformation.equals(Transformation.identity())) return model;
 | 
				
			||||||
        return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
 | 
					        return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,8 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
				
			|||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import dan200.computercraft.impl.UpgradeManager;
 | 
					import dan200.computercraft.impl.UpgradeManager;
 | 
				
			||||||
import net.minecraft.client.Minecraft;
 | 
					import net.minecraft.client.Minecraft;
 | 
				
			||||||
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.WeakHashMap;
 | 
					import java.util.WeakHashMap;
 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap;
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
@@ -52,12 +52,18 @@ public final class TurtleUpgradeModellers {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess access, TurtleSide side) {
 | 
					    public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
 | 
				
			||||||
        @SuppressWarnings("unchecked")
 | 
					        @SuppressWarnings("unchecked")
 | 
				
			||||||
        var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
 | 
					        var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
 | 
				
			||||||
        return modeller.getModel(upgrade, access, side);
 | 
					        return modeller.getModel(upgrade, access, side);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
 | 
				
			||||||
 | 
					        @SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
					        var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
 | 
				
			||||||
 | 
					        return modeller.getModel(upgrade, data, side);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
 | 
					    private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
 | 
				
			||||||
        var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
 | 
					        var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
 | 
				
			||||||
        if (wrapper == null) return NULL_TURTLE_MODELLER;
 | 
					        if (wrapper == null) return NULL_TURTLE_MODELLER;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
 | 
				
			|||||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
					import dan200.computercraft.api.ComputerCraftAPI;
 | 
				
			||||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
 | 
					import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
 | 
					import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.core.util.Colour;
 | 
					import dan200.computercraft.core.util.Colour;
 | 
				
			||||||
import dan200.computercraft.shared.ModRegistry;
 | 
					import dan200.computercraft.shared.ModRegistry;
 | 
				
			||||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
					import dan200.computercraft.shared.common.IColouredItem;
 | 
				
			||||||
@@ -110,7 +111,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
				
			|||||||
            var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
 | 
					            var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
 | 
					            for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
 | 
				
			||||||
                var result = turtleItem.create(-1, null, -1, null, upgrade, -1, null);
 | 
					                var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
 | 
				
			||||||
                ShapedRecipeBuilder
 | 
					                ShapedRecipeBuilder
 | 
				
			||||||
                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
					                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
				
			||||||
                    .group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
 | 
					                    .group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
 | 
				
			||||||
@@ -146,7 +147,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
				
			|||||||
            var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
 | 
					            var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
 | 
					            for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
 | 
				
			||||||
                var result = pocket.create(-1, null, -1, upgrade);
 | 
					                var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
 | 
				
			||||||
                ShapedRecipeBuilder
 | 
					                ShapedRecipeBuilder
 | 
				
			||||||
                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
					                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
				
			||||||
                    .group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
 | 
					                    .group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package dan200.computercraft.impl;
 | 
				
			|||||||
import com.google.gson.*;
 | 
					import com.google.gson.*;
 | 
				
			||||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
					import dan200.computercraft.api.ComputerCraftAPI;
 | 
				
			||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
					import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
					import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
				
			||||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
					import dan200.computercraft.shared.platform.PlatformHelper;
 | 
				
			||||||
import net.minecraft.core.Registry;
 | 
					import net.minecraft.core.Registry;
 | 
				
			||||||
@@ -74,13 +75,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nullable
 | 
					    @Nullable
 | 
				
			||||||
    public T get(ItemStack stack) {
 | 
					    public UpgradeData<T> get(ItemStack stack) {
 | 
				
			||||||
        if (stack.isEmpty()) return null;
 | 
					        if (stack.isEmpty()) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (var wrapper : current.values()) {
 | 
					        for (var wrapper : current.values()) {
 | 
				
			||||||
            var craftingStack = wrapper.upgrade().getCraftingItem();
 | 
					            var craftingStack = wrapper.upgrade().getCraftingItem();
 | 
				
			||||||
            if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
 | 
					            if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
 | 
				
			||||||
                return wrapper.upgrade();
 | 
					                return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries;
 | 
				
			|||||||
import dan200.computercraft.api.media.IMedia;
 | 
					import dan200.computercraft.api.media.IMedia;
 | 
				
			||||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
					import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
					import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.core.util.Colour;
 | 
					import dan200.computercraft.core.util.Colour;
 | 
				
			||||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
					import dan200.computercraft.impl.PocketUpgrades;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
@@ -446,12 +447,12 @@ public final class ModRegistry {
 | 
				
			|||||||
    private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
 | 
					    private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
 | 
				
			||||||
        out.accept(turtle.create(-1, null, -1, null, null, 0, null));
 | 
					        out.accept(turtle.create(-1, null, -1, null, null, 0, null));
 | 
				
			||||||
        TurtleUpgrades.getVanillaUpgrades()
 | 
					        TurtleUpgrades.getVanillaUpgrades()
 | 
				
			||||||
            .map(x -> turtle.create(-1, null, -1, null, x, 0, null))
 | 
					            .map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null))
 | 
				
			||||||
            .forEach(out::accept);
 | 
					            .forEach(out::accept);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
 | 
					    private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
 | 
				
			||||||
        out.accept(pocket.create(-1, null, -1, null));
 | 
					        out.accept(pocket.create(-1, null, -1, null));
 | 
				
			||||||
        PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, x)).forEach(out::accept);
 | 
					        PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@
 | 
				
			|||||||
package dan200.computercraft.shared.integration;
 | 
					package dan200.computercraft.shared.integration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
					import dan200.computercraft.api.ComputerCraftAPI;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
					import dan200.computercraft.impl.PocketUpgrades;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.ModRegistry;
 | 
					import dan200.computercraft.shared.ModRegistry;
 | 
				
			||||||
@@ -56,14 +57,14 @@ public final class RecipeModHelpers {
 | 
				
			|||||||
        for (var turtleSupplier : TURTLES) {
 | 
					        for (var turtleSupplier : TURTLES) {
 | 
				
			||||||
            var turtle = turtleSupplier.get();
 | 
					            var turtle = turtleSupplier.get();
 | 
				
			||||||
            for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
 | 
					            for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
 | 
				
			||||||
                upgradeItems.add(turtle.create(-1, null, -1, null, upgrade, 0, null));
 | 
					                upgradeItems.add(turtle.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), 0, null));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (var pocketSupplier : POCKET_COMPUTERS) {
 | 
					        for (var pocketSupplier : POCKET_COMPUTERS) {
 | 
				
			||||||
            var pocket = pocketSupplier.get();
 | 
					            var pocket = pocketSupplier.get();
 | 
				
			||||||
            for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
 | 
					            for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
 | 
				
			||||||
                upgradeItems.add(pocket.create(-1, null, -1, upgrade));
 | 
					                upgradeItems.add(pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			|||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
					import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
					import dan200.computercraft.impl.PocketUpgrades;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
					import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
				
			||||||
@@ -111,20 +112,22 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (stack.getItem() instanceof TurtleItem item) {
 | 
					        if (stack.getItem() instanceof TurtleItem item) {
 | 
				
			||||||
            // Suggest possible upgrades which can be applied to this turtle
 | 
					            // Suggest possible upgrades which can be applied to this turtle
 | 
				
			||||||
            var left = item.getUpgrade(stack, TurtleSide.LEFT);
 | 
					            var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
 | 
				
			||||||
            var right = item.getUpgrade(stack, TurtleSide.RIGHT);
 | 
					            var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
 | 
				
			||||||
            if (left != null && right != null) return Collections.emptyList();
 | 
					            if (left != null && right != null) return Collections.emptyList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<T> recipes = new ArrayList<>();
 | 
					            List<T> recipes = new ArrayList<>();
 | 
				
			||||||
            var ingredient = Ingredient.of(stack);
 | 
					            var ingredient = Ingredient.of(stack);
 | 
				
			||||||
            for (var upgrade : turtleUpgrades) {
 | 
					            for (var upgrade : turtleUpgrades) {
 | 
				
			||||||
 | 
					                if (upgrade.turtle == null) throw new NullPointerException();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
 | 
					                // The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
 | 
				
			||||||
                if (left == null) {
 | 
					                if (left == null) {
 | 
				
			||||||
                    recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, upgrade.turtle, right)));
 | 
					                    recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, UpgradeData.ofDefault(upgrade.turtle), right)));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (right == null) {
 | 
					                if (right == null) {
 | 
				
			||||||
                    recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, upgrade.turtle)));
 | 
					                    recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, UpgradeData.ofDefault(upgrade.turtle))));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -137,7 +140,8 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
            List<T> recipes = new ArrayList<>();
 | 
					            List<T> recipes = new ArrayList<>();
 | 
				
			||||||
            var ingredient = Ingredient.of(stack);
 | 
					            var ingredient = Ingredient.of(stack);
 | 
				
			||||||
            for (var upgrade : pocketUpgrades) {
 | 
					            for (var upgrade : pocketUpgrades) {
 | 
				
			||||||
                recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, upgrade.pocket)));
 | 
					                if (upgrade.pocket == null) throw new NullPointerException();
 | 
				
			||||||
 | 
					                recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, UpgradeData.ofDefault(upgrade.pocket))));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Collections.unmodifiableList(recipes);
 | 
					            return Collections.unmodifiableList(recipes);
 | 
				
			||||||
@@ -180,21 +184,21 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
        if (stack.getItem() instanceof TurtleItem item) {
 | 
					        if (stack.getItem() instanceof TurtleItem item) {
 | 
				
			||||||
            List<T> recipes = new ArrayList<>(0);
 | 
					            List<T> recipes = new ArrayList<>(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var left = item.getUpgrade(stack, TurtleSide.LEFT);
 | 
					            var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
 | 
				
			||||||
            var right = item.getUpgrade(stack, TurtleSide.RIGHT);
 | 
					            var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
 | 
					            // The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
 | 
				
			||||||
            if (left != null) {
 | 
					            if (left != null) {
 | 
				
			||||||
                recipes.add(turtle(
 | 
					                recipes.add(turtle(
 | 
				
			||||||
                    Ingredient.of(turtleWith(stack, null, right)),
 | 
					                    Ingredient.of(turtleWith(stack, null, right)),
 | 
				
			||||||
                    Ingredient.of(left.getCraftingItem()),
 | 
					                    Ingredient.of(left.getUpgradeItem()),
 | 
				
			||||||
                    stack
 | 
					                    stack
 | 
				
			||||||
                ));
 | 
					                ));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (right != null) {
 | 
					            if (right != null) {
 | 
				
			||||||
                recipes.add(turtle(
 | 
					                recipes.add(turtle(
 | 
				
			||||||
                    Ingredient.of(right.getCraftingItem()),
 | 
					                    Ingredient.of(right.getUpgradeItem()),
 | 
				
			||||||
                    Ingredient.of(turtleWith(stack, left, null)),
 | 
					                    Ingredient.of(turtleWith(stack, left, null)),
 | 
				
			||||||
                    stack
 | 
					                    stack
 | 
				
			||||||
                ));
 | 
					                ));
 | 
				
			||||||
@@ -204,9 +208,9 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
        } else if (stack.getItem() instanceof PocketComputerItem) {
 | 
					        } else if (stack.getItem() instanceof PocketComputerItem) {
 | 
				
			||||||
            List<T> recipes = new ArrayList<>(0);
 | 
					            List<T> recipes = new ArrayList<>(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var back = PocketComputerItem.getUpgrade(stack);
 | 
					            var back = PocketComputerItem.getUpgradeWithData(stack);
 | 
				
			||||||
            if (back != null) {
 | 
					            if (back != null) {
 | 
				
			||||||
                recipes.add(pocket(Ingredient.of(back.getCraftingItem()), Ingredient.of(pocketWith(stack, null)), stack));
 | 
					                recipes.add(pocket(Ingredient.of(back.getUpgradeItem()), Ingredient.of(pocketWith(stack, null)), stack));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Collections.unmodifiableList(recipes);
 | 
					            return Collections.unmodifiableList(recipes);
 | 
				
			||||||
@@ -215,7 +219,7 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable ITurtleUpgrade right) {
 | 
					    private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) {
 | 
				
			||||||
        var item = (TurtleItem) stack.getItem();
 | 
					        var item = (TurtleItem) stack.getItem();
 | 
				
			||||||
        return item.create(
 | 
					        return item.create(
 | 
				
			||||||
            item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
 | 
					            item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
 | 
				
			||||||
@@ -223,7 +227,7 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static ItemStack pocketWith(ItemStack stack, @Nullable IPocketUpgrade back) {
 | 
					    private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) {
 | 
				
			||||||
        var item = (PocketComputerItem) stack.getItem();
 | 
					        var item = (PocketComputerItem) stack.getItem();
 | 
				
			||||||
        return item.create(
 | 
					        return item.create(
 | 
				
			||||||
            item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
 | 
					            item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
 | 
				
			||||||
@@ -272,7 +276,7 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
                    recipes.add(turtle(
 | 
					                    recipes.add(turtle(
 | 
				
			||||||
                        ingredient, // Right upgrade, recipe on left
 | 
					                        ingredient, // Right upgrade, recipe on left
 | 
				
			||||||
                        Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
 | 
					                        Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
 | 
				
			||||||
                        turtleItem.create(-1, null, -1, null, turtle, 0, null)
 | 
					                        turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(turtle), 0, null)
 | 
				
			||||||
                    ));
 | 
					                    ));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -283,7 +287,7 @@ public class UpgradeRecipeGenerator<T> {
 | 
				
			|||||||
                    recipes.add(pocket(
 | 
					                    recipes.add(pocket(
 | 
				
			||||||
                        ingredient,
 | 
					                        ingredient,
 | 
				
			||||||
                        Ingredient.of(pocketItem.create(-1, null, -1, null)),
 | 
					                        Ingredient.of(pocketItem.create(-1, null, -1, null)),
 | 
				
			||||||
                        pocketItem.create(-1, null, -1, pocket)
 | 
					                        pocketItem.create(-1, null, -1, UpgradeData.ofDefault(pocket))
 | 
				
			||||||
                    ));
 | 
					                    ));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,6 +68,13 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
 | 
				
			|||||||
        return (PlatformHelper) dan200.computercraft.impl.PlatformHelper.get();
 | 
					        return (PlatformHelper) dan200.computercraft.impl.PlatformHelper.get();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if we're running in a development environment.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return If we're running in a development environment.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    boolean isDevelopmentEnvironment();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Create a new config builder.
 | 
					     * Create a new config builder.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.apis;
 | 
				
			|||||||
import dan200.computercraft.api.lua.ILuaAPI;
 | 
					import dan200.computercraft.api.lua.ILuaAPI;
 | 
				
			||||||
import dan200.computercraft.api.lua.LuaFunction;
 | 
					import dan200.computercraft.api.lua.LuaFunction;
 | 
				
			||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
					import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
					import dan200.computercraft.impl.PocketUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
 | 
					import dan200.computercraft.shared.pocket.core.PocketServerComputer;
 | 
				
			||||||
import net.minecraft.core.NonNullList;
 | 
					import net.minecraft.core.NonNullList;
 | 
				
			||||||
@@ -14,6 +15,7 @@ import net.minecraft.world.entity.player.Player;
 | 
				
			|||||||
import net.minecraft.world.item.ItemStack;
 | 
					import net.minecraft.world.item.ItemStack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.Nullable;
 | 
					import javax.annotation.Nullable;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Control the current pocket computer, adding or removing upgrades.
 | 
					 * Control the current pocket computer, adding or removing upgrades.
 | 
				
			||||||
@@ -68,7 +70,7 @@ public class PocketAPI implements ILuaAPI {
 | 
				
			|||||||
        if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
 | 
					        if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Remove the current upgrade
 | 
					        // Remove the current upgrade
 | 
				
			||||||
        if (previousUpgrade != null) storeItem(player, previousUpgrade.getCraftingItem().copy());
 | 
					        if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Set the new upgrade
 | 
					        // Set the new upgrade
 | 
				
			||||||
        computer.setUpgrade(newUpgrade);
 | 
					        computer.setUpgrade(newUpgrade);
 | 
				
			||||||
@@ -93,7 +95,7 @@ public class PocketAPI implements ILuaAPI {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        computer.setUpgrade(null);
 | 
					        computer.setUpgrade(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        storeItem(player, previousUpgrade.getCraftingItem().copy());
 | 
					        storeItem(player, previousUpgrade.getUpgradeItem());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Object[]{ true };
 | 
					        return new Object[]{ true };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -105,13 +107,13 @@ public class PocketAPI implements ILuaAPI {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static @Nullable IPocketUpgrade findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable IPocketUpgrade previous) {
 | 
					    private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) {
 | 
				
			||||||
        for (var i = 0; i < inv.size(); i++) {
 | 
					        for (var i = 0; i < inv.size(); i++) {
 | 
				
			||||||
            var invStack = inv.get((i + start) % inv.size());
 | 
					            var invStack = inv.get((i + start) % inv.size());
 | 
				
			||||||
            if (!invStack.isEmpty()) {
 | 
					            if (!invStack.isEmpty()) {
 | 
				
			||||||
                var newUpgrade = PocketUpgrades.instance().get(invStack);
 | 
					                var newUpgrade = PocketUpgrades.instance().get(invStack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (newUpgrade != null && newUpgrade != previous) {
 | 
					                if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) {
 | 
				
			||||||
                    // Consume an item from this stack and exit the loop
 | 
					                    // Consume an item from this stack and exit the loop
 | 
				
			||||||
                    invStack = invStack.copy();
 | 
					                    invStack = invStack.copy();
 | 
				
			||||||
                    invStack.shrink(1);
 | 
					                    invStack.shrink(1);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.core;
 | 
				
			|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
					import dan200.computercraft.api.peripheral.IPeripheral;
 | 
				
			||||||
import dan200.computercraft.api.pocket.IPocketAccess;
 | 
					import dan200.computercraft.api.pocket.IPocketAccess;
 | 
				
			||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
					import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.core.computer.ComputerSide;
 | 
					import dan200.computercraft.core.computer.ComputerSide;
 | 
				
			||||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
					import dan200.computercraft.shared.common.IColouredItem;
 | 
				
			||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
					import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
				
			||||||
@@ -109,8 +110,8 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
 | 
				
			|||||||
        return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
 | 
					        return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public @Nullable IPocketUpgrade getUpgrade() {
 | 
					    public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
 | 
				
			||||||
        return upgrade;
 | 
					        return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -120,13 +121,11 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param upgrade The new upgrade to set it to, may be {@code null}.
 | 
					     * @param upgrade The new upgrade to set it to, may be {@code null}.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public void setUpgrade(@Nullable IPocketUpgrade upgrade) {
 | 
					    public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
 | 
				
			||||||
        if (this.upgrade == upgrade) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        synchronized (this) {
 | 
					        synchronized (this) {
 | 
				
			||||||
            PocketComputerItem.setUpgrade(stack, upgrade);
 | 
					            PocketComputerItem.setUpgrade(stack, upgrade);
 | 
				
			||||||
            updateUpgradeNBTData();
 | 
					            updateUpgradeNBTData();
 | 
				
			||||||
            this.upgrade = upgrade;
 | 
					            this.upgrade = upgrade == null ? null : upgrade.upgrade();
 | 
				
			||||||
            invalidatePeripheral();
 | 
					            invalidatePeripheral();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
 | 
				
			|||||||
import dan200.computercraft.api.filesystem.Mount;
 | 
					import dan200.computercraft.api.filesystem.Mount;
 | 
				
			||||||
import dan200.computercraft.api.media.IMedia;
 | 
					import dan200.computercraft.api.media.IMedia;
 | 
				
			||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
					import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.core.computer.ComputerSide;
 | 
					import dan200.computercraft.core.computer.ComputerSide;
 | 
				
			||||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
					import dan200.computercraft.impl.PocketUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.ModRegistry;
 | 
					import dan200.computercraft.shared.ModRegistry;
 | 
				
			||||||
@@ -23,6 +24,7 @@ import dan200.computercraft.shared.pocket.apis.PocketAPI;
 | 
				
			|||||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
 | 
					import dan200.computercraft.shared.pocket.core.PocketServerComputer;
 | 
				
			||||||
import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider;
 | 
					import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider;
 | 
				
			||||||
import dan200.computercraft.shared.util.IDAssigner;
 | 
					import dan200.computercraft.shared.util.IDAssigner;
 | 
				
			||||||
 | 
					import dan200.computercraft.shared.util.NBTUtil;
 | 
				
			||||||
import net.minecraft.ChatFormatting;
 | 
					import net.minecraft.ChatFormatting;
 | 
				
			||||||
import net.minecraft.nbt.CompoundTag;
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
import net.minecraft.network.chat.Component;
 | 
					import net.minecraft.network.chat.Component;
 | 
				
			||||||
@@ -58,7 +60,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
				
			|||||||
        this.family = family;
 | 
					        this.family = family;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable IPocketUpgrade upgrade) {
 | 
					    public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
 | 
				
			||||||
        return switch (family) {
 | 
					        return switch (family) {
 | 
				
			||||||
            case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
 | 
					            case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
 | 
				
			||||||
            case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
 | 
					            case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
 | 
				
			||||||
@@ -66,11 +68,14 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ItemStack create(int id, @Nullable String label, int colour, @Nullable IPocketUpgrade upgrade) {
 | 
					    public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
 | 
				
			||||||
        var result = new ItemStack(this);
 | 
					        var result = new ItemStack(this);
 | 
				
			||||||
        if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
 | 
					        if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
 | 
				
			||||||
        if (label != null) result.setHoverName(Component.literal(label));
 | 
					        if (label != null) result.setHoverName(Component.literal(label));
 | 
				
			||||||
        if (upgrade != null) result.getOrCreateTag().putString(NBT_UPGRADE, upgrade.getUpgradeID().toString());
 | 
					        if (upgrade != null) {
 | 
				
			||||||
 | 
					            result.getOrCreateTag().putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
 | 
				
			||||||
 | 
					            if (!upgrade.data().isEmpty()) result.getOrCreateTag().put(NBT_UPGRADE_INFO, upgrade.data().copy());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (colour != -1) result.getOrCreateTag().putInt(NBT_COLOUR, colour);
 | 
					        if (colour != -1) result.getOrCreateTag().putInt(NBT_COLOUR, colour);
 | 
				
			||||||
        return result;
 | 
					        return result;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -207,7 +212,9 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
				
			|||||||
            setInstanceID(stack, computer.register());
 | 
					            setInstanceID(stack, computer.register());
 | 
				
			||||||
            setSessionID(stack, registry.getSessionID());
 | 
					            setSessionID(stack, registry.getSessionID());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            computer.updateValues(entity, stack, getUpgrade(stack));
 | 
					            var upgrade = getUpgrade(stack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            computer.updateValues(entity, stack, upgrade);
 | 
				
			||||||
            computer.addAPI(new PocketAPI(computer));
 | 
					            computer.addAPI(new PocketAPI(computer));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Only turn on when initially creating the computer, rather than each tick.
 | 
					            // Only turn on when initially creating the computer, rather than each tick.
 | 
				
			||||||
@@ -244,7 +251,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
				
			|||||||
    public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
 | 
					    public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
 | 
				
			||||||
        return create(
 | 
					        return create(
 | 
				
			||||||
            getComputerID(stack), getLabel(stack), getColour(stack),
 | 
					            getComputerID(stack), getLabel(stack), getColour(stack),
 | 
				
			||||||
            family, getUpgrade(stack)
 | 
					            family, getUpgradeWithData(stack)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -294,20 +301,27 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static @Nullable IPocketUpgrade getUpgrade(ItemStack stack) {
 | 
					    public static @Nullable IPocketUpgrade getUpgrade(ItemStack stack) {
 | 
				
			||||||
        var compound = stack.getTag();
 | 
					        var compound = stack.getTag();
 | 
				
			||||||
        return compound != null && compound.contains(NBT_UPGRADE)
 | 
					        if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
 | 
				
			||||||
            ? PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE)) : null;
 | 
					        return PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void setUpgrade(ItemStack stack, @Nullable IPocketUpgrade upgrade) {
 | 
					    public static @Nullable UpgradeData<IPocketUpgrade> getUpgradeWithData(ItemStack stack) {
 | 
				
			||||||
 | 
					        var compound = stack.getTag();
 | 
				
			||||||
 | 
					        if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
 | 
				
			||||||
 | 
					        var upgrade = PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
 | 
				
			||||||
 | 
					        return upgrade == null ? null : UpgradeData.of(upgrade, NBTUtil.getCompoundOrEmpty(compound, NBT_UPGRADE_INFO));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static void setUpgrade(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
 | 
				
			||||||
        var compound = stack.getOrCreateTag();
 | 
					        var compound = stack.getOrCreateTag();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (upgrade == null) {
 | 
					        if (upgrade == null) {
 | 
				
			||||||
            compound.remove(NBT_UPGRADE);
 | 
					            compound.remove(NBT_UPGRADE);
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            compound.putString(NBT_UPGRADE, upgrade.getUpgradeID().toString());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            compound.remove(NBT_UPGRADE_INFO);
 | 
					            compound.remove(NBT_UPGRADE_INFO);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            compound.putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
 | 
				
			||||||
 | 
					            compound.put(NBT_UPGRADE_INFO, upgrade.data().copy());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static CompoundTag getUpgradeInfo(ItemStack stack) {
 | 
					    public static CompoundTag getUpgradeInfo(ItemStack stack) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@
 | 
				
			|||||||
package dan200.computercraft.shared.pocket.recipes;
 | 
					package dan200.computercraft.shared.pocket.recipes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
					import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
					import dan200.computercraft.impl.PocketUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.ModRegistry;
 | 
					import dan200.computercraft.shared.ModRegistry;
 | 
				
			||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
					import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
				
			||||||
@@ -62,7 +63,7 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
 | 
				
			|||||||
        if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY;
 | 
					        if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check for upgrades around the item
 | 
					        // Check for upgrades around the item
 | 
				
			||||||
        IPocketUpgrade upgrade = null;
 | 
					        UpgradeData<IPocketUpgrade> upgrade = null;
 | 
				
			||||||
        for (var y = 0; y < inventory.getHeight(); y++) {
 | 
					        for (var y = 0; y < inventory.getHeight(); y++) {
 | 
				
			||||||
            for (var x = 0; x < inventory.getWidth(); x++) {
 | 
					            for (var x = 0; x < inventory.getWidth(); x++) {
 | 
				
			||||||
                var item = inventory.getItem(x + y * inventory.getWidth());
 | 
					                var item = inventory.getItem(x + y * inventory.getWidth());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,9 @@
 | 
				
			|||||||
package dan200.computercraft.shared.turtle.blocks;
 | 
					package dan200.computercraft.shared.turtle.blocks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dan200.computercraft.annotations.ForgeOverride;
 | 
					import dan200.computercraft.annotations.ForgeOverride;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
 | 
					import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
 | 
				
			||||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
 | 
					import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
 | 
				
			||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
					import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
				
			||||||
@@ -128,7 +130,7 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
 | 
				
			|||||||
            if (stack.getItem() instanceof TurtleItem item) {
 | 
					            if (stack.getItem() instanceof TurtleItem item) {
 | 
				
			||||||
                // Set Upgrades
 | 
					                // Set Upgrades
 | 
				
			||||||
                for (var side : TurtleSide.values()) {
 | 
					                for (var side : TurtleSide.values()) {
 | 
				
			||||||
                    turtle.getAccess().setUpgrade(side, item.getUpgrade(stack, side));
 | 
					                    turtle.getAccess().setUpgradeWithData(side, item.getUpgradeWithData(stack, side));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                turtle.getAccess().setFuelLevel(item.getFuelLevel(stack));
 | 
					                turtle.getAccess().setFuelLevel(item.getFuelLevel(stack));
 | 
				
			||||||
@@ -161,11 +163,16 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
 | 
				
			|||||||
        var access = turtle.getAccess();
 | 
					        var access = turtle.getAccess();
 | 
				
			||||||
        return TurtleItem.create(
 | 
					        return TurtleItem.create(
 | 
				
			||||||
            turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
 | 
					            turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
 | 
				
			||||||
            access.getUpgrade(TurtleSide.LEFT), access.getUpgrade(TurtleSide.RIGHT),
 | 
					            withPersistedData(access.getUpgradeWithData(TurtleSide.LEFT)),
 | 
				
			||||||
 | 
					            withPersistedData(access.getUpgradeWithData(TurtleSide.RIGHT)),
 | 
				
			||||||
            access.getFuelLevel(), turtle.getOverlay()
 | 
					            access.getFuelLevel(), turtle.getOverlay()
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static @Nullable UpgradeData<ITurtleUpgrade> withPersistedData(@Nullable UpgradeData<ITurtleUpgrade> upgrade) {
 | 
				
			||||||
 | 
					        return upgrade == null ? null : UpgradeData.of(upgrade.upgrade(), upgrade.upgrade().getPersistedData(upgrade.data()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    @Nullable
 | 
					    @Nullable
 | 
				
			||||||
    public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
 | 
					    public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			|||||||
import dan200.computercraft.api.turtle.TurtleAnimation;
 | 
					import dan200.computercraft.api.turtle.TurtleAnimation;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleCommand;
 | 
					import dan200.computercraft.api.turtle.TurtleCommand;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.core.computer.ComputerSide;
 | 
					import dan200.computercraft.core.computer.ComputerSide;
 | 
				
			||||||
import dan200.computercraft.core.util.Colour;
 | 
					import dan200.computercraft.core.util.Colour;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
@@ -141,17 +142,16 @@ public class TurtleBrain implements TurtleAccessInternal {
 | 
				
			|||||||
        overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null;
 | 
					        overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Read upgrades
 | 
					        // Read upgrades
 | 
				
			||||||
        setUpgradeDirect(TurtleSide.LEFT, nbt.contains(NBT_LEFT_UPGRADE) ? TurtleUpgrades.instance().get(nbt.getString(NBT_LEFT_UPGRADE)) : null);
 | 
					        setUpgradeDirect(TurtleSide.LEFT, readUpgrade(nbt, NBT_LEFT_UPGRADE, NBT_LEFT_UPGRADE_DATA));
 | 
				
			||||||
        setUpgradeDirect(TurtleSide.RIGHT, nbt.contains(NBT_RIGHT_UPGRADE) ? TurtleUpgrades.instance().get(nbt.getString(NBT_RIGHT_UPGRADE)) : null);
 | 
					        setUpgradeDirect(TurtleSide.RIGHT, readUpgrade(nbt, NBT_RIGHT_UPGRADE, NBT_RIGHT_UPGRADE_DATA));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // NBT
 | 
					    private @Nullable UpgradeData<ITurtleUpgrade> readUpgrade(CompoundTag tag, String upgradeKey, String dataKey) {
 | 
				
			||||||
        upgradeNBTData.clear();
 | 
					        if (!tag.contains(upgradeKey)) return null;
 | 
				
			||||||
        if (nbt.contains(NBT_LEFT_UPGRADE_DATA)) {
 | 
					        var upgrade = TurtleUpgrades.instance().get(tag.getString(upgradeKey));
 | 
				
			||||||
            upgradeNBTData.put(TurtleSide.LEFT, nbt.getCompound(NBT_LEFT_UPGRADE_DATA).copy());
 | 
					        if (upgrade == null) return null;
 | 
				
			||||||
        }
 | 
					
 | 
				
			||||||
        if (nbt.contains(NBT_RIGHT_UPGRADE_DATA)) {
 | 
					        return UpgradeData.of(upgrade, tag.getCompound(dataKey));
 | 
				
			||||||
            upgradeNBTData.put(TurtleSide.RIGHT, nbt.getCompound(NBT_RIGHT_UPGRADE_DATA).copy());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void writeCommon(CompoundTag nbt) {
 | 
					    private void writeCommon(CompoundTag nbt) {
 | 
				
			||||||
@@ -516,7 +516,7 @@ public class TurtleBrain implements TurtleAccessInternal {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
 | 
					    public void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
 | 
				
			||||||
        if (!setUpgradeDirect(side, upgrade) || owner.getLevel() == null) return;
 | 
					        if (!setUpgradeDirect(side, upgrade) || owner.getLevel() == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
 | 
					        // This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
 | 
				
			||||||
@@ -529,19 +529,18 @@ public class TurtleBrain implements TurtleAccessInternal {
 | 
				
			|||||||
        owner.updateInputsImmediately();
 | 
					        owner.updateInputsImmediately();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean setUpgradeDirect(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
 | 
					    private boolean setUpgradeDirect(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
 | 
				
			||||||
        // Remove old upgrade
 | 
					        // Remove old upgrade
 | 
				
			||||||
        if (upgrades.containsKey(side)) {
 | 
					        var oldUpgrade = upgrades.remove(side);
 | 
				
			||||||
            if (upgrades.get(side) == upgrade) return false;
 | 
					        if (oldUpgrade == null && upgrade == null) return false;
 | 
				
			||||||
            upgrades.remove(side);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            if (upgrade == null) return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        upgradeNBTData.remove(side);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Set new upgrade
 | 
					        // Set new upgrade
 | 
				
			||||||
        if (upgrade != null) upgrades.put(side, upgrade);
 | 
					        if (upgrade == null) {
 | 
				
			||||||
 | 
					            upgradeNBTData.remove(side);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            upgrades.put(side, upgrade.upgrade());
 | 
				
			||||||
 | 
					            upgradeNBTData.put(side, upgrade.data().copy());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Notify clients and create peripherals
 | 
					        // Notify clients and create peripherals
 | 
				
			||||||
        if (owner.getLevel() != null && !owner.getLevel().isClientSide) {
 | 
					        if (owner.getLevel() != null && !owner.getLevel().isClientSide) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@
 | 
				
			|||||||
package dan200.computercraft.shared.turtle.core;
 | 
					package dan200.computercraft.shared.turtle.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dan200.computercraft.api.turtle.*;
 | 
					import dan200.computercraft.api.turtle.*;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.turtle.TurtleUtil;
 | 
					import dan200.computercraft.shared.turtle.TurtleUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,10 +19,10 @@ public class TurtleEquipCommand implements TurtleCommand {
 | 
				
			|||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public TurtleCommandResult execute(ITurtleAccess turtle) {
 | 
					    public TurtleCommandResult execute(ITurtleAccess turtle) {
 | 
				
			||||||
        // Determine the upgrade to replace
 | 
					        // Determine the upgrade to replace
 | 
				
			||||||
        var oldUpgrade = turtle.getUpgrade(side);
 | 
					        var oldUpgrade = turtle.getUpgradeWithData(side);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Determine the upgrade to equipLeft
 | 
					        // Determine the upgrade to equipLeft
 | 
				
			||||||
        ITurtleUpgrade newUpgrade;
 | 
					        UpgradeData<ITurtleUpgrade> newUpgrade;
 | 
				
			||||||
        var selectedStack = turtle.getInventory().getItem(turtle.getSelectedSlot());
 | 
					        var selectedStack = turtle.getInventory().getItem(turtle.getSelectedSlot());
 | 
				
			||||||
        if (!selectedStack.isEmpty()) {
 | 
					        if (!selectedStack.isEmpty()) {
 | 
				
			||||||
            newUpgrade = TurtleUpgrades.instance().get(selectedStack);
 | 
					            newUpgrade = TurtleUpgrades.instance().get(selectedStack);
 | 
				
			||||||
@@ -32,8 +33,8 @@ public class TurtleEquipCommand implements TurtleCommand {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Do the swapping:
 | 
					        // Do the swapping:
 | 
				
			||||||
        if (newUpgrade != null) turtle.getInventory().removeItem(turtle.getSelectedSlot(), 1);
 | 
					        if (newUpgrade != null) turtle.getInventory().removeItem(turtle.getSelectedSlot(), 1);
 | 
				
			||||||
        if (oldUpgrade != null) TurtleUtil.storeItemOrDrop(turtle, oldUpgrade.getCraftingItem().copy());
 | 
					        if (oldUpgrade != null) TurtleUtil.storeItemOrDrop(turtle, oldUpgrade.getUpgradeItem());
 | 
				
			||||||
        turtle.setUpgrade(side, newUpgrade);
 | 
					        turtle.setUpgradeWithData(side, newUpgrade);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Animate
 | 
					        // Animate
 | 
				
			||||||
        if (newUpgrade != null || oldUpgrade != null) {
 | 
					        if (newUpgrade != null || oldUpgrade != null) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.turtle.inventory;
 | 
				
			|||||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
					import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
				
			||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import net.minecraft.core.NonNullList;
 | 
					import net.minecraft.core.NonNullList;
 | 
				
			||||||
import net.minecraft.world.Container;
 | 
					import net.minecraft.world.Container;
 | 
				
			||||||
@@ -27,7 +28,7 @@ class UpgradeContainer implements Container {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private final ITurtleAccess turtle;
 | 
					    private final ITurtleAccess turtle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final List<ITurtleUpgrade> lastUpgrade = Arrays.asList(null, null);
 | 
					    private final List<UpgradeData<ITurtleUpgrade>> lastUpgrade = Arrays.asList(null, null);
 | 
				
			||||||
    private final NonNullList<ItemStack> lastStack = NonNullList.withSize(2, ItemStack.EMPTY);
 | 
					    private final NonNullList<ItemStack> lastStack = NonNullList.withSize(2, ItemStack.EMPTY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    UpgradeContainer(ITurtleAccess turtle) {
 | 
					    UpgradeContainer(ITurtleAccess turtle) {
 | 
				
			||||||
@@ -44,22 +45,25 @@ class UpgradeContainer implements Container {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ItemStack getItem(int slot) {
 | 
					    public ItemStack getItem(int slot) {
 | 
				
			||||||
        var upgrade = turtle.getUpgrade(getSide(slot));
 | 
					        var side = getSide(slot);
 | 
				
			||||||
 | 
					        var upgrade = turtle.getUpgrade(side);
 | 
				
			||||||
 | 
					        if (upgrade == null) return ItemStack.EMPTY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // We don't want to return getCraftingItem directly here, as consumers may mutate the stack (they shouldn't!,
 | 
					        // We don't want to return getCraftingItem directly here, as consumers may mutate the stack (they shouldn't!,
 | 
				
			||||||
        // but if they do it's a pain to track down). To avoid recreating the stack each tick, we maintain a simple
 | 
					        // but if they do it's a pain to track down). To avoid recreating the stack each tick, we maintain a simple
 | 
				
			||||||
        // cache.
 | 
					        // cache. We use an inlined getUpgradeData here to avoid the additional defensive copy.
 | 
				
			||||||
        if (upgrade == lastUpgrade.get(slot)) return lastStack.get(slot);
 | 
					        var upgradeData = UpgradeData.of(upgrade, turtle.getUpgradeNBTData(side));
 | 
				
			||||||
 | 
					        if (upgradeData.equals(lastUpgrade.get(slot))) return lastStack.get(slot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getCraftingItem().copy();
 | 
					        var stack = upgradeData.getUpgradeItem();
 | 
				
			||||||
        lastUpgrade.set(slot, upgrade);
 | 
					        lastUpgrade.set(slot, upgradeData.copy());
 | 
				
			||||||
        lastStack.set(slot, stack);
 | 
					        lastStack.set(slot, stack);
 | 
				
			||||||
        return stack;
 | 
					        return stack;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void setItem(int slot, ItemStack itemStack) {
 | 
					    public void setItem(int slot, ItemStack itemStack) {
 | 
				
			||||||
        turtle.setUpgrade(getSide(slot), TurtleUpgrades.instance().get(itemStack));
 | 
					        turtle.setUpgradeWithData(getSide(slot), TurtleUpgrades.instance().get(itemStack));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,14 @@ import dan200.computercraft.annotations.ForgeOverride;
 | 
				
			|||||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
					import dan200.computercraft.api.ComputerCraftAPI;
 | 
				
			||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.ModRegistry;
 | 
					import dan200.computercraft.shared.ModRegistry;
 | 
				
			||||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
					import dan200.computercraft.shared.common.IColouredItem;
 | 
				
			||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
					import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
				
			||||||
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
 | 
					import dan200.computercraft.shared.computer.items.AbstractComputerItem;
 | 
				
			||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
 | 
					import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
 | 
				
			||||||
 | 
					import dan200.computercraft.shared.util.NBTUtil;
 | 
				
			||||||
import net.minecraft.core.cauldron.CauldronInteraction;
 | 
					import net.minecraft.core.cauldron.CauldronInteraction;
 | 
				
			||||||
import net.minecraft.network.chat.Component;
 | 
					import net.minecraft.network.chat.Component;
 | 
				
			||||||
import net.minecraft.resources.ResourceLocation;
 | 
					import net.minecraft.resources.ResourceLocation;
 | 
				
			||||||
@@ -32,7 +34,7 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static ItemStack create(
 | 
					    public static ItemStack create(
 | 
				
			||||||
        int id, @Nullable String label, int colour, ComputerFamily family,
 | 
					        int id, @Nullable String label, int colour, ComputerFamily family,
 | 
				
			||||||
        @Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade,
 | 
					        @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
 | 
				
			||||||
        int fuelLevel, @Nullable ResourceLocation overlay
 | 
					        int fuelLevel, @Nullable ResourceLocation overlay
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        return switch (family) {
 | 
					        return switch (family) {
 | 
				
			||||||
@@ -46,7 +48,7 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public ItemStack create(
 | 
					    public ItemStack create(
 | 
				
			||||||
        int id, @Nullable String label, int colour,
 | 
					        int id, @Nullable String label, int colour,
 | 
				
			||||||
        @Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade,
 | 
					        @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
 | 
				
			||||||
        int fuelLevel, @Nullable ResourceLocation overlay
 | 
					        int fuelLevel, @Nullable ResourceLocation overlay
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        // Build the stack
 | 
					        // Build the stack
 | 
				
			||||||
@@ -58,11 +60,15 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
				
			|||||||
        if (overlay != null) stack.getOrCreateTag().putString(NBT_OVERLAY, overlay.toString());
 | 
					        if (overlay != null) stack.getOrCreateTag().putString(NBT_OVERLAY, overlay.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (leftUpgrade != null) {
 | 
					        if (leftUpgrade != null) {
 | 
				
			||||||
            stack.getOrCreateTag().putString(NBT_LEFT_UPGRADE, leftUpgrade.getUpgradeID().toString());
 | 
					            var tag = stack.getOrCreateTag();
 | 
				
			||||||
 | 
					            tag.putString(NBT_LEFT_UPGRADE, leftUpgrade.upgrade().getUpgradeID().toString());
 | 
				
			||||||
 | 
					            if (!leftUpgrade.data().isEmpty()) tag.put(NBT_LEFT_UPGRADE_DATA, leftUpgrade.data().copy());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (rightUpgrade != null) {
 | 
					        if (rightUpgrade != null) {
 | 
				
			||||||
            stack.getOrCreateTag().putString(NBT_RIGHT_UPGRADE, rightUpgrade.getUpgradeID().toString());
 | 
					            var tag = stack.getOrCreateTag();
 | 
				
			||||||
 | 
					            tag.putString(NBT_RIGHT_UPGRADE, rightUpgrade.upgrade().getUpgradeID().toString());
 | 
				
			||||||
 | 
					            if (!rightUpgrade.data().isEmpty()) tag.put(NBT_RIGHT_UPGRADE_DATA, rightUpgrade.data().copy());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return stack;
 | 
					        return stack;
 | 
				
			||||||
@@ -117,7 +123,7 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
				
			|||||||
        return create(
 | 
					        return create(
 | 
				
			||||||
            getComputerID(stack), getLabel(stack),
 | 
					            getComputerID(stack), getLabel(stack),
 | 
				
			||||||
            getColour(stack), family,
 | 
					            getColour(stack), family,
 | 
				
			||||||
            getUpgrade(stack, TurtleSide.LEFT), getUpgrade(stack, TurtleSide.RIGHT),
 | 
					            getUpgradeWithData(stack, TurtleSide.LEFT), getUpgradeWithData(stack, TurtleSide.RIGHT),
 | 
				
			||||||
            getFuelLevel(stack), getOverlay(stack)
 | 
					            getFuelLevel(stack), getOverlay(stack)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -127,7 +133,20 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
				
			|||||||
        if (tag == null) return null;
 | 
					        if (tag == null) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
 | 
					        var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
 | 
				
			||||||
        return tag.contains(key) ? TurtleUpgrades.instance().get(tag.getString(key)) : null;
 | 
					        if (!tag.contains(key)) return null;
 | 
				
			||||||
 | 
					        return TurtleUpgrades.instance().get(tag.getString(key));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(ItemStack stack, TurtleSide side) {
 | 
				
			||||||
 | 
					        var tag = stack.getTag();
 | 
				
			||||||
 | 
					        if (tag == null) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
 | 
				
			||||||
 | 
					        if (!tag.contains(key)) return null;
 | 
				
			||||||
 | 
					        var upgrade = TurtleUpgrades.instance().get(tag.getString(key));
 | 
				
			||||||
 | 
					        if (upgrade == null) return null;
 | 
				
			||||||
 | 
					        var dataKey = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE_DATA : NBT_RIGHT_UPGRADE_DATA;
 | 
				
			||||||
 | 
					        return UpgradeData.of(upgrade, NBTUtil.getCompoundOrEmpty(tag, dataKey));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public @Nullable ResourceLocation getOverlay(ItemStack stack) {
 | 
					    public @Nullable ResourceLocation getOverlay(ItemStack stack) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,8 +38,8 @@ public class TurtleOverlayRecipe extends ShapelessRecipe {
 | 
				
			|||||||
            turtle.getComputerID(stack),
 | 
					            turtle.getComputerID(stack),
 | 
				
			||||||
            turtle.getLabel(stack),
 | 
					            turtle.getLabel(stack),
 | 
				
			||||||
            turtle.getColour(stack),
 | 
					            turtle.getColour(stack),
 | 
				
			||||||
            turtle.getUpgrade(stack, TurtleSide.LEFT),
 | 
					            turtle.getUpgradeWithData(stack, TurtleSide.LEFT),
 | 
				
			||||||
            turtle.getUpgrade(stack, TurtleSide.RIGHT),
 | 
					            turtle.getUpgradeWithData(stack, TurtleSide.RIGHT),
 | 
				
			||||||
            turtle.getFuelLevel(stack),
 | 
					            turtle.getFuelLevel(stack),
 | 
				
			||||||
            overlay
 | 
					            overlay
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package dan200.computercraft.shared.turtle.recipes;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
					import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
				
			||||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
					import dan200.computercraft.api.turtle.TurtleSide;
 | 
				
			||||||
 | 
					import dan200.computercraft.api.upgrades.UpgradeData;
 | 
				
			||||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
					import dan200.computercraft.impl.TurtleUpgrades;
 | 
				
			||||||
import dan200.computercraft.shared.ModRegistry;
 | 
					import dan200.computercraft.shared.ModRegistry;
 | 
				
			||||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
					import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
				
			||||||
@@ -104,9 +105,10 @@ public final class TurtleUpgradeRecipe extends CustomRecipe {
 | 
				
			|||||||
        // At this point we have a turtle + 1 or 2 items
 | 
					        // At this point we have a turtle + 1 or 2 items
 | 
				
			||||||
        // Get the turtle we already have
 | 
					        // Get the turtle we already have
 | 
				
			||||||
        var itemTurtle = (TurtleItem) turtle.getItem();
 | 
					        var itemTurtle = (TurtleItem) turtle.getItem();
 | 
				
			||||||
        var upgrades = new ITurtleUpgrade[]{
 | 
					        @SuppressWarnings({ "unchecked", "rawtypes" })
 | 
				
			||||||
            itemTurtle.getUpgrade(turtle, TurtleSide.LEFT),
 | 
					        UpgradeData<ITurtleUpgrade>[] upgrades = new UpgradeData[]{
 | 
				
			||||||
            itemTurtle.getUpgrade(turtle, TurtleSide.RIGHT),
 | 
					            itemTurtle.getUpgradeWithData(turtle, TurtleSide.LEFT),
 | 
				
			||||||
 | 
					            itemTurtle.getUpgradeWithData(turtle, TurtleSide.RIGHT),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get the upgrades for the new items
 | 
					        // Get the upgrades for the new items
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import dan200.computercraft.api.turtle.*;
 | 
				
			|||||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
 | 
					import dan200.computercraft.shared.peripheral.modem.ModemState;
 | 
				
			||||||
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
 | 
					import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
 | 
				
			||||||
import net.minecraft.core.Direction;
 | 
					import net.minecraft.core.Direction;
 | 
				
			||||||
 | 
					import net.minecraft.nbt.CompoundTag;
 | 
				
			||||||
import net.minecraft.resources.ResourceLocation;
 | 
					import net.minecraft.resources.ResourceLocation;
 | 
				
			||||||
import net.minecraft.world.item.ItemStack;
 | 
					import net.minecraft.world.item.ItemStack;
 | 
				
			||||||
import net.minecraft.world.level.Level;
 | 
					import net.minecraft.world.level.Level;
 | 
				
			||||||
@@ -77,4 +78,9 @@ public class TurtleModem extends AbstractTurtleUpgrade {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public CompoundTag getPersistedData(CompoundTag upgradeData) {
 | 
				
			||||||
 | 
					        return new CompoundTag();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.util;
 | 
				
			|||||||
import com.google.common.annotations.VisibleForTesting;
 | 
					import com.google.common.annotations.VisibleForTesting;
 | 
				
			||||||
import com.google.common.io.BaseEncoding;
 | 
					import com.google.common.io.BaseEncoding;
 | 
				
			||||||
import dan200.computercraft.core.util.Nullability;
 | 
					import dan200.computercraft.core.util.Nullability;
 | 
				
			||||||
 | 
					import dan200.computercraft.shared.platform.PlatformHelper;
 | 
				
			||||||
import net.minecraft.nbt.*;
 | 
					import net.minecraft.nbt.*;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
@@ -19,6 +20,7 @@ import java.io.OutputStream;
 | 
				
			|||||||
import java.security.MessageDigest;
 | 
					import java.security.MessageDigest;
 | 
				
			||||||
import java.security.NoSuchAlgorithmException;
 | 
					import java.security.NoSuchAlgorithmException;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,9 +29,42 @@ public final class NBTUtil {
 | 
				
			|||||||
    @VisibleForTesting
 | 
					    @VisibleForTesting
 | 
				
			||||||
    static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase();
 | 
					    static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final CompoundTag EMPTY_TAG;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static {
 | 
				
			||||||
 | 
					        // If in a development environment, create a magic immutable compound tag.
 | 
				
			||||||
 | 
					        // We avoid doing this in prod, as I fear it might mess up the JIT inlining things.
 | 
				
			||||||
 | 
					        if (PlatformHelper.get().isDevelopmentEnvironment()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                var ctor = CompoundTag.class.getDeclaredConstructor(Map.class);
 | 
				
			||||||
 | 
					                ctor.setAccessible(true);
 | 
				
			||||||
 | 
					                EMPTY_TAG = ctor.newInstance(Collections.emptyMap());
 | 
				
			||||||
 | 
					            } catch (ReflectiveOperationException e) {
 | 
				
			||||||
 | 
					                throw new RuntimeException(e);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            EMPTY_TAG = new CompoundTag();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private NBTUtil() {
 | 
					    private NBTUtil() {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a singleton empty {@link CompoundTag}. This tag should never be modified.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return The empty compound tag.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static CompoundTag emptyTag() {
 | 
				
			||||||
 | 
					        if (EMPTY_TAG.size() != 0) LOG.error("The empty tag has been modified.");
 | 
				
			||||||
 | 
					        return EMPTY_TAG;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static CompoundTag getCompoundOrEmpty(CompoundTag tag, String key) {
 | 
				
			||||||
 | 
					        var childTag = tag.get(key);
 | 
				
			||||||
 | 
					        return childTag != null && childTag.getId() == Tag.TAG_COMPOUND ? (CompoundTag) childTag : emptyTag();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static @Nullable Tag toNBTTag(@Nullable Object object) {
 | 
					    private static @Nullable Tag toNBTTag(@Nullable Object object) {
 | 
				
			||||||
        if (object == null) return null;
 | 
					        if (object == null) return null;
 | 
				
			||||||
        if (object instanceof Boolean) return ByteTag.valueOf((byte) ((boolean) (Boolean) object ? 1 : 0));
 | 
					        if (object instanceof Boolean) return ByteTag.valueOf((byte) ((boolean) (Boolean) object ? 1 : 0));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,6 +58,11 @@ import java.util.function.Predicate;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class })
 | 
					@AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class })
 | 
				
			||||||
public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper {
 | 
					public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public boolean isDevelopmentEnvironment() {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ConfigFile.Builder createConfigBuilder() {
 | 
					    public ConfigFile.Builder createConfigBuilder() {
 | 
				
			||||||
        throw new UnsupportedOperationException("Cannot create config file inside tests");
 | 
					        throw new UnsupportedOperationException("Cannot create config file inside tests");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,7 @@ import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType;
 | 
				
			|||||||
import net.fabricmc.fabric.api.tag.convention.v1.ConventionalItemTags;
 | 
					import net.fabricmc.fabric.api.tag.convention.v1.ConventionalItemTags;
 | 
				
			||||||
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
 | 
					import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
 | 
				
			||||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
 | 
					import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
 | 
				
			||||||
 | 
					import net.fabricmc.loader.api.FabricLoader;
 | 
				
			||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
 | 
					import net.minecraft.commands.synchronization.ArgumentTypeInfo;
 | 
				
			||||||
import net.minecraft.core.BlockPos;
 | 
					import net.minecraft.core.BlockPos;
 | 
				
			||||||
import net.minecraft.core.Direction;
 | 
					import net.minecraft.core.Direction;
 | 
				
			||||||
@@ -81,6 +82,11 @@ import java.util.function.*;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@AutoService(dan200.computercraft.impl.PlatformHelper.class)
 | 
					@AutoService(dan200.computercraft.impl.PlatformHelper.class)
 | 
				
			||||||
public class PlatformHelperImpl implements PlatformHelper {
 | 
					public class PlatformHelperImpl implements PlatformHelper {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public boolean isDevelopmentEnvironment() {
 | 
				
			||||||
 | 
					        return FabricLoader.getInstance().isDevelopmentEnvironment();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ConfigFile.Builder createConfigBuilder() {
 | 
					    public ConfigFile.Builder createConfigBuilder() {
 | 
				
			||||||
        return new FabricConfigFile.Builder();
 | 
					        return new FabricConfigFile.Builder();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,6 +62,7 @@ import net.minecraftforge.common.util.NonNullConsumer;
 | 
				
			|||||||
import net.minecraftforge.event.ForgeEventFactory;
 | 
					import net.minecraftforge.event.ForgeEventFactory;
 | 
				
			||||||
import net.minecraftforge.eventbus.api.Event;
 | 
					import net.minecraftforge.eventbus.api.Event;
 | 
				
			||||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
 | 
					import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
 | 
				
			||||||
 | 
					import net.minecraftforge.fml.loading.FMLLoader;
 | 
				
			||||||
import net.minecraftforge.items.wrapper.InvWrapper;
 | 
					import net.minecraftforge.items.wrapper.InvWrapper;
 | 
				
			||||||
import net.minecraftforge.items.wrapper.SidedInvWrapper;
 | 
					import net.minecraftforge.items.wrapper.SidedInvWrapper;
 | 
				
			||||||
import net.minecraftforge.network.NetworkHooks;
 | 
					import net.minecraftforge.network.NetworkHooks;
 | 
				
			||||||
@@ -76,6 +77,11 @@ import java.util.function.*;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@AutoService(dan200.computercraft.impl.PlatformHelper.class)
 | 
					@AutoService(dan200.computercraft.impl.PlatformHelper.class)
 | 
				
			||||||
public class PlatformHelperImpl implements PlatformHelper {
 | 
					public class PlatformHelperImpl implements PlatformHelper {
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public boolean isDevelopmentEnvironment() {
 | 
				
			||||||
 | 
					        return !FMLLoader.isProduction();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ConfigFile.Builder createConfigBuilder() {
 | 
					    public ConfigFile.Builder createConfigBuilder() {
 | 
				
			||||||
        return new ForgeConfigFile.Builder();
 | 
					        return new ForgeConfigFile.Builder();
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user