1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-16 18:19:55 +00:00

Allow upgrades to read/write upgrade data from ItemStacks (#1465)

This commit is contained in:
Edvin 2023-07-02 12:55:55 +03:00 committed by GitHub
parent 94f5ede75a
commit f54cb8a432
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 455 additions and 113 deletions

View File

@ -11,6 +11,7 @@
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
@ -28,12 +29,27 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
*
* @param upgrade The upgrade that you're getting the model for.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
*
* @param upgrade The upgrade that you're getting the model for.
* @param data Upgrade data instance for current turtle side.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade, (ITurtleAccess) null, side);
}
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
* crafting item}.

View File

@ -5,9 +5,11 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Map;
@ -69,6 +71,8 @@ public interface IPocketAccess {
*
* @return The upgrade's NBT.
* @see #updateUpgradeNBTData()
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
CompoundTag getUpgradeNBTData();

View File

@ -8,10 +8,13 @@
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 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 net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
@ -245,23 +248,51 @@ public interface ITurtleAccess {
void playAnimation(TurtleAnimation animation);
/**
* Returns the turtle on the specified side of the turtle, if there is one.
* Returns the upgrade on the specified side of the turtle, if there is one.
*
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, if there is one.
* @see #setUpgrade(TurtleSide, ITurtleUpgrade)
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/
@Nullable
ITurtleUpgrade getUpgrade(TurtleSide side);
/**
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
* update data}.
*
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/
default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
var upgrade = getUpgrade(side);
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
}
/**
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgrade(TurtleSide)
* @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
*/
void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
@Deprecated
default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
}
/**
* Set the upgrade for a given side and its upgrade data.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgradeWithData(TurtleSide)
*/
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
@ -281,6 +312,8 @@ 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)
*/
CompoundTag getUpgradeNBTData(TurtleSide side);

View File

@ -7,6 +7,7 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import javax.annotation.Nullable;
@ -79,4 +80,17 @@ default TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, Turtl
*/
default void update(ITurtleAccess turtle, TurtleSide side) {
}
/**
* Get upgrade data that should be persisted when the turtle was broken.
* <p>
* This method should be overridden when you don't need to store all upgrade data by default. For instance, if you
* store peripheral state in the upgrade data, which should be lost when the turtle is broken.
*
* @param upgradeData Data that currently stored for this upgrade
* @return Filtered version of this data.
*/
default CompoundTag getPersistedData(CompoundTag upgradeData) {
return upgradeData;
}
}

View File

@ -4,10 +4,14 @@
package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@ -50,6 +54,42 @@ public interface UpgradeBase {
*/
ItemStack getCraftingItem();
/**
* Returns the item stack representing a currently equipped turtle upgrade.
* <p>
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
* and the original item stack is returned.
* <p>
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
* {@linkplain #getUpgradeData(ItemStack) re-create the upgrade data} if the item is re-equipped.
* <p>
* When overriding this, you should override {@link #getUpgradeData(ItemStack)} and {@link #isItemSuitable(ItemStack)}
* at the same time,
*
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
* @return The item stack returned when unequipping.
*/
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
return getCraftingItem();
}
/**
* Extract upgrade data from an {@link ItemStack}.
* <p>
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeNBTData()}.
* <p>
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
*
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
* {@link #getCraftingItem()}.
* @return The upgrade data that should be set on the turtle or pocket computer.
*/
default CompoundTag getUpgradeData(ItemStack stack) {
return new CompoundTag();
}
/**
* Determine if an item is suitable for being used for this upgrade.
* <p>

View File

@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
/**
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
* <p>
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
*
* @param upgrade The current upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
* @param upgrade An upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance.
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
return new UpgradeData<>(upgrade, data);
}
/**
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
*
* @param upgrade The upgrade instance.
* @param <T> The type of upgrade.
* @return The default upgrade data.
*/
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
}
/**
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
*
* @param upgrade The copied upgrade data.
* @param <T> The type of upgrade.
* @return The newly created upgrade data.
*/
@Contract("!null -> !null; null -> null")
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
return upgrade == null ? null : upgrade.copy();
}
/**
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
* <p>
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
*
* @return This upgrade's item.
*/
public ItemStack getUpgradeItem() {
return upgrade.getUpgradeItem(data).copy();
}
/**
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
* the upgrade data.
*
* @return A copy of the current instance.
*/
public UpgradeData<T> copy() {
return new UpgradeData<>(upgrade(), data().copy());
}
}

View File

@ -4,11 +4,13 @@
package dan200.computercraft.client.model.turtle;
import com.google.common.cache.CacheBuilder;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
@ -21,9 +23,9 @@
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
@ -46,12 +48,19 @@ public final class TurtleModelParts<T> {
private record Combination(
boolean colour,
@Nullable ITurtleUpgrade leftUpgrade,
@Nullable ITurtleUpgrade rightUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
@Nullable ResourceLocation overlay,
boolean christmas,
boolean flip
) {
Combination copy() {
if (leftUpgrade == null && rightUpgrade == null) return this;
return new Combination(
colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade),
overlay, christmas, flip
);
}
}
private final BakedModel familyModel;
@ -63,12 +72,20 @@ private record Combination(
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
*/
private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(30, TimeUnit.SECONDS)
.<TransformedModel, BakedModel>build()
.asMap();
/**
* A cache of {@link Combination}s to the combined model.
*/
private final Map<Combination, T> modelCache = new HashMap<>();
private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.expireAfterAccess(30, TimeUnit.SECONDS)
.<Combination, T>build()
.asMap();
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
this.familyModel = familyModel;
@ -78,7 +95,15 @@ public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTra
}
public T getModel(ItemStack stack) {
return modelCache.computeIfAbsent(getCombination(stack), buildModel);
var combination = getCombination(stack);
var existing = modelCache.get(combination);
if (existing != null) return existing;
// Take a defensive copy of the upgrade data, and add it to the cache.
var newCombination = combination.copy();
var newModel = buildModel.apply(newCombination);
modelCache.put(newCombination, newModel);
return newModel;
}
private Combination getCombination(ItemStack stack) {
@ -89,8 +114,8 @@ private Combination getCombination(ItemStack stack) {
}
var colour = turtle.getColour(stack);
var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
var rightUpgrade = turtle.getUpgrade(stack, TurtleSide.RIGHT);
var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
var overlay = turtle.getOverlay(stack);
var label = turtle.getLabel(stack);
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
@ -110,18 +135,19 @@ private List<BakedModel> buildModel(Combination combo) {
if (overlayModelLocation != null) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
}
if (combo.leftUpgrade() != null) {
var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
if (combo.rightUpgrade() != null) {
var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
return parts;
}
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) return;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
private BakedModel transform(BakedModel model, Transformation transformation) {
if (transformation.equals(Transformation.identity())) return model;
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);

View File

@ -14,8 +14,8 @@
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
@ -52,12 +52,18 @@ public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T
}
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess access, TurtleSide side) {
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, access, side);
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, data, side);
}
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
if (wrapper == null) return NULL_TURTLE_MODELLER;

View File

@ -8,6 +8,7 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
@ -110,7 +111,7 @@ private void turtleUpgrades(Consumer<FinishedRecipe> add) {
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
var result = turtleItem.create(-1, null, -1, null, upgrade, -1, null);
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
@ -146,7 +147,7 @@ private void pocketUpgrades(Consumer<FinishedRecipe> add) {
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
var result = pocket.create(-1, null, -1, upgrade);
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))

View File

@ -7,6 +7,7 @@
import com.google.gson.*;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.Registry;
@ -74,13 +75,13 @@ public String getOwner(T upgrade) {
}
@Nullable
public T get(ItemStack stack) {
public UpgradeData<T> get(ItemStack stack) {
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 wrapper.upgrade();
return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack));
}
}

View File

@ -11,6 +11,7 @@
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
@ -446,12 +447,12 @@ public static CreativeModeTab.Builder registerCreativeTab(CreativeModeTab.Builde
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
TurtleUpgrades.getVanillaUpgrades()
.map(x -> turtle.create(-1, null, -1, null, x, 0, null))
.map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null))
.forEach(out::accept);
}
private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
out.accept(pocket.create(-1, null, -1, null));
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, x)).forEach(out::accept);
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept);
}
}

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.integration;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
@ -56,14 +57,14 @@ public static List<ItemStack> getExtraStacks() {
for (var turtleSupplier : TURTLES) {
var turtle = turtleSupplier.get();
for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
upgradeItems.add(turtle.create(-1, null, -1, null, upgrade, 0, null));
upgradeItems.add(turtle.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), 0, null));
}
}
for (var pocketSupplier : POCKET_COMPUTERS) {
var pocket = pocketSupplier.get();
for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
upgradeItems.add(pocket.create(-1, null, -1, upgrade));
upgradeItems.add(pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)));
}
}

View File

@ -9,6 +9,7 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
@ -111,20 +112,22 @@ public List<T> findRecipesWithInput(ItemStack stack) {
if (stack.getItem() instanceof TurtleItem item) {
// Suggest possible upgrades which can be applied to this turtle
var left = item.getUpgrade(stack, TurtleSide.LEFT);
var right = item.getUpgrade(stack, TurtleSide.RIGHT);
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
if (left != null && right != null) return Collections.emptyList();
List<T> recipes = new ArrayList<>();
var ingredient = Ingredient.of(stack);
for (var upgrade : turtleUpgrades) {
if (upgrade.turtle == null) throw new NullPointerException();
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
if (left == null) {
recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, upgrade.turtle, right)));
recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, UpgradeData.ofDefault(upgrade.turtle), right)));
}
if (right == null) {
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, upgrade.turtle)));
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, UpgradeData.ofDefault(upgrade.turtle))));
}
}
@ -137,7 +140,8 @@ public List<T> findRecipesWithInput(ItemStack stack) {
List<T> recipes = new ArrayList<>();
var ingredient = Ingredient.of(stack);
for (var upgrade : pocketUpgrades) {
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, upgrade.pocket)));
if (upgrade.pocket == null) throw new NullPointerException();
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, UpgradeData.ofDefault(upgrade.pocket))));
}
return Collections.unmodifiableList(recipes);
@ -180,21 +184,21 @@ public List<T> findRecipesWithOutput(ItemStack stack) {
if (stack.getItem() instanceof TurtleItem item) {
List<T> recipes = new ArrayList<>(0);
var left = item.getUpgrade(stack, TurtleSide.LEFT);
var right = item.getUpgrade(stack, TurtleSide.RIGHT);
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
if (left != null) {
recipes.add(turtle(
Ingredient.of(turtleWith(stack, null, right)),
Ingredient.of(left.getCraftingItem()),
Ingredient.of(left.getUpgradeItem()),
stack
));
}
if (right != null) {
recipes.add(turtle(
Ingredient.of(right.getCraftingItem()),
Ingredient.of(right.getUpgradeItem()),
Ingredient.of(turtleWith(stack, left, null)),
stack
));
@ -204,9 +208,9 @@ public List<T> findRecipesWithOutput(ItemStack stack) {
} else if (stack.getItem() instanceof PocketComputerItem) {
List<T> recipes = new ArrayList<>(0);
var back = PocketComputerItem.getUpgrade(stack);
var back = PocketComputerItem.getUpgradeWithData(stack);
if (back != null) {
recipes.add(pocket(Ingredient.of(back.getCraftingItem()), Ingredient.of(pocketWith(stack, null)), stack));
recipes.add(pocket(Ingredient.of(back.getUpgradeItem()), Ingredient.of(pocketWith(stack, null)), stack));
}
return Collections.unmodifiableList(recipes);
@ -215,7 +219,7 @@ public List<T> findRecipesWithOutput(ItemStack stack) {
}
}
private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable ITurtleUpgrade right) {
private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) {
var item = (TurtleItem) stack.getItem();
return item.create(
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
@ -223,7 +227,7 @@ private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade le
);
}
private static ItemStack pocketWith(ItemStack stack, @Nullable IPocketUpgrade back) {
private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) {
var item = (PocketComputerItem) stack.getItem();
return item.create(
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
@ -272,7 +276,7 @@ List<T> getRecipes() {
recipes.add(turtle(
ingredient, // Right upgrade, recipe on left
Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
turtleItem.create(-1, null, -1, null, turtle, 0, null)
turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(turtle), 0, null)
));
}
}
@ -283,7 +287,7 @@ List<T> getRecipes() {
recipes.add(pocket(
ingredient,
Ingredient.of(pocketItem.create(-1, null, -1, null)),
pocketItem.create(-1, null, -1, pocket)
pocketItem.create(-1, null, -1, UpgradeData.ofDefault(pocket))
));
}
}

View File

@ -68,6 +68,13 @@ static PlatformHelper get() {
return (PlatformHelper) dan200.computercraft.impl.PlatformHelper.get();
}
/**
* Check if we're running in a development environment.
*
* @return If we're running in a development environment.
*/
boolean isDevelopmentEnvironment();
/**
* Create a new config builder.
*

View File

@ -7,6 +7,7 @@
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.core.NonNullList;
@ -14,6 +15,7 @@
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* Control the current pocket computer, adding or removing upgrades.
@ -68,7 +70,7 @@ public final Object[] equipBack() {
if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
// Remove the current upgrade
if (previousUpgrade != null) storeItem(player, previousUpgrade.getCraftingItem().copy());
if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
// Set the new upgrade
computer.setUpgrade(newUpgrade);
@ -93,7 +95,7 @@ public final Object[] unequipBack() {
computer.setUpgrade(null);
storeItem(player, previousUpgrade.getCraftingItem().copy());
storeItem(player, previousUpgrade.getUpgradeItem());
return new Object[]{ true };
}
@ -105,13 +107,13 @@ private static void storeItem(Player player, ItemStack stack) {
}
}
private static @Nullable IPocketUpgrade findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable IPocketUpgrade previous) {
private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) {
for (var i = 0; i < inv.size(); i++) {
var invStack = inv.get((i + start) % inv.size());
if (!invStack.isEmpty()) {
var newUpgrade = PocketUpgrades.instance().get(invStack);
if (newUpgrade != null && newUpgrade != previous) {
if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) {
// Consume an item from this stack and exit the loop
invStack = invStack.copy();
invStack.shrink(1);

View File

@ -7,6 +7,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.core.computer.ComputerSide;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@ -109,8 +110,8 @@ public Map<ResourceLocation, IPeripheral> getUpgrades() {
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
}
public @Nullable IPocketUpgrade getUpgrade() {
return upgrade;
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData());
}
/**
@ -120,13 +121,11 @@ public Map<ResourceLocation, IPeripheral> getUpgrades() {
*
* @param upgrade The new upgrade to set it to, may be {@code null}.
*/
public void setUpgrade(@Nullable IPocketUpgrade upgrade) {
if (this.upgrade == upgrade) return;
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
synchronized (this) {
PocketComputerItem.setUpgrade(stack, upgrade);
updateUpgradeNBTData();
this.upgrade = upgrade;
this.upgrade = upgrade == null ? null : upgrade.upgrade();
invalidatePeripheral();
}
}

View File

@ -10,6 +10,7 @@
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.media.IMedia;
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.shared.ModRegistry;
@ -23,6 +24,7 @@
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
@ -58,7 +60,7 @@ public PocketComputerItem(Properties settings, ComputerFamily family) {
this.family = family;
}
public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable IPocketUpgrade upgrade) {
public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
return switch (family) {
case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
@ -66,11 +68,14 @@ public static ItemStack create(int id, @Nullable String label, int colour, Compu
};
}
public ItemStack create(int id, @Nullable String label, int colour, @Nullable IPocketUpgrade upgrade) {
public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
var result = new ItemStack(this);
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.getUpgradeID().toString());
if (upgrade != null) {
result.getOrCreateTag().putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
if (!upgrade.data().isEmpty()) result.getOrCreateTag().put(NBT_UPGRADE_INFO, upgrade.data().copy());
}
if (colour != -1) result.getOrCreateTag().putInt(NBT_COLOUR, colour);
return result;
}
@ -207,7 +212,9 @@ public PocketServerComputer createServerComputer(ServerLevel level, Entity entit
setInstanceID(stack, computer.register());
setSessionID(stack, registry.getSessionID());
computer.updateValues(entity, stack, getUpgrade(stack));
var upgrade = getUpgrade(stack);
computer.updateValues(entity, stack, upgrade);
computer.addAPI(new PocketAPI(computer));
// Only turn on when initially creating the computer, rather than each tick.
@ -244,7 +251,7 @@ public ComputerFamily getFamily() {
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
return create(
getComputerID(stack), getLabel(stack), getColour(stack),
family, getUpgrade(stack)
family, getUpgradeWithData(stack)
);
}
@ -294,20 +301,27 @@ private static boolean isMarkedOn(ItemStack stack) {
public static @Nullable IPocketUpgrade getUpgrade(ItemStack stack) {
var compound = stack.getTag();
return compound != null && compound.contains(NBT_UPGRADE)
? PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE)) : null;
if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
return PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
}
public static void setUpgrade(ItemStack stack, @Nullable IPocketUpgrade upgrade) {
public static @Nullable UpgradeData<IPocketUpgrade> getUpgradeWithData(ItemStack stack) {
var compound = stack.getTag();
if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
var upgrade = PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
return upgrade == null ? null : UpgradeData.of(upgrade, NBTUtil.getCompoundOrEmpty(compound, NBT_UPGRADE_INFO));
}
public static void setUpgrade(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
var compound = stack.getOrCreateTag();
if (upgrade == null) {
compound.remove(NBT_UPGRADE);
compound.remove(NBT_UPGRADE_INFO);
} else {
compound.putString(NBT_UPGRADE, upgrade.getUpgradeID().toString());
compound.putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
compound.put(NBT_UPGRADE_INFO, upgrade.data().copy());
}
compound.remove(NBT_UPGRADE_INFO);
}
public static CompoundTag getUpgradeInfo(ItemStack stack) {

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.pocket.recipes;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
@ -62,7 +63,7 @@ public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAc
if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY;
// Check for upgrades around the item
IPocketUpgrade upgrade = null;
UpgradeData<IPocketUpgrade> upgrade = null;
for (var y = 0; y < inventory.getHeight(); y++) {
for (var x = 0; x < inventory.getWidth(); x++) {
var item = inventory.getItem(x + y * inventory.getWidth());

View File

@ -5,7 +5,9 @@
package dan200.computercraft.shared.turtle.blocks;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@ -128,7 +130,7 @@ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable L
if (stack.getItem() instanceof TurtleItem item) {
// Set Upgrades
for (var side : TurtleSide.values()) {
turtle.getAccess().setUpgrade(side, item.getUpgrade(stack, side));
turtle.getAccess().setUpgradeWithData(side, item.getUpgradeWithData(stack, side));
}
turtle.getAccess().setFuelLevel(item.getFuelLevel(stack));
@ -161,11 +163,16 @@ protected ItemStack getItem(AbstractComputerBlockEntity tile) {
var access = turtle.getAccess();
return TurtleItem.create(
turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
access.getUpgrade(TurtleSide.LEFT), access.getUpgrade(TurtleSide.RIGHT),
withPersistedData(access.getUpgradeWithData(TurtleSide.LEFT)),
withPersistedData(access.getUpgradeWithData(TurtleSide.RIGHT)),
access.getFuelLevel(), turtle.getOverlay()
);
}
private static @Nullable UpgradeData<ITurtleUpgrade> withPersistedData(@Nullable UpgradeData<ITurtleUpgrade> upgrade) {
return upgrade == null ? null : UpgradeData.of(upgrade.upgrade(), upgrade.upgrade().getPersistedData(upgrade.data()));
}
@Override
@Nullable
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {

View File

@ -13,6 +13,7 @@
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.core.computer.ComputerSide;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.impl.TurtleUpgrades;
@ -141,17 +142,16 @@ private void readCommon(CompoundTag nbt) {
overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null;
// Read upgrades
setUpgradeDirect(TurtleSide.LEFT, nbt.contains(NBT_LEFT_UPGRADE) ? TurtleUpgrades.instance().get(nbt.getString(NBT_LEFT_UPGRADE)) : null);
setUpgradeDirect(TurtleSide.RIGHT, nbt.contains(NBT_RIGHT_UPGRADE) ? TurtleUpgrades.instance().get(nbt.getString(NBT_RIGHT_UPGRADE)) : null);
setUpgradeDirect(TurtleSide.LEFT, readUpgrade(nbt, NBT_LEFT_UPGRADE, NBT_LEFT_UPGRADE_DATA));
setUpgradeDirect(TurtleSide.RIGHT, readUpgrade(nbt, NBT_RIGHT_UPGRADE, NBT_RIGHT_UPGRADE_DATA));
}
// NBT
upgradeNBTData.clear();
if (nbt.contains(NBT_LEFT_UPGRADE_DATA)) {
upgradeNBTData.put(TurtleSide.LEFT, nbt.getCompound(NBT_LEFT_UPGRADE_DATA).copy());
}
if (nbt.contains(NBT_RIGHT_UPGRADE_DATA)) {
upgradeNBTData.put(TurtleSide.RIGHT, nbt.getCompound(NBT_RIGHT_UPGRADE_DATA).copy());
}
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));
}
private void writeCommon(CompoundTag nbt) {
@ -516,7 +516,7 @@ public GameProfile getOwningPlayer() {
}
@Override
public void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
public void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (!setUpgradeDirect(side, upgrade) || owner.getLevel() == null) return;
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
@ -529,19 +529,18 @@ public void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
owner.updateInputsImmediately();
}
private boolean setUpgradeDirect(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
private boolean setUpgradeDirect(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
// Remove old upgrade
if (upgrades.containsKey(side)) {
if (upgrades.get(side) == upgrade) return false;
upgrades.remove(side);
} else {
if (upgrade == null) return false;
}
upgradeNBTData.remove(side);
var oldUpgrade = upgrades.remove(side);
if (oldUpgrade == null && upgrade == null) return false;
// Set new upgrade
if (upgrade != null) upgrades.put(side, upgrade);
if (upgrade == null) {
upgradeNBTData.remove(side);
} else {
upgrades.put(side, upgrade.upgrade());
upgradeNBTData.put(side, upgrade.data().copy());
}
// Notify clients and create peripherals
if (owner.getLevel() != null && !owner.getLevel().isClientSide) {
@ -595,7 +594,7 @@ public Vec3 getRenderOffset(float f) {
public float getToolRenderAngle(TurtleSide side, float f) {
return (side == TurtleSide.LEFT && animation == TurtleAnimation.SWING_LEFT_TOOL) ||
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
? 45.0f * (float) Math.sin(getAnimationFraction(f) * Math.PI)
: 0.0f;
}

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.turtle.core;
import dan200.computercraft.api.turtle.*;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.turtle.TurtleUtil;
@ -18,10 +19,10 @@ public TurtleEquipCommand(TurtleSide side) {
@Override
public TurtleCommandResult execute(ITurtleAccess turtle) {
// Determine the upgrade to replace
var oldUpgrade = turtle.getUpgrade(side);
var oldUpgrade = turtle.getUpgradeWithData(side);
// Determine the upgrade to equipLeft
ITurtleUpgrade newUpgrade;
UpgradeData<ITurtleUpgrade> newUpgrade;
var selectedStack = turtle.getInventory().getItem(turtle.getSelectedSlot());
if (!selectedStack.isEmpty()) {
newUpgrade = TurtleUpgrades.instance().get(selectedStack);
@ -32,8 +33,8 @@ public TurtleCommandResult execute(ITurtleAccess turtle) {
// Do the swapping:
if (newUpgrade != null) turtle.getInventory().removeItem(turtle.getSelectedSlot(), 1);
if (oldUpgrade != null) TurtleUtil.storeItemOrDrop(turtle, oldUpgrade.getCraftingItem().copy());
turtle.setUpgrade(side, newUpgrade);
if (oldUpgrade != null) TurtleUtil.storeItemOrDrop(turtle, oldUpgrade.getUpgradeItem());
turtle.setUpgradeWithData(side, newUpgrade);
// Animate
if (newUpgrade != null || oldUpgrade != null) {

View File

@ -7,6 +7,7 @@
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.TurtleUpgrades;
import net.minecraft.core.NonNullList;
import net.minecraft.world.Container;
@ -27,7 +28,7 @@ class UpgradeContainer implements Container {
private final ITurtleAccess turtle;
private final List<ITurtleUpgrade> lastUpgrade = Arrays.asList(null, null);
private final List<UpgradeData<ITurtleUpgrade>> lastUpgrade = Arrays.asList(null, null);
private final NonNullList<ItemStack> lastStack = NonNullList.withSize(2, ItemStack.EMPTY);
UpgradeContainer(ITurtleAccess turtle) {
@ -44,22 +45,25 @@ private TurtleSide getSide(int slot) {
@Override
public ItemStack getItem(int slot) {
var upgrade = turtle.getUpgrade(getSide(slot));
var side = getSide(slot);
var upgrade = turtle.getUpgrade(side);
if (upgrade == null) return ItemStack.EMPTY;
// We don't want to return getCraftingItem directly here, as consumers may mutate the stack (they shouldn't!,
// but if they do it's a pain to track down). To avoid recreating the stack each tick, we maintain a simple
// cache.
if (upgrade == lastUpgrade.get(slot)) return lastStack.get(slot);
// cache. We use an inlined getUpgradeData here to avoid the additional defensive copy.
var upgradeData = UpgradeData.of(upgrade, turtle.getUpgradeNBTData(side));
if (upgradeData.equals(lastUpgrade.get(slot))) return lastStack.get(slot);
var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getCraftingItem().copy();
lastUpgrade.set(slot, upgrade);
var stack = upgradeData.getUpgradeItem();
lastUpgrade.set(slot, upgradeData.copy());
lastStack.set(slot, stack);
return stack;
}
@Override
public void setItem(int slot, ItemStack itemStack) {
turtle.setUpgrade(getSide(slot), TurtleUpgrades.instance().get(itemStack));
turtle.setUpgradeWithData(getSide(slot), TurtleUpgrades.instance().get(itemStack));
}
@Override

View File

@ -8,12 +8,14 @@
import dan200.computercraft.api.ComputerCraftAPI;
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.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
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;
@ -32,7 +34,7 @@ public TurtleItem(TurtleBlock block, Properties settings) {
public static ItemStack create(
int id, @Nullable String label, int colour, ComputerFamily family,
@Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
int fuelLevel, @Nullable ResourceLocation overlay
) {
return switch (family) {
@ -46,7 +48,7 @@ public static ItemStack create(
public ItemStack create(
int id, @Nullable String label, int colour,
@Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
int fuelLevel, @Nullable ResourceLocation overlay
) {
// Build the stack
@ -58,11 +60,15 @@ public ItemStack create(
if (overlay != null) stack.getOrCreateTag().putString(NBT_OVERLAY, overlay.toString());
if (leftUpgrade != null) {
stack.getOrCreateTag().putString(NBT_LEFT_UPGRADE, leftUpgrade.getUpgradeID().toString());
var tag = stack.getOrCreateTag();
tag.putString(NBT_LEFT_UPGRADE, leftUpgrade.upgrade().getUpgradeID().toString());
if (!leftUpgrade.data().isEmpty()) tag.put(NBT_LEFT_UPGRADE_DATA, leftUpgrade.data().copy());
}
if (rightUpgrade != null) {
stack.getOrCreateTag().putString(NBT_RIGHT_UPGRADE, rightUpgrade.getUpgradeID().toString());
var tag = stack.getOrCreateTag();
tag.putString(NBT_RIGHT_UPGRADE, rightUpgrade.upgrade().getUpgradeID().toString());
if (!rightUpgrade.data().isEmpty()) tag.put(NBT_RIGHT_UPGRADE_DATA, rightUpgrade.data().copy());
}
return stack;
@ -117,7 +123,7 @@ public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
return create(
getComputerID(stack), getLabel(stack),
getColour(stack), family,
getUpgrade(stack, TurtleSide.LEFT), getUpgrade(stack, TurtleSide.RIGHT),
getUpgradeWithData(stack, TurtleSide.LEFT), getUpgradeWithData(stack, TurtleSide.RIGHT),
getFuelLevel(stack), getOverlay(stack)
);
}
@ -127,7 +133,20 @@ public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
if (tag == null) return null;
var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
return tag.contains(key) ? TurtleUpgrades.instance().get(tag.getString(key)) : null;
if (!tag.contains(key)) return null;
return TurtleUpgrades.instance().get(tag.getString(key));
}
public @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(ItemStack stack, TurtleSide side) {
var tag = stack.getTag();
if (tag == null) return null;
var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
if (!tag.contains(key)) return null;
var upgrade = TurtleUpgrades.instance().get(tag.getString(key));
if (upgrade == null) return null;
var dataKey = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE_DATA : NBT_RIGHT_UPGRADE_DATA;
return UpgradeData.of(upgrade, NBTUtil.getCompoundOrEmpty(tag, dataKey));
}
public @Nullable ResourceLocation getOverlay(ItemStack stack) {

View File

@ -38,8 +38,8 @@ private static ItemStack make(ItemStack stack, ResourceLocation overlay) {
turtle.getComputerID(stack),
turtle.getLabel(stack),
turtle.getColour(stack),
turtle.getUpgrade(stack, TurtleSide.LEFT),
turtle.getUpgrade(stack, TurtleSide.RIGHT),
turtle.getUpgradeWithData(stack, TurtleSide.LEFT),
turtle.getUpgradeWithData(stack, TurtleSide.RIGHT),
turtle.getFuelLevel(stack),
overlay
);

View File

@ -6,6 +6,7 @@
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.shared.ModRegistry;
import dan200.computercraft.shared.turtle.items.TurtleItem;
@ -104,9 +105,10 @@ public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAc
// At this point we have a turtle + 1 or 2 items
// Get the turtle we already have
var itemTurtle = (TurtleItem) turtle.getItem();
var upgrades = new ITurtleUpgrade[]{
itemTurtle.getUpgrade(turtle, TurtleSide.LEFT),
itemTurtle.getUpgrade(turtle, TurtleSide.RIGHT),
@SuppressWarnings({ "unchecked", "rawtypes" })
UpgradeData<ITurtleUpgrade>[] upgrades = new UpgradeData[]{
itemTurtle.getUpgradeWithData(turtle, TurtleSide.LEFT),
itemTurtle.getUpgradeWithData(turtle, TurtleSide.RIGHT),
};
// Get the upgrades for the new items

View File

@ -9,6 +9,7 @@
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
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.level.Level;
@ -77,4 +78,9 @@ public void update(ITurtleAccess turtle, TurtleSide side) {
}
}
}
@Override
public CompoundTag getPersistedData(CompoundTag upgradeData) {
return new CompoundTag();
}
}

View File

@ -7,6 +7,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.BaseEncoding;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.nbt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -19,6 +20,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -27,9 +29,42 @@ public final class NBTUtil {
@VisibleForTesting
static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase();
private static final CompoundTag EMPTY_TAG;
static {
// If in a development environment, create a magic immutable compound tag.
// We avoid doing this in prod, as I fear it might mess up the JIT inlining things.
if (PlatformHelper.get().isDevelopmentEnvironment()) {
try {
var ctor = CompoundTag.class.getDeclaredConstructor(Map.class);
ctor.setAccessible(true);
EMPTY_TAG = ctor.newInstance(Collections.emptyMap());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
} else {
EMPTY_TAG = new CompoundTag();
}
}
private NBTUtil() {
}
/**
* Get a singleton empty {@link CompoundTag}. This tag should never be modified.
*
* @return The empty compound tag.
*/
public static CompoundTag emptyTag() {
if (EMPTY_TAG.size() != 0) LOG.error("The empty tag has been modified.");
return EMPTY_TAG;
}
public static CompoundTag getCompoundOrEmpty(CompoundTag tag, String key) {
var childTag = tag.get(key);
return childTag != null && childTag.getId() == Tag.TAG_COMPOUND ? (CompoundTag) childTag : emptyTag();
}
private static @Nullable Tag toNBTTag(@Nullable Object object) {
if (object == null) return null;
if (object instanceof Boolean) return ByteTag.valueOf((byte) ((boolean) (Boolean) object ? 1 : 0));

View File

@ -58,6 +58,11 @@
@AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class })
public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper {
@Override
public boolean isDevelopmentEnvironment() {
return true;
}
@Override
public ConfigFile.Builder createConfigBuilder() {
throw new UnsupportedOperationException("Cannot create config file inside tests");

View File

@ -33,6 +33,7 @@
import net.fabricmc.fabric.api.tag.convention.v1.ConventionalItemTags;
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -81,6 +82,11 @@
@AutoService(dan200.computercraft.impl.PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper {
@Override
public boolean isDevelopmentEnvironment() {
return FabricLoader.getInstance().isDevelopmentEnvironment();
}
@Override
public ConfigFile.Builder createConfigBuilder() {
return new FabricConfigFile.Builder();

View File

@ -62,6 +62,7 @@
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import net.minecraftforge.network.NetworkHooks;
@ -76,6 +77,11 @@
@AutoService(dan200.computercraft.impl.PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper {
@Override
public boolean isDevelopmentEnvironment() {
return !FMLLoader.isProduction();
}
@Override
public ConfigFile.Builder createConfigBuilder() {
return new ForgeConfigFile.Builder();