1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-28 00:12:16 +00:00

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.
This commit is contained in:
Jummit 2021-05-16 00:13:22 +02:00
parent 1f1b20c81e
commit a4e68d637a
7 changed files with 116 additions and 132 deletions

View File

@ -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 );
}
}

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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) {