From a4e68d637abdf0502cfc090a3e12258f8581b3c7 Mon Sep 17 00:00:00 2001 From: Jummit Date: Sun, 16 May 2021 00:13:22 +0200 Subject: [PATCH] Make upgrade recipe requirements a little more lax - Move some common upgrade code to IUpgradeBase. 99% sure this this preserves binary compatibility (on the JVM at least). - Instead of requiring the share tag to match, allow upgrades to specify their own predicate. IMO this is a little ugly, but required to fix #614 as other mods chuck their own NBT on items. --- .../computercraft/api/IUpgradeBase.java | 81 +++++++++++++++++++ .../api/pocket/IPocketUpgrade.java | 46 +---------- .../api/turtle/ITurtleUpgrade.java | 38 +-------- .../computercraft/shared/PocketUpgrades.java | 3 +- .../computercraft/shared/TurtleUpgrades.java | 4 +- .../shared/turtle/upgrades/TurtleTool.java | 28 ++++++- .../shared/util/InventoryUtil.java | 48 ----------- 7 files changed, 116 insertions(+), 132 deletions(-) create mode 100644 src/main/java/dan200/computercraft/api/IUpgradeBase.java diff --git a/src/main/java/dan200/computercraft/api/IUpgradeBase.java b/src/main/java/dan200/computercraft/api/IUpgradeBase.java new file mode 100644 index 000000000..c659ea5f5 --- /dev/null +++ b/src/main/java/dan200/computercraft/api/IUpgradeBase.java @@ -0,0 +1,81 @@ +package dan200.computercraft.api; + +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; + +/** + * Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}. + */ +public interface IUpgradeBase +{ + /** + * Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" + * or "my_mod:my_upgrade". + * + * You should use a unique resource domain to ensure this upgrade is uniquely identified. + * The upgrade will fail registration if an already used ID is specified. + * + * @return The unique ID for this upgrade. + */ + @Nonnull + Identifier getUpgradeID(); + + /** + * Return an unlocalised string to describe this type of computer in item names. + * + * Examples of built-in adjectives are "Wireless", "Mining" and "Crafty". + * + * @return The localisation key for this upgrade's adjective. + */ + @Nonnull + String getUnlocalisedAdjective(); + + /** + * Return an item stack representing the type of item that a computer must be crafted + * with to create a version which holds this upgrade. This item stack is also used + * to determine the upgrade given by {@code turtle.equipLeft()} or {@code pocket.equipBack()} + * + * This should be constant over a session (or at least a datapack reload). It is recommended + * that you cache the stack too, in order to prevent constructing it every time the method + * is called. + * + * @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted. + */ + @Nonnull + ItemStack getCraftingItem(); + + /** + * Determine if an item is suitable for being used for this upgrade. + * + * When un-equipping an upgrade, we return {@link #getCraftingItem()} rather than + * the original stack. In order to prevent people losing items with enchantments (or + * repairing items with non-0 damage), we impose additional checks on the item. + * + * The default check requires that any non-capability NBT is exactly the same as the + * crafting item, but this may be relaxed for your upgrade. + * + * @param stack The stack to check. This is guaranteed to be non-empty and have the same item as + * {@link #getCraftingItem()}. + * @return If this stack may be used to equip this upgrade. + * @see net.minecraftforge.common.crafting.NBTIngredient#test(ItemStack) For the implementation of the default + * check. + */ + default boolean isItemSuitable( @Nonnull ItemStack stack ) + { + ItemStack crafting = getCraftingItem(); + + // A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a + // null one. + CompoundTag shareTag = stack.getTag(); + CompoundTag craftingShareTag = crafting.getTag(); + if( shareTag == craftingShareTag ) return true; + if( shareTag == null ) return craftingShareTag.isEmpty(); + if( craftingShareTag == null ) return shareTag.isEmpty(); + return shareTag.equals( craftingShareTag ); + } +} diff --git a/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java b/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java index 150c0feb4..37fb9687a 100644 --- a/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java +++ b/src/main/java/dan200/computercraft/api/pocket/IPocketUpgrade.java @@ -10,56 +10,18 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.IUpgradeBase; import dan200.computercraft.api.peripheral.IPeripheral; -import dan200.computercraft.api.turtle.ITurtleUpgrade; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Identifier; import net.minecraft.world.World; /** * Additional peripherals for pocket computers. * - * This is similar to {@link ITurtleUpgrade}. + * @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade) */ -public interface IPocketUpgrade { - - /** - * Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade". - * - * You should use a unique resource domain to ensure this upgrade is uniquely identified. The upgrade will fail registration if an already used ID is - * specified. - * - * @return The upgrade's id. - * @see IPocketUpgrade#getUpgradeID() - * @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade) - */ - @Nonnull - Identifier getUpgradeID(); - - /** - * Return an unlocalised string to describe the type of pocket computer this upgrade provides. - * - * An example of a built-in adjectives is "Wireless" - this is converted to "Wireless Pocket Computer". - * - * @return The unlocalised adjective. - * @see ITurtleUpgrade#getUnlocalisedAdjective() - */ - @Nonnull - String getUnlocalisedAdjective(); - - /** - * Return an item stack representing the type of item that a pocket computer must be crafted with to create a pocket computer which holds this upgrade. - * This item stack is also used to determine the upgrade given by {@code pocket.equip()}/{@code pocket.unequip()}. - * - * Ideally this should be constant over a session. It is recommended that you cache the item too, in order to prevent constructing it every time the - * method is called. - * - * @return The item stack used for crafting. This can be {@link ItemStack#EMPTY} if crafting is disabled. - */ - @Nonnull - ItemStack getCraftingItem(); - +public interface IPocketUpgrade extends IUpgradeBase +{ /** * Creates a peripheral for the pocket computer. * diff --git a/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java b/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java index 479b626e5..7048031b4 100644 --- a/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java +++ b/src/main/java/dan200/computercraft/api/turtle/ITurtleUpgrade.java @@ -10,11 +10,10 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.IUpgradeBase; import dan200.computercraft.api.client.TransformedModel; import dan200.computercraft.api.peripheral.IPeripheral; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Identifier; import net.minecraft.util.math.Direction; import net.fabricmc.api.EnvType; @@ -25,27 +24,8 @@ import net.fabricmc.api.Environment; * * @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade) */ -public interface ITurtleUpgrade { - /** - * Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade". You should use a unique - * resource domain to ensure this upgrade is uniquely identified. The turtle will fail registration if an already used ID is specified. - * - * @return The unique ID for this upgrade. - * @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade) - */ - @Nonnull - Identifier getUpgradeID(); - - /** - * Return an unlocalised string to describe this type of turtle in turtle item names. - * - * Examples of built-in adjectives are "Wireless", "Mining" and "Crafty". - * - * @return The localisation key for this upgrade's adjective. - */ - @Nonnull - String getUnlocalisedAdjective(); - +public interface ITurtleUpgrade extends IUpgradeBase +{ /** * Return whether this turtle adds a tool or a peripheral to the turtle. * @@ -55,18 +35,6 @@ public interface ITurtleUpgrade { @Nonnull TurtleUpgradeType getType(); - /** - * Return an item stack representing the type of item that a turtle must be crafted with to create a turtle which holds this upgrade. This item stack is - * also used to determine the upgrade given by {@code turtle.equip()} - * - * Ideally this should be constant over a session. It is recommended that you cache the item too, in order to prevent constructing it every time the - * method is called. - * - * @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted. - */ - @Nonnull - ItemStack getCraftingItem(); - /** * Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade. * diff --git a/src/main/java/dan200/computercraft/shared/PocketUpgrades.java b/src/main/java/dan200/computercraft/shared/PocketUpgrades.java index f63074733..696acc6ad 100644 --- a/src/main/java/dan200/computercraft/shared/PocketUpgrades.java +++ b/src/main/java/dan200/computercraft/shared/PocketUpgrades.java @@ -59,7 +59,8 @@ public final class PocketUpgrades { for (IPocketUpgrade upgrade : upgrades.values()) { ItemStack craftingStack = upgrade.getCraftingItem(); - if (!craftingStack.isEmpty() && InventoryUtil.areItemsSimilar(stack, craftingStack)) { + if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && upgrade.isItemSuitable( stack ) ) + { return upgrade; } } diff --git a/src/main/java/dan200/computercraft/shared/TurtleUpgrades.java b/src/main/java/dan200/computercraft/shared/TurtleUpgrades.java index e34d2d19e..fe5c51947 100644 --- a/src/main/java/dan200/computercraft/shared/TurtleUpgrades.java +++ b/src/main/java/dan200/computercraft/shared/TurtleUpgrades.java @@ -19,7 +19,6 @@ import javax.annotation.Nullable; import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.shared.computer.core.ComputerFamily; -import dan200.computercraft.shared.util.InventoryUtil; import net.minecraft.item.ItemStack; @@ -96,7 +95,8 @@ public final class TurtleUpgrades { } ItemStack craftingStack = wrapper.upgrade.getCraftingItem(); - if (!craftingStack.isEmpty() && InventoryUtil.areItemsSimilar(stack, craftingStack)) { + if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade.isItemSuitable( stack ) ) + { return wrapper.upgrade; } } diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index b06d6f25c..f8039ee3e 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -11,6 +11,8 @@ import java.util.function.Function; import javax.annotation.Nonnull; +import org.apache.commons.lang3.tuple.Pair; + import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.client.TransformedModel; import dan200.computercraft.api.turtle.AbstractTurtleUpgrade; @@ -29,8 +31,6 @@ import dan200.computercraft.shared.turtle.core.TurtlePlayer; import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.WorldUtil; -import org.apache.commons.lang3.tuple.Pair; - import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; @@ -41,9 +41,9 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.decoration.ArmorStandEntity; -import net.minecraft.fluid.FluidState; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; @@ -59,6 +59,9 @@ import net.fabricmc.fabric.api.event.player.AttackEntityCallback; public class TurtleTool extends AbstractTurtleUpgrade { protected final ItemStack item; + private static final int TAG_LIST = 9; + private static final int TAG_COMPOUND = 10; + public TurtleTool(Identifier id, String adjective, Item item) { super(id, TurtleUpgradeType.TOOL, adjective, item); this.item = new ItemStack(item); @@ -74,6 +77,24 @@ public class TurtleTool extends AbstractTurtleUpgrade { this.item = toolItem; } + @Override + public boolean isItemSuitable( @Nonnull ItemStack stack ) + { + CompoundTag tag = stack.getTag(); + if( tag == null || tag.isEmpty() ) return true; + + // Check we've not got anything vaguely interesting on the item. We allow other mods to add their + // own NBT, with the understanding such details will be lost to the mist of time. + if( stack.isDamaged() || stack.hasEnchantments() || stack.hasCustomName() ) return false; + if( tag.contains( "AttributeModifiers", TAG_LIST ) && + !tag.getList( "AttributeModifiers", TAG_COMPOUND ).isEmpty() ) + { + return false; + } + + return true; + } + @Nonnull @Override public TurtleCommandResult useTool(@Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction) { @@ -183,7 +204,6 @@ public class TurtleTool extends AbstractTurtleUpgrade { } BlockState state = world.getBlockState(blockPosition); - FluidState fluidState = world.getFluidState(blockPosition); TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, turtlePosition, direction); turtlePlayer.loadInventory(this.item.copy()); diff --git a/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java b/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java index a8495d9aa..c30454b2f 100644 --- a/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java @@ -14,7 +14,6 @@ import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.Entity; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundTag; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; @@ -28,53 +27,6 @@ public final class InventoryUtil { return a == b || (a.getItem() == b.getItem() && ItemStack.areTagsEqual(a, b)); } - /** - * Determines if two items are "mostly" equivalent. Namely, they have the same item and damage, and identical share stacks. - * - * sufficient to ensure basic information (such as enchantments) are the same, while not having to worry about capabilities. - * - * @param a The first stack to check - * @param b The second stack to check - * @return If these items are largely the same. - */ - public static boolean areItemsSimilar(@Nonnull ItemStack a, @Nonnull ItemStack b) { - if (a == b) { - return true; - } - if (a.isEmpty()) { - return !b.isEmpty(); - } - - if (a.getItem() != b.getItem()) { - return false; - } - - // A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a - // null one. - CompoundTag shareTagA = a.getTag(); - CompoundTag shareTagB = b.getTag(); - if (shareTagA == shareTagB) { - return true; - } - if (shareTagA == null) { - return shareTagB.isEmpty(); - } - if (shareTagB == null) { - return shareTagA.isEmpty(); - } - return shareTagA.equals(shareTagB); - } - - @Nonnull - public static ItemStack copyItem(@Nonnull ItemStack a) { - return a.copy(); - } - - public static ItemStorage getStorage(World world, BlockPos pos, Direction side) { - Inventory inventory = getInventory(world, pos, side); - return inventory == null ? null : ItemStorage.wrap(inventory, side); - } - // Methods for finding inventories: public static Inventory getInventory(World world, BlockPos pos, Direction side) {