Rewrite turtle upgrade interface

This commit is contained in:
Jonathan Coates
2026-10-15 22:10:26 +01:00
parent 7b5fae17f3
commit 0611e23d1b
86 changed files with 746 additions and 332 deletions
@@ -8,10 +8,12 @@ import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
@@ -42,7 +44,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
*
* @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, unless
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overridden.
* {@link #getModel(UpgradeData, TurtleSide)} is overridden.
* @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.
*/
@@ -57,11 +59,25 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
* @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.
* @deprecated Prefer {@link #getModel(UpgradeData, TurtleSide)}.
*/
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
@Deprecated
default TransformedModel getModel(T upgrade, @Unmodifiable CompoundTag data, TurtleSide side) {
return getModel(upgrade, (ITurtleAccess) null, 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 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(UpgradeData<T> upgrade, TurtleSide side) {
return getModel(upgrade.upgrade(), upgrade.data(), side);
}
/**
* Get a list of models that this turtle modeller depends on.
@@ -9,6 +9,7 @@ import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
@@ -41,10 +42,16 @@ final class TurtleUpgradeModellers {
}
@Override
@Deprecated
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(data), side);
}
@Override
public TransformedModel getModel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(), side);
}
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
@@ -5,8 +5,10 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
/**
@@ -18,11 +20,21 @@ public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
private final ResourceLocation id;
private final String adjective;
private final ItemStack stack;
private final Ingredient ingredient;
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
this.id = id;
this.adjective = adjective;
this.stack = stack;
this.ingredient = Ingredient.of(stack);
}
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, Ingredient ingredient) {
this.id = id;
this.adjective = adjective;
var items = ingredient.getItems();
this.stack = items.length == 0 ? ItemStack.EMPTY : items[0];
this.ingredient = ingredient;
}
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
@@ -43,4 +55,26 @@ public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
public final ItemStack getCraftingItem() {
return stack;
}
@Override
public final Ingredient getCraftingIngredient() {
return ingredient;
}
public abstract static class StackAware extends AbstractPocketUpgrade {
protected StackAware(ResourceLocation id, String adjective, Ingredient ingredient) {
super(id, adjective, ingredient);
}
@Override
@Deprecated
public final CompoundTag getUpgradeData(ItemStack stack) {
return super.getUpgradeData(stack);
}
@Override
public boolean isItemSuitable(ItemStack stack) {
return true;
}
}
}
@@ -7,6 +7,7 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeHandle;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
@@ -90,8 +91,9 @@ public interface IPocketAccess {
* @see #getUpgradeNBTData()
* @see #setUpgrade(UpgradeData)
*/
@Nullable
UpgradeData<IPocketUpgrade> getUpgrade();
@Nullable UpgradeData<IPocketUpgrade> getUpgrade();
@Nullable UpgradeHandle<IPocketUpgrade> getUpgradeHandle();
/**
* Set the upgrade for this pocket computer, also updating the item stack.
@@ -113,15 +115,30 @@ public interface IPocketAccess {
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
* @see #getUpgrade()
* @deprecated Use {@link #getUpgradeHandle()} and {@link UpgradeHandle#getData()}.
*/
CompoundTag getUpgradeNBTData();
@Deprecated
default CompoundTag getUpgradeNBTData() {
var handle = getUpgradeHandle();
return handle == null ? new CompoundTag() : handle.getData();
}
/**
* Mark the upgrade-specific NBT as dirty.
*
* @see #getUpgradeNBTData()
* @deprecated Use {@link #getUpgradeHandle()} and {@link UpgradeHandle#setChanged()}.
*/
void updateUpgradeNBTData();
@Deprecated
default void updateUpgradeNBTData() {
var handle = getUpgradeHandle();
if (handle == null) return;
// Legacy behaviour: overwrite the item with the version based on the upgrade data.
handle.setItem(handle.getUpgrade().getUpgradeItem(handle.getData()));
handle.setChanged();
}
/**
* Remove the current peripheral and create a new one.
@@ -7,12 +7,14 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingIngredient;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
@@ -76,4 +78,24 @@ public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends Upgra
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingIngredient()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomIngredient(BiFunction<ResourceLocation, Ingredient, T> factory) {
final class Impl extends SerialiserWithCraftingIngredient<T> implements PocketUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, Ingredient, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}
@@ -5,8 +5,10 @@
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
/**
@@ -19,12 +21,23 @@ public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
private final TurtleUpgradeType type;
private final String adjective;
private final ItemStack stack;
private final Ingredient ingredient;
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
this.id = id;
this.type = type;
this.adjective = adjective;
this.stack = stack;
this.ingredient = Ingredient.of(stack);
}
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, Ingredient ingredient) {
this.id = id;
this.type = type;
this.adjective = adjective;
var items = ingredient.getItems();
this.stack = items.length == 0 ? ItemStack.EMPTY : items[0];
this.ingredient = ingredient;
}
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
@@ -50,4 +63,26 @@ public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
public final ItemStack getCraftingItem() {
return stack;
}
@Override
public final Ingredient getCraftingIngredient() {
return ingredient;
}
public abstract static class StackAware extends AbstractTurtleUpgrade {
protected StackAware(ResourceLocation id, TurtleUpgradeType type, String adjective, Ingredient ingredient) {
super(id, type, adjective, ingredient);
}
@Override
@Deprecated
public final CompoundTag getUpgradeData(ItemStack stack) {
return super.getUpgradeData(stack);
}
@Override
public boolean isItemSuitable(ItemStack stack) {
return true;
}
}
}
@@ -8,13 +8,12 @@ import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeHandle;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
@@ -242,10 +241,9 @@ public interface ITurtleAccess {
* @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));
}
@Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side);
@Nullable UpgradeHandle<ITurtleUpgrade> getUpgradeHandle(TurtleSide side);
/**
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
@@ -275,8 +273,7 @@ public interface ITurtleAccess {
* @param side The side to get the peripheral from.
* @return The peripheral created by the upgrade on the specified side of the turtle, {@code null} if none exists.
*/
@Nullable
IPeripheral getPeripheral(TurtleSide side);
@Nullable IPeripheral getPeripheral(TurtleSide side);
/**
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
@@ -286,11 +283,12 @@ public interface ITurtleAccess {
*
* @param side The side to get the upgrade data for.
* @return The upgrade-specific data.
* @see #updateUpgradeNBTData(TurtleSide)
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
* @deprecated Use {@link #getUpgradeHandle(TurtleSide)} and {@link UpgradeHandle#getData()}.
*/
CompoundTag getUpgradeNBTData(TurtleSide side);
default CompoundTag getUpgradeNBTData(TurtleSide side) {
var handle = getUpgradeHandle(side);
return handle == null ? new CompoundTag() : handle.getData();
}
/**
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
@@ -298,6 +296,16 @@ public interface ITurtleAccess {
*
* @param side The side to mark dirty.
* @see #updateUpgradeNBTData(TurtleSide)
* @deprecated Use {@link #getUpgradeHandle(TurtleSide)} and {@link UpgradeHandle#setChanged()}.
*/
void updateUpgradeNBTData(TurtleSide side);
@Deprecated
default void updateUpgradeNBTData(TurtleSide side) {
var handle = getUpgradeHandle(side);
if (handle == null) return;
// Legacy behaviour: overwrite the item with the version based on the upgrade data.
handle.setItem(handle.getUpgrade().getUpgradeItem(handle.getData()));
handle.setChanged();
}
}
@@ -7,12 +7,14 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingIngredient;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
@@ -80,4 +82,23 @@ public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends Upgra
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingIngredient()} <strong>MUST</strong> equal the provided ingredient.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomIngredient(BiFunction<ResourceLocation, Ingredient, T> factory) {
final class Impl extends SerialiserWithCraftingIngredient<T> implements TurtleUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, Ingredient, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}
@@ -14,6 +14,7 @@ import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import java.util.Objects;
@@ -51,9 +52,15 @@ public interface UpgradeBase {
* is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
* @deprecated Prefer {@link #getCraftingIngredient()}.
*/
@Deprecated
ItemStack getCraftingItem();
default Ingredient getCraftingIngredient() {
return Ingredient.of(getCraftingItem());
}
/**
* Returns the item stack representing a currently equipped turtle upgrade.
* <p>
@@ -86,6 +93,7 @@ public interface UpgradeBase {
* {@link #getCraftingItem()}.
* @return The upgrade data that should be set on the turtle or pocket computer.
*/
@Deprecated
default CompoundTag getUpgradeData(ItemStack stack) {
return new CompoundTag();
}
@@ -9,19 +9,49 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
/**
* 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}.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
public final class UpgradeData<T extends UpgradeBase> {
private final T upgrade;
private final CompoundTag data;
private final @Nullable ItemStack stack;
private UpgradeData(T upgrade, CompoundTag data, @Nullable ItemStack stack) {
this.upgrade = upgrade;
this.data = data;
this.stack = stack;
if (stack != null) {
if (stack.getCount() != 1) throw new IllegalArgumentException("Stack must be of size 1");
if (!upgrade.getCraftingIngredient().test(stack) || !upgrade.isItemSuitable(stack)) {
throw new IllegalArgumentException("Item is not suitable for this upgrade");
}
}
}
/**
* Create a new {@link UpgradeData} with an item and upgrade data.
*
* @param upgrade The current upgrade.
* @param data The upgrade's data.
* @deprecated Use {@link UpgradeData#of(UpgradeBase, ItemStack, CompoundTag)}.
*/
@Deprecated
public UpgradeData(T upgrade, @Unmodifiable CompoundTag data) {
this(upgrade, data, null);
}
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
@@ -29,11 +59,25 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance.
* @deprecated Use {@link #of(UpgradeBase, ItemStack, CompoundTag)}
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
@Deprecated
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, @Unmodifiable CompoundTag data) {
return new UpgradeData<>(upgrade, data);
}
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
* @param <T> The type of upgrade.
* @param upgrade An upgrade.
* @param data The upgrade's data.
* @return The new {@link UpgradeData} instance.
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, @Unmodifiable ItemStack stack, @Unmodifiable CompoundTag data) {
return new UpgradeData<>(upgrade, data, stack);
}
/**
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
*
@@ -42,7 +86,7 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
* @return The default upgrade data.
*/
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
return of(upgrade, upgrade.getCraftingItem(), upgrade.getUpgradeData(upgrade.getCraftingItem()));
}
/**
@@ -57,16 +101,23 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
return upgrade == null ? null : upgrade.copy();
}
public T upgrade() {
return upgrade;
}
public @Unmodifiable CompoundTag data() {
return data;
}
/**
* 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}.
* This returns a defensive copy of the item, to prevent accidental mutation of the stack.
*
* @return This upgrade's item.
*/
public ItemStack getUpgradeItem() {
return upgrade.getUpgradeItem(data).copy();
return (stack == null ? upgrade.getUpgradeItem(data) : stack).copy();
}
/**
@@ -76,6 +127,34 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
* @return A copy of the current instance.
*/
public UpgradeData<T> copy() {
return new UpgradeData<>(upgrade(), data().copy());
return new UpgradeData<>(upgrade(), data().copy(), stack == null ? null : stack.copy());
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
return obj instanceof UpgradeData<?> that && upgrade == that.upgrade && data.equals(that.data) && areStacksEqual(stack, that.stack);
}
private static boolean areStacksEqual(@Nullable ItemStack left, @Nullable ItemStack right) {
if (left == null) return right == null;
return right != null && ItemStack.isSameItemSameTags(left, right);
}
@Override
public int hashCode() {
var result = upgrade.hashCode();
result = 31 * result + data.hashCode();
result = 31 * result + hashStack(stack);
return result;
}
private static int hashStack(@Nullable ItemStack stack) {
return stack == null ? 0 : stack.getItem().hashCode() * 31 + Objects.hash(stack.getTag());
}
@Override
public String toString() {
return "UpgradeData[upgrade=" + upgrade + ", data=" + data + ", stack=" + stack + "]";
}
}
@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingIngredient;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.Util;
@@ -19,6 +20,7 @@ import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.crafting.Ingredient;
import org.jspecify.annotations.Nullable;
import java.util.*;
@@ -75,6 +77,10 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
if (serialiser instanceof SerialiserWithCraftingIngredient<?>) {
return simpleWithCustomItem(id, serialiser, Ingredient.of(item));
}
if (!(serialiser instanceof SerialiserWithCraftingItem)) {
throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
}
@@ -84,11 +90,35 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
);
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @param item The crafting upgrade for this item.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Ingredient item) {
if (!(serialiser instanceof SerialiserWithCraftingIngredient<?>)) {
throw new IllegalStateException(serialiser + " must be a simpleWithCustomIngredient() serialiser.");
}
return new Upgrade<>(id, serialiser, s -> {
var itemJson = item.toJson();
if (itemJson instanceof JsonObject obj && obj.size() == 1 && obj.has("item")) {
// Preserve compatibility with our previous format.
s.add("item", obj.get("item"));
} else {
s.add("item", itemJson);
}
});
}
/**
* Add all turtle or pocket computer upgrades.
*
* <h4>Example</h4>
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
* {@snippet class = com.example.examplemod.data.TurtleDataProvider region = body}
*
* @param addUpgrade A callback used to register an upgrade.
*/
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
public interface UpgradeHandle<T extends UpgradeBase> {
T getUpgrade();
default UpgradeData<T> getUpgradeWithData() {
return UpgradeData.of(getUpgrade(), getItem(), getData());
}
ItemStack getItem();
void setItem(ItemStack item);
CompoundTag getData();
void setComponents(CompoundTag data);
void setChanged();
void clear();
}
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl.upgrades;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.crafting.Ingredient;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.BiFunction;
/**
* Simple serialiser which returns a constant upgrade with a custom crafting item.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SerialiserWithCraftingIngredient<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final BiFunction<ResourceLocation, Ingredient, T> factory;
protected SerialiserWithCraftingIngredient(BiFunction<ResourceLocation, Ingredient, T> factory) {
this.factory = factory;
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
return factory.apply(id, getIngredientOrItem(object, "item"));
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
return factory.apply(id, Ingredient.fromNetwork(buffer));
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
upgrade.getCraftingIngredient().toNetwork(buffer);
}
public static Ingredient getIngredientOrItem(JsonObject object, String key) {
return object.get(key) instanceof JsonPrimitive p && p.isString()
? Ingredient.of(GsonHelper.getAsItem(object, key))
: Ingredient.fromJson(GsonHelper.getAsJsonObject(object, key));
}
}
@@ -194,7 +194,7 @@ public final class ClientRegistry {
var upgrade = item.getUpgradeWithData(stack, side);
if (upgrade == null) return -1;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
var model = TurtleUpgradeModellers.getModel(upgrade, side);
return model.getTint((layer - 1) / 2);
}
@@ -147,7 +147,7 @@ public final class TurtleModelParts<T> {
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);
var model = TurtleUpgradeModellers.getModel(upgrade, side);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix()), side));
}
@@ -41,17 +41,22 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
@Override
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
var active = false;
if (turtle != null) {
var turtleNBT = turtle.getUpgradeNBTData(side);
active = turtleNBT.contains("active") && turtleNBT.getBoolean("active");
}
var active = isActive(turtle, side);
return side == TurtleSide.LEFT
? TransformedModel.of(active ? leftOnModel : leftOffModel)
: TransformedModel.of(active ? rightOnModel : rightOffModel);
}
private static boolean isActive(@Nullable ITurtleAccess turtle, TurtleSide side) {
if (turtle == null) return false;
var handle = turtle.getUpgradeHandle(side);
if (handle == null) return false;
var data = handle.getData();
return data.contains("active") && data.getBoolean("active");
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel);
@@ -11,11 +11,11 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -70,10 +70,10 @@ public final class TurtleUpgradeModellers {
return modeller.getModel(upgrade, access, side);
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
public static TransformedModel getModel(UpgradeData<ITurtleUpgrade> upgrade, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, data, side);
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade.upgrade(), TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, side);
}
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
@@ -19,8 +19,6 @@ import net.minecraft.DetectedVersion;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.metadata.PackMetadataGenerator;
@@ -29,10 +27,8 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.metadata.pack.PackMetadataSection;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import java.util.Arrays;
import java.util.List;
@@ -99,20 +95,11 @@ public final class DataProviders {
shulkerPack.add(out -> new TurtleUpgradeDataProvider(out) {
@Override
protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
for (var item : BuiltInRegistries.ITEM) {
if (item instanceof BlockItem blockItem && blockItem.getBlock() instanceof ShulkerBoxBlock) {
// Shuffle the name about, so blue_shulker_box becomes shulker/blue. This is a bit of a hack to
// ensure they're all grouped together in the creative tab.
var name = PlatformHelper.get().getRegistryKey(Registries.ITEM, item).getPath();
if (name.endsWith("shulker_box")) {
name = name.substring(0, name.length() - "shulker_box".length());
}
if (name.endsWith("_")) name = name.substring(0, name.length() - 1);
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name.isEmpty() ? "shulker" : "shulker/" + name);
simpleWithCustomItem(id, ModRegistry.TurtleSerialisers.STORAGE.get(), item).add(addUpgrade);
}
}
simpleWithCustomItem(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "shulker"),
ModRegistry.TurtleSerialisers.STORAGE.get(),
PlatformHelper.get().getRecipeIngredients().shulkerBox()
).add(addUpgrade);
}
});
}
@@ -22,7 +22,7 @@ class PocketUpgradeProvider extends PocketUpgradeDataProvider {
@Override
protected void addUpgrades(Consumer<Upgrade<PocketUpgradeSerialiser<?>>> addUpgrade) {
addUpgrade.accept(simpleWithCustomItem(id("speaker"), PocketUpgradeSerialisers.SPEAKER.get(), Items.SPEAKER.get()));
simpleWithCustomItem(id("speaker"), PocketUpgradeSerialisers.SPEAKER.get(), Items.SPEAKER.get()).add(addUpgrade);
simpleWithCustomItem(id("wireless_modem_normal"), PocketUpgradeSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);
simpleWithCustomItem(id("wireless_modem_advanced"), PocketUpgradeSerialisers.WIRELESS_MODEM_ADVANCED.get(), Items.WIRELESS_MODEM_ADVANCED.get()).add(addUpgrade);
}
@@ -114,9 +114,9 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.group(name.toString())
.pattern("#T")
.define('T', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
.define('#', upgrade.getCraftingIngredient())
.unlockedBy("has_items",
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
inventoryChange(itemPredicate(base.getItem()), itemPredicate(upgrade.getCraftingIngredient())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
@@ -147,9 +147,9 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.pattern("#")
.pattern("P")
.define('P', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
.define('#', upgrade.getCraftingIngredient())
.unlockedBy("has_items",
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
inventoryChange(itemPredicate(base.getItem()), itemPredicate(upgrade.getCraftingIngredient())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
@@ -4,6 +4,9 @@
"group": "computercraft:pocket_advanced",
"key": {"#": {"item": "computercraft:speaker"}, "P": {"item": "computercraft:pocket_computer_advanced"}},
"pattern": ["#", "P"],
"result": {"item": "computercraft:pocket_computer_advanced", "nbt": "{Upgrade:\"computercraft:speaker\"}"},
"result": {
"item": "computercraft:pocket_computer_advanced",
"nbt": "{Upgrade:\"computercraft:speaker\",UpgradeItem:{Count:1b,id:\"computercraft:speaker\"}}"
},
"show_notification": true
}
@@ -9,7 +9,7 @@
"pattern": ["#", "P"],
"result": {
"item": "computercraft:pocket_computer_advanced",
"nbt": "{Upgrade:\"computercraft:wireless_modem_advanced\"}"
"nbt": "{Upgrade:\"computercraft:wireless_modem_advanced\",UpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_advanced\"}}"
},
"show_notification": true
}
@@ -9,7 +9,7 @@
"pattern": ["#", "P"],
"result": {
"item": "computercraft:pocket_computer_advanced",
"nbt": "{Upgrade:\"computercraft:wireless_modem_normal\"}"
"nbt": "{Upgrade:\"computercraft:wireless_modem_normal\",UpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_normal\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:pocket_normal",
"key": {"#": {"item": "computercraft:speaker"}, "P": {"item": "computercraft:pocket_computer_normal"}},
"pattern": ["#", "P"],
"result": {"item": "computercraft:pocket_computer_normal", "nbt": "{Upgrade:\"computercraft:speaker\"}"},
"result": {
"item": "computercraft:pocket_computer_normal",
"nbt": "{Upgrade:\"computercraft:speaker\",UpgradeItem:{Count:1b,id:\"computercraft:speaker\"}}"
},
"show_notification": true
}
@@ -9,7 +9,7 @@
"pattern": ["#", "P"],
"result": {
"item": "computercraft:pocket_computer_normal",
"nbt": "{Upgrade:\"computercraft:wireless_modem_advanced\"}"
"nbt": "{Upgrade:\"computercraft:wireless_modem_advanced\",UpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_advanced\"}}"
},
"show_notification": true
}
@@ -9,7 +9,7 @@
"pattern": ["#", "P"],
"result": {
"item": "computercraft:pocket_computer_normal",
"nbt": "{Upgrade:\"computercraft:wireless_modem_normal\"}"
"nbt": "{Upgrade:\"computercraft:wireless_modem_normal\",UpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_normal\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_advanced",
"key": {"#": {"item": "computercraft:speaker"}, "T": {"item": "computercraft:turtle_advanced"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"computercraft:speaker\"}"},
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"computercraft:speaker\",RightUpgradeItem:{Count:1b,id:\"computercraft:speaker\"}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"computercraft:wireless_modem_advanced\"}"
"nbt": "{RightUpgrade:\"computercraft:wireless_modem_advanced\",RightUpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_advanced\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_advanced",
"key": {"#": {"item": "computercraft:wireless_modem_normal"}, "T": {"item": "computercraft:turtle_advanced"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"computercraft:wireless_modem_normal\"}"},
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"computercraft:wireless_modem_normal\",RightUpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_normal\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_advanced",
"key": {"#": {"item": "minecraft:crafting_table"}, "T": {"item": "computercraft:turtle_advanced"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"minecraft:crafting_table\"}"},
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"minecraft:crafting_table\",RightUpgradeItem:{Count:1b,id:\"minecraft:crafting_table\"}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"minecraft:diamond_axe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_axe\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_axe\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"minecraft:diamond_hoe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_hoe\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_hoe\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_pickaxe\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"minecraft:diamond_shovel\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_shovel\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_shovel\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_advanced",
"nbt": "{RightUpgrade:\"minecraft:diamond_sword\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_sword\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_sword\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_normal",
"key": {"#": {"item": "computercraft:speaker"}, "T": {"item": "computercraft:turtle_normal"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"computercraft:speaker\"}"},
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"computercraft:speaker\",RightUpgradeItem:{Count:1b,id:\"computercraft:speaker\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_normal",
"key": {"#": {"item": "computercraft:wireless_modem_advanced"}, "T": {"item": "computercraft:turtle_normal"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"computercraft:wireless_modem_advanced\"}"},
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"computercraft:wireless_modem_advanced\",RightUpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_advanced\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_normal",
"key": {"#": {"item": "computercraft:wireless_modem_normal"}, "T": {"item": "computercraft:turtle_normal"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"computercraft:wireless_modem_normal\"}"},
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"computercraft:wireless_modem_normal\",RightUpgradeItem:{Count:1b,id:\"computercraft:wireless_modem_normal\"}}"
},
"show_notification": true
}
@@ -4,6 +4,9 @@
"group": "computercraft:turtle_normal",
"key": {"#": {"item": "minecraft:crafting_table"}, "T": {"item": "computercraft:turtle_normal"}},
"pattern": ["#T"],
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"minecraft:crafting_table\"}"},
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"minecraft:crafting_table\",RightUpgradeItem:{Count:1b,id:\"minecraft:crafting_table\"}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"minecraft:diamond_axe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_axe\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_axe\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"minecraft:diamond_hoe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_hoe\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_hoe\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_pickaxe\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"minecraft:diamond_shovel\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_shovel\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_shovel\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -6,7 +6,7 @@
"pattern": ["#T"],
"result": {
"item": "computercraft:turtle_normal",
"nbt": "{RightUpgrade:\"minecraft:diamond_sword\",RightUpgradeNbt:{Tag:{Damage:0}}}"
"nbt": "{RightUpgrade:\"minecraft:diamond_sword\",RightUpgradeItem:{Count:1b,id:\"minecraft:diamond_sword\",tag:{Damage:0}}}"
},
"show_notification": true
}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:black_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:blue_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:brown_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:cyan_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:gray_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:green_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:light_blue_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:light_gray_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:lime_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:magenta_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:orange_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:pink_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:purple_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:red_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:white_shulker_box"}
@@ -1 +0,0 @@
{"type": "computercraft:storage", "item": "minecraft:yellow_shulker_box"}
@@ -11,6 +11,8 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
@@ -36,7 +38,7 @@ import java.util.stream.Collectors;
* @see TurtleUpgrades
* @see PocketUpgrades
*/
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
public final class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
@@ -79,9 +81,9 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
if (stack.isEmpty()) return null;
for (var wrapper : current.values()) {
var craftingStack = wrapper.upgrade().getCraftingItem();
if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack));
var upgrade = wrapper.upgrade;
if (upgrade.getCraftingIngredient().test(stack) && upgrade.isItemSuitable(stack)) {
return UpgradeData.of(wrapper.upgrade, stack, wrapper.upgrade.getUpgradeData(stack));
}
}
@@ -96,6 +98,39 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
return current;
}
public record UpgradeKey(String id, String item, String data) {
public UpgradeKey(String id, String data) {
this(id, id + "Item", data);
}
}
public @Nullable UpgradeData<T> readUpgrade(CompoundTag tag, UpgradeKey key) {
if (!tag.contains(key.id(), Tag.TAG_STRING)) return null;
var upgrade = get(tag.getString(key.id()));
if (upgrade == null) return null;
var data = tag.getCompound(key.data());
var item = tag.contains(key.item(), Tag.TAG_COMPOUND) ? ItemStack.of(tag.getCompound(key.item())) : upgrade.getUpgradeItem(data);
return UpgradeData.of(upgrade, item, data);
}
public static <T extends UpgradeBase> void writeUpgrade(CompoundTag tag, UpgradeKey key, @Nullable UpgradeData<T> upgrade) {
if (upgrade == null) {
tag.remove(key.id());
tag.remove(key.data());
tag.remove(key.item());
} else {
tag.putString(key.id(), upgrade.upgrade().getUpgradeID().toString());
tag.put(key.item(), upgrade.getUpgradeItem().save(new CompoundTag()));
if (upgrade.data().isEmpty()) {
tag.remove(key.data());
} else {
tag.put(key.data(), upgrade.data().copy());
}
}
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) {
Map<String, UpgradeWrapper<R, T>> newUpgrades = new HashMap<>();
@@ -296,15 +296,15 @@ public final class ModRegistry {
static final RegistrationHelper<TurtleUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(TurtleUpgradeSerialiser.registryId());
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleSpeaker::new));
REGISTRY.register("speaker", () -> TurtleUpgradeSerialiser.simpleWithCustomIngredient(TurtleSpeaker::new));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleCraftingTable>> WORKBENCH =
REGISTRY.register("workbench", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleCraftingTable::new));
REGISTRY.register("workbench", () -> TurtleUpgradeSerialiser.simpleWithCustomIngredient(TurtleCraftingTable::new));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> TurtleUpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, false)));
REGISTRY.register("wireless_modem_normal", () -> TurtleUpgradeSerialiser.simpleWithCustomIngredient((id, item) -> new TurtleModem(id, item, false)));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> TurtleUpgradeSerialiser.simpleWithCustomItem((id, item) -> new TurtleModem(id, item, true)));
REGISTRY.register("wireless_modem_advanced", () -> TurtleUpgradeSerialiser.simpleWithCustomIngredient((id, item) -> new TurtleModem(id, item, true)));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleStorage>> STORAGE =
REGISTRY.register("storage", () -> TurtleUpgradeSerialiser.simpleWithCustomItem(TurtleStorage::new));
REGISTRY.register("storage", () -> TurtleUpgradeSerialiser.simpleWithCustomIngredient(TurtleStorage::new));
public static final RegistryEntry<TurtleUpgradeSerialiser<TurtleTool>> TOOL = REGISTRY.register("tool", () -> TurtleToolSerialiser.INSTANCE);
}
@@ -313,11 +313,11 @@ public final class ModRegistry {
static final RegistrationHelper<PocketUpgradeSerialiser<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(PocketUpgradeSerialiser.registryId());
public static final RegistryEntry<PocketUpgradeSerialiser<PocketSpeaker>> SPEAKER =
REGISTRY.register("speaker", () -> PocketUpgradeSerialiser.simpleWithCustomItem(PocketSpeaker::new));
REGISTRY.register("speaker", () -> PocketUpgradeSerialiser.simpleWithCustomIngredient(PocketSpeaker::new));
public static final RegistryEntry<PocketUpgradeSerialiser<PocketModem>> WIRELESS_MODEM_NORMAL =
REGISTRY.register("wireless_modem_normal", () -> PocketUpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, false)));
REGISTRY.register("wireless_modem_normal", () -> PocketUpgradeSerialiser.simpleWithCustomIngredient((id, item) -> new PocketModem(id, item, false)));
public static final RegistryEntry<PocketUpgradeSerialiser<PocketModem>> WIRELESS_MODEM_ADVANCED =
REGISTRY.register("wireless_modem_advanced", () -> PocketUpgradeSerialiser.simpleWithCustomItem((id, item) -> new PocketModem(id, item, true)));
REGISTRY.register("wireless_modem_advanced", () -> PocketUpgradeSerialiser.simpleWithCustomIngredient((id, item) -> new PocketModem(id, item, true)));
}
public static class Menus {
@@ -22,6 +22,7 @@ import net.minecraft.world.item.crafting.Ingredient;
* @param dye All dye items.
* @param enderPearl All {@link Items#ENDER_PEARL} items.
* @param woodenChest All wooden chests (both normal and trapped chests).
* @param shulkerBox All shulker boxes.
*/
public record RecipeIngredients(
Ingredient redstone,
@@ -35,6 +36,7 @@ public record RecipeIngredients(
Ingredient head,
Ingredient dye,
Ingredient enderPearl,
Ingredient woodenChest
Ingredient woodenChest,
Ingredient shulkerBox
) {
}
@@ -8,6 +8,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeHandle;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ServerComputer;
@@ -147,6 +148,11 @@ public final class PocketBrain implements IPocketAccess {
return upgrade == null ? new CompoundTag() : upgrade.data();
}
@Override
public @Nullable UpgradeHandle<IPocketUpgrade> getUpgradeHandle() {
throw new UnsupportedOperationException(); // TODO
}
@Override
public void updateUpgradeNBTData() {
dirty = true;
@@ -10,6 +10,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@@ -26,7 +27,6 @@ import dan200.computercraft.shared.pocket.core.PocketHolder;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
@@ -49,8 +49,7 @@ import java.util.Objects;
import java.util.UUID;
public class PocketComputerItem extends Item implements IComputerItem, IColouredItem {
private static final String NBT_UPGRADE = "Upgrade";
private static final String NBT_UPGRADE_INFO = "UpgradeInfo";
private static final UpgradeManager.UpgradeKey NBT_UPGRADE = new UpgradeManager.UpgradeKey("Upgrade", "UpgradeInfo");
public static final String NBT_ON = "On";
private static final String NBT_INSTANCE = "InstanceId";
@@ -67,10 +66,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
var result = new ItemStack(this);
if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
if (label != null) result.setHoverName(Component.literal(label));
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 (upgrade != null) UpgradeManager.writeUpgrade(result.getOrCreateTag(), NBT_UPGRADE, upgrade);
if (colour != -1) result.getOrCreateTag().putInt(NBT_COLOUR, colour);
return result;
}
@@ -365,31 +361,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IColoured
}
public static @Nullable IPocketUpgrade getUpgrade(ItemStack stack) {
var compound = stack.getTag();
if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
return PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
var upgrade = getUpgradeWithData(stack);
return upgrade == null ? null : upgrade.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));
var tag = stack.getTag();
return tag == null ? null : PocketUpgrades.instance().readUpgrade(tag, NBT_UPGRADE);
}
public static void setUpgrade(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
var compound = stack.getOrCreateTag();
if (upgrade == null) {
compound.remove(NBT_UPGRADE);
compound.remove(NBT_UPGRADE_INFO);
} else {
compound.putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
if (upgrade.data().isEmpty()) {
compound.remove(NBT_UPGRADE_INFO);
} else {
compound.put(NBT_UPGRADE_INFO, upgrade.data().copy());
}
}
UpgradeManager.writeUpgrade(stack.getOrCreateTag(), NBT_UPGRADE, upgrade);
}
}
@@ -9,14 +9,14 @@ import dan200.computercraft.api.pocket.AbstractPocketUpgrade;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.jspecify.annotations.Nullable;
public class PocketModem extends AbstractPocketUpgrade {
public class PocketModem extends AbstractPocketUpgrade.StackAware {
private final boolean advanced;
public PocketModem(ResourceLocation id, ItemStack stack, boolean advanced) {
super(id, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, stack);
public PocketModem(ResourceLocation id, Ingredient item, boolean advanced) {
super(id, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, item);
this.advanced = advanced;
}
@@ -9,11 +9,11 @@ import dan200.computercraft.api.pocket.AbstractPocketUpgrade;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.jspecify.annotations.Nullable;
public class PocketSpeaker extends AbstractPocketUpgrade {
public PocketSpeaker(ResourceLocation id, ItemStack item) {
public class PocketSpeaker extends AbstractPocketUpgrade.StackAware {
public PocketSpeaker(ResourceLocation id, Ingredient item) {
super(id, UpgradeSpeakerPeripheral.ADJECTIVE, item);
}
@@ -203,7 +203,7 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
}
private static @Nullable UpgradeData<ITurtleUpgrade> withPersistedData(@Nullable UpgradeData<ITurtleUpgrade> upgrade) {
return upgrade == null ? null : UpgradeData.of(upgrade.upgrade(), upgrade.upgrade().getPersistedData(upgrade.data()));
return upgrade == null ? null : UpgradeData.of(upgrade.upgrade(), upgrade.getUpgradeItem(), upgrade.upgrade().getPersistedData(upgrade.data()));
}
@Override
@@ -13,9 +13,11 @@ import dan200.computercraft.api.turtle.TurtleAnimation;
import dan200.computercraft.api.turtle.TurtleCommand;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeHandle;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.util.PeripheralHelpers;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
@@ -48,10 +50,8 @@ import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR;
import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED;
public class TurtleBrain implements TurtleAccessInternal {
public static final String NBT_RIGHT_UPGRADE = "RightUpgrade";
public static final String NBT_RIGHT_UPGRADE_DATA = "RightUpgradeNbt";
public static final String NBT_LEFT_UPGRADE = "LeftUpgrade";
public static final String NBT_LEFT_UPGRADE_DATA = "LeftUpgradeNbt";
public static final UpgradeManager.UpgradeKey NBT_RIGHT_UPGRADE = new UpgradeManager.UpgradeKey("RightUpgrade", "RightUpgradeNbt");
public static final UpgradeManager.UpgradeKey NBT_LEFT_UPGRADE = new UpgradeManager.UpgradeKey("LeftUpgrade", "LeftUpgradeNbt");
public static final String NBT_FUEL = "Fuel";
public static final String NBT_OVERLAY = "Overlay";
@@ -137,16 +137,8 @@ public class TurtleBrain implements TurtleAccessInternal {
overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null;
// Read upgrades
setUpgradeDirect(TurtleSide.LEFT, readUpgrade(nbt, NBT_LEFT_UPGRADE, NBT_LEFT_UPGRADE_DATA));
setUpgradeDirect(TurtleSide.RIGHT, readUpgrade(nbt, NBT_RIGHT_UPGRADE, NBT_RIGHT_UPGRADE_DATA));
}
private @Nullable UpgradeData<ITurtleUpgrade> readUpgrade(CompoundTag tag, String upgradeKey, String dataKey) {
if (!tag.contains(upgradeKey)) return null;
var upgrade = TurtleUpgrades.instance().get(tag.getString(upgradeKey));
if (upgrade == null) return null;
return UpgradeData.of(upgrade, tag.getCompound(dataKey));
setUpgradeDirect(TurtleSide.LEFT, TurtleUpgrades.instance().readUpgrade(nbt, NBT_LEFT_UPGRADE));
setUpgradeDirect(TurtleSide.RIGHT, TurtleUpgrades.instance().readUpgrade(nbt, NBT_RIGHT_UPGRADE));
}
private void writeCommon(CompoundTag nbt) {
@@ -155,18 +147,8 @@ public class TurtleBrain implements TurtleAccessInternal {
if (overlay != null) nbt.putString(NBT_OVERLAY, overlay.toString());
// Write upgrades
var leftUpgradeId = getUpgradeId(getUpgrade(TurtleSide.LEFT));
if (leftUpgradeId != null) nbt.putString(NBT_LEFT_UPGRADE, leftUpgradeId);
var rightUpgradeId = getUpgradeId(getUpgrade(TurtleSide.RIGHT));
if (rightUpgradeId != null) nbt.putString(NBT_RIGHT_UPGRADE, rightUpgradeId);
// Write upgrade NBT
if (upgradeNBTData.containsKey(TurtleSide.LEFT)) {
nbt.put(NBT_LEFT_UPGRADE_DATA, getUpgradeNBTData(TurtleSide.LEFT).copy());
}
if (upgradeNBTData.containsKey(TurtleSide.RIGHT)) {
nbt.put(NBT_RIGHT_UPGRADE_DATA, getUpgradeNBTData(TurtleSide.RIGHT).copy());
}
UpgradeManager.writeUpgrade(nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT));
UpgradeManager.writeUpgrade(nbt, NBT_RIGHT_UPGRADE, getUpgradeWithData(TurtleSide.RIGHT));
}
public void readFromNBT(CompoundTag nbt) {
@@ -206,10 +188,6 @@ public class TurtleBrain implements TurtleAccessInternal {
return nbt;
}
private static @Nullable String getUpgradeId(@Nullable ITurtleUpgrade upgrade) {
return upgrade != null ? upgrade.getUpgradeID().toString() : null;
}
public void readDescription(CompoundTag nbt) {
readCommon(nbt);
@@ -522,6 +500,16 @@ public class TurtleBrain implements TurtleAccessInternal {
return peripherals.get(side);
}
@Override
public @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public @Nullable UpgradeHandle<ITurtleUpgrade> getUpgradeHandle(TurtleSide side) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public CompoundTag getUpgradeNBTData(TurtleSide side) {
var nbt = upgradeNBTData.get(side);
@@ -10,10 +10,10 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
@@ -43,17 +43,8 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
if (fuelLevel > 0) stack.getOrCreateTag().putInt(NBT_FUEL, fuelLevel);
if (overlay != null) stack.getOrCreateTag().putString(NBT_OVERLAY, overlay.toString());
if (leftUpgrade != null) {
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) {
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());
}
if (leftUpgrade != null) UpgradeManager.writeUpgrade(stack.getOrCreateTag(), NBT_LEFT_UPGRADE, leftUpgrade);
if (rightUpgrade != null) UpgradeManager.writeUpgrade(stack.getOrCreateTag(), NBT_RIGHT_UPGRADE, rightUpgrade);
return stack;
}
@@ -113,24 +104,15 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
}
public @Nullable ITurtleUpgrade getUpgrade(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;
return TurtleUpgrades.instance().get(tag.getString(key));
var upgrade = getUpgradeWithData(stack, side);
return upgrade == null ? null : upgrade.upgrade();
}
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));
return tag == null ? null : TurtleUpgrades.instance().readUpgrade(
tag, side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE
);
}
public @Nullable ResourceLocation getOverlay(ItemStack stack) {
@@ -10,12 +10,12 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
public class TurtleCraftingTable extends AbstractTurtleUpgrade {
public TurtleCraftingTable(ResourceLocation id, ItemStack stack) {
super(id, TurtleUpgradeType.PERIPHERAL, "upgrade.minecraft.crafting_table.adjective", stack);
public class TurtleCraftingTable extends AbstractTurtleUpgrade.StackAware {
public TurtleCraftingTable(ResourceLocation id, Ingredient item) {
super(id, TurtleUpgradeType.PERIPHERAL, "upgrade.minecraft.crafting_table.adjective", item);
}
@Override
@@ -11,12 +11,12 @@ import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeriph
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
public class TurtleModem extends AbstractTurtleUpgrade {
public class TurtleModem extends AbstractTurtleUpgrade.StackAware {
private static class Peripheral extends WirelessModemPeripheral {
private final ITurtleAccess turtle;
@@ -48,8 +48,8 @@ public class TurtleModem extends AbstractTurtleUpgrade {
private final boolean advanced;
public TurtleModem(ResourceLocation id, ItemStack stack, boolean advanced) {
super(id, TurtleUpgradeType.PERIPHERAL, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, stack);
public TurtleModem(ResourceLocation id, Ingredient item, boolean advanced) {
super(id, TurtleUpgradeType.PERIPHERAL, advanced ? WirelessModemPeripheral.ADVANCED_ADJECTIVE : WirelessModemPeripheral.NORMAL_ADJECTIVE, item);
this.advanced = advanced;
}
@@ -66,15 +66,16 @@ public class TurtleModem extends AbstractTurtleUpgrade {
@Override
public void update(ITurtleAccess turtle, TurtleSide side) {
// Advance the modem
if (!turtle.getLevel().isClientSide) {
var peripheral = turtle.getPeripheral(side);
if (peripheral instanceof Peripheral modem) {
var state = modem.getModemState();
if (state.pollChanged()) {
turtle.getUpgradeNBTData(side).putBoolean("active", state.isOpen());
turtle.updateUpgradeNBTData(side);
}
}
if (turtle.getLevel().isClientSide) return;
var peripheral = turtle.getPeripheral(side);
var handle = turtle.getUpgradeHandle(side);
if (!(peripheral instanceof Peripheral modem) || handle == null) return;
var state = modem.getModemState();
if (state.pollChanged()) {
handle.getData().putBoolean("active", state.isOpen());
handle.setChanged();
}
}
@@ -12,11 +12,11 @@ import dan200.computercraft.api.turtle.TurtleUpgradeType;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
public class TurtleSpeaker extends AbstractTurtleUpgrade {
public class TurtleSpeaker extends AbstractTurtleUpgrade.StackAware {
private static class Peripheral extends UpgradeSpeakerPeripheral {
final ITurtleAccess turtle;
@@ -35,7 +35,7 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade {
}
}
public TurtleSpeaker(ResourceLocation id, ItemStack item) {
public TurtleSpeaker(ResourceLocation id, Ingredient item) {
super(id, TurtleUpgradeType.PERIPHERAL, UpgradeSpeakerPeripheral.ADJECTIVE, item);
}
@@ -7,16 +7,15 @@ package dan200.computercraft.shared.turtle.upgrades;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.AbstractTurtleUpgrade;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import dan200.computercraft.api.turtle.*;
import dan200.computercraft.api.upgrades.UpgradeHandle;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.platform.ItemContainer;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.jspecify.annotations.Nullable;
import java.util.Map;
@@ -27,26 +26,15 @@ import static net.minecraft.nbt.Tag.TAG_COMPOUND;
/**
* Turtle storage upgrade.
*/
public final class TurtleStorage extends AbstractTurtleUpgrade {
public final class TurtleStorage extends AbstractTurtleUpgrade.StackAware {
private static final String TAG_ITEM_TAG = "Tag";
public static final String ADJECTIVE = "upgrade.computercraft.storage.adjective";
public static final String DATA_PACK_NAME = "turtle_storage";
public static final String DATA_PACK_TRANSLATION = "dataPack." + DATA_PACK_NAME + ".description";
public TurtleStorage(ResourceLocation id, ItemStack stack) {
super(id, TurtleUpgradeType.PERIPHERAL, ADJECTIVE, stack);
}
@Override
public CompoundTag getUpgradeData(ItemStack stack) {
var upgradeData = super.getUpgradeData(stack);
// Store the item's current tag.
var itemTag = stack.getTag();
if (itemTag != null) upgradeData.put(TAG_ITEM_TAG, itemTag);
return upgradeData;
public TurtleStorage(ResourceLocation id, Ingredient item) {
super(id, TurtleUpgradeType.PERIPHERAL, ADJECTIVE, item);
}
@Override
@@ -87,12 +75,16 @@ public final class TurtleStorage extends AbstractTurtleUpgrade {
this.side = side;
}
private ItemStack getStack() throws LuaException {
var upgrade = turtle.getUpgradeWithData(side);
if (upgrade == null || !(upgrade.upgrade() instanceof TurtleStorage)) {
private UpgradeHandle<ITurtleUpgrade> getHandle() throws LuaException {
var upgrade = turtle.getUpgradeHandle(side);
if (upgrade == null || !(upgrade.getUpgrade() instanceof TurtleStorage)) {
throw new LuaException("Upgrade is not equipped");
}
return upgrade.getUpgradeItem();
return upgrade;
}
private ItemStack getStack() throws LuaException {
return getHandle().getItem();
}
private static ItemContainer getContainer(ItemStack stack) throws LuaException {
@@ -157,8 +149,9 @@ public final class TurtleStorage extends AbstractTurtleUpgrade {
*/
@LuaFunction(mainThread = true)
public int pushItems(int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot) throws LuaException {
var stack = getStack();
return moveAndUpdate(stack, getContainer(stack), fromSlot, turtleInventory, toSlot, limit);
var handle = getHandle();
var stack = handle.getItem();
return moveAndUpdate(handle, stack, getContainer(stack), fromSlot, turtleInventory, toSlot, limit);
}
/**
@@ -172,16 +165,14 @@ public final class TurtleStorage extends AbstractTurtleUpgrade {
*/
@LuaFunction(mainThread = true)
public int pullItems(Optional<Integer> fromSlot, Optional<Integer> limit, Optional<Integer> toSlot) throws LuaException {
var stack = getStack();
return moveAndUpdate(stack, turtleInventory, fromSlot.orElse(turtle.getSelectedSlot() + 1), getContainer(stack), toSlot, limit);
var handle = getHandle();
var stack = handle.getItem();
return moveAndUpdate(handle, stack, turtleInventory, fromSlot.orElse(turtle.getSelectedSlot() + 1), getContainer(stack), toSlot, limit);
}
private int moveAndUpdate(ItemStack stack, ItemContainer from, int fromSlot, ItemContainer to, Optional<Integer> toSlot, Optional<Integer> limit) throws LuaException {
private int moveAndUpdate(UpgradeHandle<ITurtleUpgrade> handle, ItemStack stack, ItemContainer from, int fromSlot, ItemContainer to, Optional<Integer> toSlot, Optional<Integer> limit) throws LuaException {
var moved = AbstractInventoryMethods.move(from, fromSlot, to, toSlot, limit);
if (moved > 0) {
turtle.getUpgradeNBTData(side).put(TAG_ITEM_TAG, stack.getOrCreateTag());
turtle.updateUpgradeNBTData(side);
}
if (moved > 0) handle.setItem(stack);
return moved;
}
@@ -6,6 +6,7 @@ package dan200.computercraft.shared.turtle.upgrades;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.turtle.*;
import dan200.computercraft.api.upgrades.UpgradeHandle;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.TurtleUtil;
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
@@ -27,9 +28,9 @@ import net.minecraft.world.entity.MobType;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
@@ -39,30 +40,27 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.EntityHitResult;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
import java.util.function.Function;
import static net.minecraft.nbt.Tag.TAG_COMPOUND;
import static net.minecraft.nbt.Tag.TAG_LIST;
public class TurtleTool extends AbstractTurtleUpgrade {
public class TurtleTool extends AbstractTurtleUpgrade.StackAware {
private static final TurtleCommandResult UNBREAKABLE = TurtleCommandResult.failure("Cannot break unbreakable block");
private static final TurtleCommandResult INEFFECTIVE = TurtleCommandResult.failure("Cannot break block with this tool");
private static final String TAG_ITEM_TAG = "Tag";
final ItemStack item;
final float damageMultiplier;
final boolean allowEnchantments;
final TurtleToolDurability consumeDurability;
final @Nullable TagKey<Block> breakable;
public TurtleTool(
ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMultiplier,
ResourceLocation id, String adjective, Ingredient item, float damageMultiplier,
boolean allowEnchantments, TurtleToolDurability consumeDurability, @Nullable TagKey<Block> breakable
) {
super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem));
item = toolItem;
super(id, TurtleUpgradeType.TOOL, adjective, item);
this.damageMultiplier = damageMultiplier;
this.allowEnchantments = allowEnchantments;
this.consumeDurability = consumeDurability;
@@ -86,17 +84,6 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|| (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty());
}
@Override
public CompoundTag getUpgradeData(ItemStack stack) {
var upgradeData = super.getUpgradeData(stack);
// Store the item's current tag.
var itemTag = stack.getTag();
if (itemTag != null) upgradeData.put(TAG_ITEM_TAG, itemTag);
return upgradeData;
}
@Override
public ItemStack getUpgradeItem(CompoundTag upgradeData) {
// Copy upgrade data back to the item.
@@ -105,51 +92,37 @@ public class TurtleTool extends AbstractTurtleUpgrade {
return item;
}
private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) {
return getUpgradeItem(turtle.getUpgradeNBTData(side)).copy();
}
private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) {
var upgradeData = turtle.getUpgradeNBTData(side);
private void setToolStack(UpgradeHandle<ITurtleUpgrade> upgrade, ItemStack stack) {
var oldStack = upgrade.getItem();
var useDurability = switch (consumeDurability) {
case NEVER -> false;
case WHEN_ENCHANTED ->
upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) && isEnchanted(upgradeData.getCompound(TAG_ITEM_TAG));
case WHEN_ENCHANTED -> isEnchanted(oldStack);
case ALWAYS -> true;
};
if (!useDurability) return;
// If the tool has broken, remove the upgrade!
if (stack.isEmpty()) {
turtle.setUpgradeWithData(side, null);
upgrade.clear();
return;
}
// If the tool has changed, no clue what's going on.
if (stack.getItem() != item.getItem()) return;
var itemTag = stack.getTag();
if (!getCraftingIngredient().test(stack) || !isItemSuitable(stack)) return;
// Early return if the item hasn't changed to avoid redundant syncs with the client.
if (Objects.equals(itemTag, upgradeData.get(TAG_ITEM_TAG))) return;
if (ItemStack.isSameItemSameTags(stack, oldStack)) return;
if (itemTag == null) {
upgradeData.remove(TAG_ITEM_TAG);
} else {
upgradeData.put(TAG_ITEM_TAG, itemTag);
}
turtle.updateUpgradeNBTData(side);
upgrade.setItem(stack);
}
private <T> T withEquippedItem(ITurtleAccess turtle, TurtleSide side, Direction direction, Function<TurtlePlayer, T> action) {
private <T> T withEquippedItem(ITurtleAccess turtle, UpgradeHandle<ITurtleUpgrade> upgrade, Direction direction, Function<TurtlePlayer, T> action) {
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtle.getPosition(), direction);
turtlePlayer.loadInventory(getToolStack(turtle, side));
turtlePlayer.loadInventory(upgrade.getItem());
var result = action.apply(turtlePlayer);
setToolStack(turtle, side, turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND));
setToolStack(upgrade, turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND));
turtlePlayer.player().getInventory().clearContent();
return result;
@@ -157,9 +130,12 @@ public class TurtleTool extends AbstractTurtleUpgrade {
@Override
public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
var handle = turtle.getUpgradeHandle(side);
if (handle == null) return TurtleCommandResult.failure("Upgrade is not equipped");
return switch (verb) {
case ATTACK -> attack(turtle, side, direction);
case DIG -> dig(turtle, side, direction);
case ATTACK -> attack(turtle, handle, direction);
case DIG -> dig(turtle, handle, direction);
};
}
@@ -177,11 +153,10 @@ public class TurtleTool extends AbstractTurtleUpgrade {
* Attack an entity.
*
* @param turtle The current turtle.
* @param side The side the tool is on.
* @param direction The direction we're attacking in.
* @return Whether an attack occurred.
*/
private TurtleCommandResult attack(ITurtleAccess turtle, TurtleSide side, Direction direction) {
private TurtleCommandResult attack(ITurtleAccess turtle, UpgradeHandle<ITurtleUpgrade> upgrade, Direction direction) {
// Create a fake player, and orient it appropriately
var world = turtle.getLevel();
var position = turtle.getPosition();
@@ -196,8 +171,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
var attacked = false;
if (hit instanceof EntityHitResult entityHit) {
// Load up the turtle's inventory
var stack = getToolStack(turtle, side);
turtlePlayer.loadInventory(stack);
turtlePlayer.loadInventory(upgrade.getItem());
var hitEntity = entityHit.getEntity();
@@ -216,7 +190,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
TurtleUtil.stopConsuming(turtle);
// Put everything we collected into the turtles inventory.
setToolStack(turtle, side, player.getItemInHand(InteractionHand.MAIN_HAND));
setToolStack(upgrade, player.getItemInHand(InteractionHand.MAIN_HAND));
player.getInventory().clearContent();
}
@@ -292,10 +266,10 @@ public class TurtleTool extends AbstractTurtleUpgrade {
return true;
}
private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction direction) {
private TurtleCommandResult dig(ITurtleAccess turtle, UpgradeHandle<ITurtleUpgrade> upgrade, Direction direction) {
var level = (ServerLevel) turtle.getLevel();
return withEquippedItem(turtle, side, direction, turtlePlayer -> {
return withEquippedItem(turtle, upgrade, direction, turtlePlayer -> {
var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND);
// Right-click the block when using a shovel/hoe. Important that we do this before checking the block is
@@ -8,17 +8,15 @@ import com.google.gson.JsonObject;
import dan200.computercraft.api.turtle.TurtleToolDurability;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingIngredient;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.block.Block;
import java.util.Objects;
public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<TurtleTool> {
public static final TurtleToolSerialiser INSTANCE = new TurtleToolSerialiser();
@@ -28,8 +26,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
@Override
public TurtleTool fromJson(ResourceLocation id, JsonObject object) {
var adjective = GsonHelper.getAsString(object, "adjective", UpgradeBase.getDefaultAdjective(id));
var toolItem = GsonHelper.getAsItem(object, "item");
var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem);
var item = SerialiserWithCraftingIngredient.getIngredientOrItem(object, "item");
var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f);
var allowEnchantments = GsonHelper.getAsBoolean(object, "allowEnchantments", false);
var consumeDurability = TurtleToolDurability.CODEC.byName(GsonHelper.getAsString(object, "consumeDurability", null), TurtleToolDurability.NEVER);
@@ -40,15 +38,13 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
breakable = TagKey.create(Registries.BLOCK, tag);
}
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowEnchantments, consumeDurability, breakable);
return new TurtleTool(id, adjective, item, damageMultiplier, allowEnchantments, consumeDurability, breakable);
}
@Override
public TurtleTool fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
var adjective = buffer.readUtf();
var craftingItem = buffer.readById(RegistryWrappers.ITEMS);
Objects.requireNonNull(craftingItem, "Unknown crafting item");
var toolItem = buffer.readItem();
var item = Ingredient.fromNetwork(buffer);
// damageMultiplier and breakable aren't used by the client, but we need to construct the upgrade exactly
// as otherwise syncing on an SP world will overwrite the (shared) upgrade registry with an invalid upgrade!
var damageMultiplier = buffer.readFloat();
@@ -56,14 +52,13 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
var consumesDurability = buffer.readEnum(TurtleToolDurability.class);
var breakable = buffer.readNullable(b -> TagKey.create(Registries.BLOCK, b.readResourceLocation()));
return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, allowsEnchantments, consumesDurability, breakable);
return new TurtleTool(id, adjective, item, damageMultiplier, allowsEnchantments, consumesDurability, breakable);
}
@Override
public void toNetwork(FriendlyByteBuf buffer, TurtleTool upgrade) {
buffer.writeUtf(upgrade.getUnlocalisedAdjective());
buffer.writeId(RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
buffer.writeItem(upgrade.item);
upgrade.getCraftingIngredient().toNetwork(buffer);
buffer.writeFloat(upgrade.damageMultiplier);
buffer.writeBoolean(upgrade.allowEnchantments);
buffer.writeEnum(upgrade.consumeDurability);
@@ -44,8 +44,7 @@ class TurtleToolSerialiserTest {
return Combinators.combine(
MinecraftArbitraries.resourceLocation(),
Arbitraries.strings().ofMaxLength(100),
MinecraftArbitraries.item(),
MinecraftArbitraries.itemStack(),
MinecraftArbitraries.ingredient(),
Arbitraries.floats(),
Arbitraries.of(true, false),
Arbitraries.of(TurtleToolDurability.values()),
@@ -55,8 +54,7 @@ class TurtleToolSerialiserTest {
private static final StructuralEquality<TurtleTool> equality = StructuralEquality.all(
StructuralEquality.at("id", ITurtleUpgrade::getUpgradeID),
StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingItem, MinecraftEqualities.itemStack),
StructuralEquality.at("tool", x -> x.item, MinecraftEqualities.itemStack),
StructuralEquality.at("craftingItem", ITurtleUpgrade::getCraftingIngredient, MinecraftEqualities.ingredient),
StructuralEquality.at("damageMultiplier", x -> x.damageMultiplier),
StructuralEquality.at("allowEnchantments", x -> x.allowEnchantments),
StructuralEquality.at("consumeDurability", x -> x.consumeDurability),
@@ -0,0 +1 @@
{"type": "computercraft:storage", "item": {"tag": "c:shulker_boxes"}}
@@ -246,7 +246,8 @@ public class PlatformHelperImpl implements PlatformHelper {
Ingredient.of(MoreConventionalTags.SKULLS),
Ingredient.of(ConventionalItemTags.DYES),
Ingredient.of(Items.ENDER_PEARL),
Ingredient.of(MoreConventionalTags.WOODEN_CHESTS)
Ingredient.of(MoreConventionalTags.WOODEN_CHESTS),
Ingredient.of(ConventionalItemTags.SHULKER_BOXES)
);
}
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.platform.ForgeConventionalTags;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.PackOutput;
import net.minecraft.data.tags.ItemTagsProvider;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.common.data.ExistingFileHelper;
import java.util.concurrent.CompletableFuture;
public class ForgeConventionalTagsProvider extends ItemTagsProvider {
public ForgeConventionalTagsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider, CompletableFuture<TagLookup<Block>> blockTags, ExistingFileHelper existingFileHelper) {
super(output, lookupProvider, blockTags, ComputerCraftAPI.MOD_ID, existingFileHelper);
}
@Override
protected void addTags(HolderLookup.Provider provider) {
tag(ForgeConventionalTags.SHULKER_BOX).add(
Items.SHULKER_BOX,
Items.WHITE_SHULKER_BOX,
Items.ORANGE_SHULKER_BOX,
Items.MAGENTA_SHULKER_BOX,
Items.LIGHT_BLUE_SHULKER_BOX,
Items.YELLOW_SHULKER_BOX,
Items.LIME_SHULKER_BOX,
Items.PINK_SHULKER_BOX,
Items.GRAY_SHULKER_BOX,
Items.LIGHT_GRAY_SHULKER_BOX,
Items.CYAN_SHULKER_BOX,
Items.PURPLE_SHULKER_BOX,
Items.BLUE_SHULKER_BOX,
Items.BROWN_SHULKER_BOX,
Items.GREEN_SHULKER_BOX,
Items.RED_SHULKER_BOX,
Items.BLACK_SHULKER_BOX
);
}
@Override
public String getName() {
return "Forge Conventional Item Tags";
}
}
@@ -36,7 +36,11 @@ public class ForgeDataProviders {
@SubscribeEvent
public static void gather(GatherDataEvent event) {
var generator = event.getGenerator();
DataProviders.add(new GeneratorFactoryImpl(generator, generator.getVanillaPack(true), event.getExistingFileHelper(), event.getLookupProvider()));
var pack = generator.getVanillaPack(true);
DataProviders.add(new GeneratorFactoryImpl(generator, pack, event.getExistingFileHelper(), event.getLookupProvider()));
pack.addProvider(o -> new ForgeConventionalTagsProvider(
o, event.getLookupProvider(), CompletableFuture.completedFuture(TagsProvider.TagLookup.empty()), event.getExistingFileHelper()
));
}
private record GeneratorFactoryImpl(
@@ -0,0 +1,21 @@
{
"values": [
"minecraft:shulker_box",
"minecraft:white_shulker_box",
"minecraft:orange_shulker_box",
"minecraft:magenta_shulker_box",
"minecraft:light_blue_shulker_box",
"minecraft:yellow_shulker_box",
"minecraft:lime_shulker_box",
"minecraft:pink_shulker_box",
"minecraft:gray_shulker_box",
"minecraft:light_gray_shulker_box",
"minecraft:cyan_shulker_box",
"minecraft:purple_shulker_box",
"minecraft:blue_shulker_box",
"minecraft:brown_shulker_box",
"minecraft:green_shulker_box",
"minecraft:red_shulker_box",
"minecraft:black_shulker_box"
]
}
@@ -0,0 +1 @@
{"type": "computercraft:storage", "item": {"tag": "c:shulker_box"}}
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.platform;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
/**
* {@code ConventionalItemTags} for Forge for cases where there's no existing one.
*/
public class ForgeConventionalTags {
public static final TagKey<Item> SHULKER_BOX = item("shulker_box");
private static TagKey<Item> item(String name) {
return TagKey.create(Registries.ITEM, new ResourceLocation("c", name));
}
}
@@ -250,7 +250,8 @@ public class PlatformHelperImpl implements PlatformHelper {
Ingredient.of(Tags.Items.HEADS),
Ingredient.of(Tags.Items.DYES),
Ingredient.of(Tags.Items.ENDER_PEARLS),
Ingredient.of(Tags.Items.CHESTS_WOODEN)
Ingredient.of(Tags.Items.CHESTS_WOODEN),
Ingredient.of(ForgeConventionalTags.SHULKER_BOX)
);
}