mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-12-01 03:48:06 +00:00
Merge branch 'mc-1.19.x' into mc-1.20.x
This commit is contained in:
@@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.turtle.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}.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -80,7 +84,10 @@ public interface IPocketAccess {
|
||||
void updateUpgradeNBTData();
|
||||
|
||||
/**
|
||||
* Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
|
||||
* Remove the current peripheral and create a new one.
|
||||
* <p>
|
||||
* You may wish to do this if the methods available change, for instance when the {@linkplain #getEntity() owning
|
||||
* entity} changes.
|
||||
*/
|
||||
void invalidatePeripheral();
|
||||
|
||||
@@ -88,6 +95,8 @@ public interface IPocketAccess {
|
||||
* Get a list of all upgrades for the pocket computer.
|
||||
*
|
||||
* @return A collection of all upgrade names.
|
||||
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
Map<ResourceLocation, IPeripheral> getUpgrades();
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ 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 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;
|
||||
|
||||
@@ -221,23 +224,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.
|
||||
@@ -257,6 +288,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);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
|
||||
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 @@ public interface ITurtleUpgrade extends UpgradeBase {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Indicates if an equipped turtle item will consume durability.
|
||||
*
|
||||
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
|
||||
*/
|
||||
public enum TurtleToolDurability implements StringRepresentable {
|
||||
/**
|
||||
* The equipped tool always consumes durability when using.
|
||||
*/
|
||||
ALWAYS("always"),
|
||||
|
||||
/**
|
||||
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
|
||||
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||
*/
|
||||
WHEN_ENCHANTED("when_enchanted"),
|
||||
|
||||
/**
|
||||
* The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades.
|
||||
*/
|
||||
NEVER("never");
|
||||
|
||||
private final String serialisedName;
|
||||
|
||||
/**
|
||||
* The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
|
||||
*/
|
||||
public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
|
||||
|
||||
TurtleToolDurability(String serialisedName) {
|
||||
this.serialisedName = serialisedName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return serialisedName;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,10 @@ import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
||||
private @Nullable Item craftingItem;
|
||||
private @Nullable Float damageMultiplier = null;
|
||||
private @Nullable TagKey<Block> breakable;
|
||||
private boolean allowEnchantments = false;
|
||||
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
|
||||
|
||||
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
|
||||
this.id = id;
|
||||
@@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
|
||||
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||
*
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder allowEnchantments() {
|
||||
allowEnchantments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the tool will consume durability.
|
||||
*
|
||||
* @param durability The durability predicate.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
|
||||
consumeDurability = durability;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
||||
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
||||
@@ -132,6 +158,10 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
||||
}
|
||||
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
|
||||
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
|
||||
if (allowEnchantments) s.addProperty("allowEnchantments", true);
|
||||
if (consumeDurability != TurtleToolDurability.NEVER) {
|
||||
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import cc.tweaked.gradle.clientClasses
|
||||
import cc.tweaked.gradle.commonClasses
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
id("cc-tweaked.vanilla")
|
||||
id("cc-tweaked.gametest")
|
||||
}
|
||||
|
||||
@@ -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,15 +23,17 @@ import net.minecraft.world.item.ItemStack;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Combines several individual models together to form a turtle.
|
||||
*
|
||||
* @param <T> The type of the resulting "baked model".
|
||||
*/
|
||||
public final class TurtleModelParts {
|
||||
public final class TurtleModelParts<T> {
|
||||
private static final Transformation identity, flip;
|
||||
|
||||
static {
|
||||
@@ -42,33 +46,67 @@ public final class TurtleModelParts {
|
||||
flip = new Transformation(stack.last().pose());
|
||||
}
|
||||
|
||||
public record Combination(
|
||||
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;
|
||||
private final BakedModel colourModel;
|
||||
private final Function<TransformedModel, BakedModel> transformer;
|
||||
private final Function<Combination, T> buildModel;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer) {
|
||||
/**
|
||||
* A cache of {@link Combination}s to the combined model.
|
||||
*/
|
||||
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;
|
||||
this.colourModel = colourModel;
|
||||
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
|
||||
buildModel = x -> combineModel.apply(buildModel(x));
|
||||
}
|
||||
|
||||
public Combination getCombination(ItemStack stack) {
|
||||
public T getModel(ItemStack stack) {
|
||||
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) {
|
||||
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
|
||||
|
||||
if (!(stack.getItem() instanceof TurtleItem turtle)) {
|
||||
@@ -76,8 +114,8 @@ public final class TurtleModelParts {
|
||||
}
|
||||
|
||||
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"));
|
||||
@@ -85,7 +123,7 @@ public final class TurtleModelParts {
|
||||
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
|
||||
}
|
||||
|
||||
public List<BakedModel> buildModel(Combination combo) {
|
||||
private List<BakedModel> buildModel(Combination combo) {
|
||||
var mc = Minecraft.getInstance();
|
||||
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
|
||||
|
||||
@@ -97,19 +135,20 @@ public final class TurtleModelParts {
|
||||
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;
|
||||
}
|
||||
|
||||
public BakedModel transform(BakedModel model, Transformation transformation) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
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 final class TurtleUpgradeModellers {
|
||||
}
|
||||
}
|
||||
|
||||
public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess access, TurtleSide side) {
|
||||
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
|
||||
@SuppressWarnings("unchecked")
|
||||
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;
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
|
||||
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 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
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 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
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))
|
||||
|
||||
@@ -19,8 +19,6 @@ import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
|
||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.core.apis.ApiFactories;
|
||||
import dan200.computercraft.core.asm.GenericMethod;
|
||||
import dan200.computercraft.core.filesystem.WritableFileMount;
|
||||
import dan200.computercraft.impl.detail.DetailRegistryImpl;
|
||||
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
|
||||
@@ -79,7 +77,7 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
|
||||
|
||||
@Override
|
||||
public final void registerGenericSource(GenericSource source) {
|
||||
GenericMethod.register(source);
|
||||
GenericSources.register(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
package dan200.computercraft.impl;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
|
||||
@@ -11,19 +11,24 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The global factory for {@link ILuaAPIFactory}s.
|
||||
*
|
||||
* @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection)
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
*/
|
||||
public final class ApiFactories {
|
||||
private ApiFactories() {
|
||||
}
|
||||
|
||||
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
|
||||
private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection(factories);
|
||||
|
||||
public static synchronized void register(ILuaAPIFactory factory) {
|
||||
static synchronized void register(ILuaAPIFactory factory) {
|
||||
Objects.requireNonNull(factory, "provider cannot be null");
|
||||
factories.add(factory);
|
||||
}
|
||||
|
||||
public static Iterable<ILuaAPIFactory> getAll() {
|
||||
return factoriesView;
|
||||
public static Collection<ILuaAPIFactory> getAll() {
|
||||
return Collections.unmodifiableCollection(factories);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl;
|
||||
|
||||
import dan200.computercraft.api.lua.GenericSource;
|
||||
import dan200.computercraft.core.asm.GenericMethod;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The global registry for {@link GenericSource}s.
|
||||
*
|
||||
* @see dan200.computercraft.core.ComputerContext.Builder#genericMethods(Collection)
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerGenericSource(GenericSource)
|
||||
*/
|
||||
public final class GenericSources {
|
||||
private GenericSources() {
|
||||
}
|
||||
|
||||
private static final Collection<GenericSource> sources = new LinkedHashSet<>();
|
||||
|
||||
static synchronized void register(GenericSource source) {
|
||||
Objects.requireNonNull(source, "provider cannot be null");
|
||||
sources.add(source);
|
||||
}
|
||||
|
||||
public static Collection<GenericMethod> getAllMethods() {
|
||||
return sources.stream().flatMap(GenericMethod::getMethods).toList();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.impl;
|
||||
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 class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
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;
|
||||
@@ -445,12 +446,12 @@ public final class ModRegistry {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ package dan200.computercraft.shared.command;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
@@ -173,7 +176,10 @@ public final class CommandComputerCraft {
|
||||
|
||||
.then(command("queue")
|
||||
.requires(UserLevel.ANYONE)
|
||||
.arg("computer", manyComputers())
|
||||
.arg(
|
||||
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
||||
.suggests((context, builder) -> Suggestions.empty())
|
||||
)
|
||||
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
|
||||
.executes((ctx, args) -> {
|
||||
var computers = getComputersArgument(ctx, "computer");
|
||||
|
||||
@@ -49,6 +49,29 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
||||
return source.hasPermission(toLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the union of two {@link UserLevel}s.
|
||||
* <p>
|
||||
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}.
|
||||
*
|
||||
* @param left The first user level to take the union of.
|
||||
* @param right The second user level to take the union of.
|
||||
* @return The union of two levels.
|
||||
*/
|
||||
public static UserLevel union(UserLevel left, UserLevel right) {
|
||||
if (left == right) return left;
|
||||
|
||||
// x ∪ ANYONE = ANYONE
|
||||
if (left == ANYONE || right == ANYONE) return ANYONE;
|
||||
|
||||
// x ∪ OWNER = OWNER
|
||||
if (left == OWNER) return right;
|
||||
if (right == OWNER) return left;
|
||||
|
||||
// At this point, we have x != y and x, y ∈ { OP, OWNER_OP }.
|
||||
return OWNER_OP;
|
||||
}
|
||||
|
||||
private static boolean isOwner(CommandSourceStack source) {
|
||||
var server = source.getServer();
|
||||
var sender = source.getEntity();
|
||||
|
||||
@@ -18,7 +18,6 @@ import net.minecraft.commands.CommandBuildContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -159,14 +158,14 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputersArgumentType.Template unpack(@NotNull ComputersArgumentType argumentType) {
|
||||
public ComputersArgumentType.Template unpack(ComputersArgumentType argumentType) {
|
||||
return new ComputersArgumentType.Template(this, argumentType.requireSome);
|
||||
}
|
||||
}
|
||||
|
||||
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
|
||||
@Override
|
||||
public ComputersArgumentType instantiate(@NotNull CommandBuildContext context) {
|
||||
public ComputersArgumentType instantiate(CommandBuildContext context) {
|
||||
return requireSome ? SOME : MANY;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -144,7 +143,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
|
||||
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public RepeatArgumentType<?, ?> instantiate(@NotNull CommandBuildContext commandBuildContext) {
|
||||
public RepeatArgumentType<?, ?> instantiate(CommandBuildContext commandBuildContext) {
|
||||
var child = child().instantiate(commandBuildContext);
|
||||
return flatten ? RepeatArgumentType.someFlat((ArgumentType) child, some()) : RepeatArgumentType.some(child, some());
|
||||
}
|
||||
|
||||
@@ -48,11 +48,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
|
||||
args.add(RequiredArgumentBuilder.argument(name, type));
|
||||
public CommandBuilder<S> arg(ArgumentBuilder<S, ?> arg) {
|
||||
args.add(arg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
|
||||
return arg(RequiredArgumentBuilder.argument(name, type));
|
||||
}
|
||||
|
||||
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, List<T> empty) {
|
||||
return argMany(name, type, () -> empty);
|
||||
}
|
||||
@@ -74,7 +78,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
||||
|
||||
return command -> {
|
||||
// The node for no arguments
|
||||
var tail = tail(ctx -> command.run(ctx, empty.get()));
|
||||
var tail = setupTail(ctx -> command.run(ctx, empty.get()));
|
||||
|
||||
// The node for one or more arguments
|
||||
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
||||
@@ -83,7 +87,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
||||
|
||||
// Chain all of them together!
|
||||
tail.then(moreArg);
|
||||
return link(tail);
|
||||
return buildTail(tail);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,20 +98,16 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
||||
|
||||
@Override
|
||||
public CommandNode<S> executes(Command<S> command) {
|
||||
if (args.isEmpty()) throw new IllegalStateException("Cannot have empty arg chain builder");
|
||||
|
||||
return link(tail(command));
|
||||
return buildTail(setupTail(command));
|
||||
}
|
||||
|
||||
private ArgumentBuilder<S, ?> tail(Command<S> command) {
|
||||
var defaultTail = args.get(args.size() - 1);
|
||||
defaultTail.executes(command);
|
||||
if (requires != null) defaultTail.requires(requires);
|
||||
return defaultTail;
|
||||
private ArgumentBuilder<S, ?> setupTail(Command<S> command) {
|
||||
return args.get(args.size() - 1).executes(command);
|
||||
}
|
||||
|
||||
private CommandNode<S> link(ArgumentBuilder<S, ?> tail) {
|
||||
private CommandNode<S> buildTail(ArgumentBuilder<S, ?> tail) {
|
||||
for (var i = args.size() - 2; i >= 0; i--) tail = args.get(i).then(tail);
|
||||
if (requires != null) tail.requires(requires);
|
||||
return tail.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import dan200.computercraft.shared.command.UserLevel;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
@@ -18,6 +19,8 @@ import net.minecraft.network.chat.Component;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
||||
@@ -37,6 +40,29 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
||||
return new HelpingArgumentBuilder(literal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiteralArgumentBuilder<CommandSourceStack> requires(Predicate<CommandSourceStack> requirement) {
|
||||
throw new IllegalStateException("Cannot use requires on a HelpingArgumentBuilder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<CommandSourceStack> getRequirement() {
|
||||
// The requirement of this node is the union of all child's requirements.
|
||||
var requirements = Stream.concat(
|
||||
children.stream().map(ArgumentBuilder::getRequirement),
|
||||
getArguments().stream().map(CommandNode::getRequirement)
|
||||
).toList();
|
||||
|
||||
// If all requirements are a UserLevel, take the union of those instead.
|
||||
var userLevel = UserLevel.OWNER;
|
||||
for (var requirement : requirements) {
|
||||
if (!(requirement instanceof UserLevel level)) return x -> requirements.stream().anyMatch(y -> y.test(x));
|
||||
userLevel = UserLevel.union(userLevel, level);
|
||||
}
|
||||
|
||||
return userLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiteralArgumentBuilder<CommandSourceStack> executes(final Command<CommandSourceStack> command) {
|
||||
throw new IllegalStateException("Cannot use executes on a HelpingArgumentBuilder");
|
||||
@@ -80,9 +106,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
||||
helpCommand.node = node;
|
||||
|
||||
// Set up a /... help command
|
||||
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help")
|
||||
.requires(x -> getArguments().stream().anyMatch(y -> y.getRequirement().test(x)))
|
||||
.executes(helpCommand);
|
||||
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help").executes(helpCommand);
|
||||
|
||||
// Add all normal command children to this and the help node
|
||||
for (var child : getArguments()) {
|
||||
|
||||
@@ -9,13 +9,16 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.network.PacketNetwork;
|
||||
import dan200.computercraft.core.ComputerContext;
|
||||
import dan200.computercraft.core.computer.ComputerThread;
|
||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||
import dan200.computercraft.impl.ApiFactories;
|
||||
import dan200.computercraft.impl.GenericSources;
|
||||
import dan200.computercraft.shared.CommonHooks;
|
||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||
import dan200.computercraft.shared.config.ConfigSpec;
|
||||
@@ -67,11 +70,13 @@ public final class ServerContext {
|
||||
this.server = server;
|
||||
storageDir = server.getWorldPath(FOLDER);
|
||||
mainThread = new MainThread(mainThreadConfig);
|
||||
context = new ComputerContext(
|
||||
new Environment(server),
|
||||
new ComputerThread(ConfigSpec.computerThreads.get()),
|
||||
mainThread, luaMachine
|
||||
);
|
||||
context = ComputerContext.builder(new Environment(server))
|
||||
.computerThreads(ConfigSpec.computerThreads.get())
|
||||
.mainThreadScheduler(mainThread)
|
||||
.luaFactory(luaMachine)
|
||||
.apiFactories(ApiFactories.getAll())
|
||||
.genericMethods(GenericSources.getAllMethods())
|
||||
.build();
|
||||
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
||||
}
|
||||
|
||||
@@ -133,6 +138,16 @@ public final class ServerContext {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MethodSupplier} used to find methods on peripherals.
|
||||
*
|
||||
* @return The {@link PeripheralMethod} method supplier.
|
||||
* @see ComputerContext#peripheralMethods()
|
||||
*/
|
||||
public MethodSupplier<PeripheralMethod> peripheralMethods() {
|
||||
return context.peripheralMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@ package dan200.computercraft.shared.computer.upload;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
||||
import dan200.computercraft.core.asm.ObjectSource;
|
||||
import dan200.computercraft.core.methods.ObjectSource;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -4,21 +4,19 @@
|
||||
|
||||
package dan200.computercraft.shared.config;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||
import dan200.computercraft.core.apis.http.options.InvalidRuleException;
|
||||
import dan200.computercraft.core.apis.http.options.PartialOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
|
||||
@@ -26,49 +24,65 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
class AddressRuleConfig {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
|
||||
|
||||
public static UnmodifiableConfig makeRule(String host, Action action) {
|
||||
var config = InMemoryCommentedFormat.defaultInstance().createConfig(ConcurrentHashMap::new);
|
||||
config.add("host", host);
|
||||
config.add("action", action.name().toLowerCase(Locale.ROOT));
|
||||
private static final AddressRule REJECT_ALL = AddressRule.parse("*", OptionalInt.empty(), Action.DENY.toPartial());
|
||||
|
||||
if (host.equals("*") && action == Action.ALLOW) {
|
||||
config.setComment("max_download", """
|
||||
The maximum size (in bytes) that a computer can download in a single request.
|
||||
Note that responses may receive more data than allowed, but this data will not
|
||||
be returned to the client.""");
|
||||
config.set("max_download", AddressRule.MAX_DOWNLOAD);
|
||||
public static List<UnmodifiableConfig> defaultRules() {
|
||||
return List.of(
|
||||
makeRule(config -> {
|
||||
config.setComment("host", """
|
||||
The magic "$private" host matches all private address ranges, such as localhost and 192.168.0.0/16.
|
||||
This rule prevents computers accessing internal services, and is strongly recommended.""");
|
||||
config.add("host", "$private");
|
||||
|
||||
config.setComment("max_upload", """
|
||||
The maximum size (in bytes) that a computer can upload in a single request. This
|
||||
includes headers and POST text.""");
|
||||
config.set("max_upload", AddressRule.MAX_UPLOAD);
|
||||
config.setComment("action", "Deny all requests to private IP addresses.");
|
||||
config.add("action", Action.DENY.name().toLowerCase(Locale.ROOT));
|
||||
}),
|
||||
makeRule(config -> {
|
||||
config.setComment("host", """
|
||||
The wildcard "*" rule matches all remaining hosts.""");
|
||||
config.add("host", "*");
|
||||
|
||||
config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
|
||||
config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
|
||||
config.setComment("action", "Allow all non-denied hosts.");
|
||||
config.add("action", Action.ALLOW.name().toLowerCase(Locale.ROOT));
|
||||
|
||||
config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
|
||||
config.set("use_proxy", false);
|
||||
}
|
||||
config.setComment("max_download", """
|
||||
The maximum size (in bytes) that a computer can download in a single request.
|
||||
Note that responses may receive more data than allowed, but this data will not
|
||||
be returned to the client.""");
|
||||
config.set("max_download", AddressRule.MAX_DOWNLOAD);
|
||||
|
||||
config.setComment("max_upload", """
|
||||
The maximum size (in bytes) that a computer can upload in a single request. This
|
||||
includes headers and POST text.""");
|
||||
config.set("max_upload", AddressRule.MAX_UPLOAD);
|
||||
|
||||
config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
|
||||
config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
|
||||
|
||||
config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
|
||||
config.set("use_proxy", false);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private static UnmodifiableConfig makeRule(Consumer<CommentedConfig> setup) {
|
||||
var config = InMemoryCommentedFormat.defaultInstance().createConfig(LinkedHashMap::new);
|
||||
setup.accept(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
public static boolean checkRule(UnmodifiableConfig builder) {
|
||||
var hostObj = get(builder, "host", String.class).orElse(null);
|
||||
var port = unboxOptInt(get(builder, "port", Number.class));
|
||||
return hostObj != null && checkEnum(builder, "action", Action.class)
|
||||
&& check(builder, "port", Number.class)
|
||||
&& check(builder, "max_upload", Number.class)
|
||||
&& check(builder, "max_download", Number.class)
|
||||
&& check(builder, "websocket_message", Number.class)
|
||||
&& check(builder, "use_proxy", Boolean.class)
|
||||
&& AddressRule.parse(hostObj, port, PartialOptions.DEFAULT) != null;
|
||||
public static AddressRule parseRule(UnmodifiableConfig builder) {
|
||||
try {
|
||||
return doParseRule(builder);
|
||||
} catch (InvalidRuleException e) {
|
||||
LOG.error("Malformed HTTP rule: {} HTTP will NOT work until this is fixed.", e.getMessage());
|
||||
return REJECT_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parseRule(UnmodifiableConfig builder) {
|
||||
public static AddressRule doParseRule(UnmodifiableConfig builder) {
|
||||
var hostObj = get(builder, "host", String.class).orElse(null);
|
||||
if (hostObj == null) return null;
|
||||
if (hostObj == null) throw new InvalidRuleException("No 'host' specified");
|
||||
|
||||
var action = getEnum(builder, "action", Action.class).orElse(null);
|
||||
var port = unboxOptInt(get(builder, "port", Number.class));
|
||||
@@ -88,38 +102,19 @@ class AddressRuleConfig {
|
||||
return AddressRule.parse(hostObj, port, options);
|
||||
}
|
||||
|
||||
private static <T> boolean check(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||
var value = config.get(field);
|
||||
if (value == null || klass.isInstance(value)) return true;
|
||||
|
||||
LOG.warn("HTTP rule's {} is not a {}.", field, klass.getSimpleName());
|
||||
return false;
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> boolean checkEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||
var value = config.get(field);
|
||||
if (value == null) return true;
|
||||
|
||||
if (!(value instanceof String)) {
|
||||
LOG.warn("HTTP rule's {} is not a string", field);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parseEnum(klass, (String) value) == null) {
|
||||
LOG.warn("HTTP rule's {} is not a known option", field);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static <T> Optional<T> get(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||
var value = config.get(field);
|
||||
return klass.isInstance(value) ? Optional.of(klass.cast(value)) : Optional.empty();
|
||||
if (value == null) return Optional.empty();
|
||||
if (klass.isInstance(value)) return Optional.of(klass.cast(value));
|
||||
|
||||
throw new InvalidRuleException(String.format(
|
||||
"Field '%s' should be a '%s' but is a %s.",
|
||||
field, klass.getSimpleName(), value.getClass().getSimpleName()
|
||||
));
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> Optional<T> getEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||
return get(config, field, String.class).map(x -> parseEnum(klass, x));
|
||||
return get(config, field, String.class).map(x -> parseEnum(field, klass, x));
|
||||
}
|
||||
|
||||
private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
|
||||
@@ -130,11 +125,14 @@ class AddressRuleConfig {
|
||||
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T extends Enum<T>> T parseEnum(Class<T> klass, String x) {
|
||||
private static <T extends Enum<T>> T parseEnum(String field, Class<T> klass, String x) {
|
||||
for (var value : klass.getEnumConstants()) {
|
||||
if (value.name().equalsIgnoreCase(x)) return value;
|
||||
}
|
||||
return null;
|
||||
|
||||
throw new InvalidRuleException(String.format(
|
||||
"Field '%s' should be one of %s, but is '%s'.",
|
||||
field, Arrays.stream(klass.getEnumConstants()).map(Enum::name).toList(), x
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.core.CoreConfig;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.ProxyType;
|
||||
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
@@ -20,9 +19,7 @@ import org.apache.logging.log4j.core.filter.MarkerFilter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class ConfigSpec {
|
||||
@@ -182,9 +179,9 @@ public final class ConfigSpec {
|
||||
|
||||
httpEnabled = builder
|
||||
.comment("""
|
||||
Enable the "http" API on Computers. This also disables the "pastebin" and "wget"
|
||||
programs, that many users rely on. It's recommended to leave this on and use the
|
||||
"rules" config option to impose more fine-grained control.""")
|
||||
Enable the "http" API on Computers. Disabling this also disables the "pastebin" and
|
||||
"wget" programs, that many users rely on. It's recommended to leave this on and use
|
||||
the "rules" config option to impose more fine-grained control.""")
|
||||
.define("enabled", CoreConfig.httpEnabled);
|
||||
|
||||
httpWebsocketEnabled = builder
|
||||
@@ -194,16 +191,23 @@ public final class ConfigSpec {
|
||||
httpRules = builder
|
||||
.comment("""
|
||||
A list of rules which control behaviour of the "http" API for specific domains or
|
||||
IPs. Each rule is an item with a 'host' to match against, and a series of
|
||||
properties. Rules are evaluated in order, meaning earlier rules override later
|
||||
ones.
|
||||
The host may be a domain name ("pastebin.com"), wildcard ("*.pastebin.com") or
|
||||
CIDR notation ("127.0.0.0/8").
|
||||
If no rules, the domain is blocked.""")
|
||||
.defineList("rules", Arrays.asList(
|
||||
AddressRuleConfig.makeRule("$private", Action.DENY),
|
||||
AddressRuleConfig.makeRule("*", Action.ALLOW)
|
||||
), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule((UnmodifiableConfig) x));
|
||||
IPs. Each rule matches against a hostname and an optional port, and then sets several
|
||||
properties for the request. Rules are evaluated in order, meaning earlier rules override
|
||||
later ones.
|
||||
|
||||
Valid properties:
|
||||
- "host" (required): The domain or IP address this rule matches. This may be a domain name
|
||||
("pastebin.com"), wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||
- "port" (optional): Only match requests for a specific port, such as 80 or 443.
|
||||
|
||||
- "action" (optional): Whether to allow or deny this request.
|
||||
- "max_download" (optional): The maximum size (in bytes) that a computer can download in this
|
||||
request.
|
||||
- "max_upload" (optional): The maximum size (in bytes) that a computer can upload in a this request.
|
||||
- "max_websocket_message" (optional): The maximum size (in bytes) that a computer can send or
|
||||
receive in one websocket packet.
|
||||
- "use_proxy" (optional): Enable use of the HTTP/SOCKS proxy if it is configured.""")
|
||||
.defineList("rules", AddressRuleConfig.defaultRules(), x -> x instanceof UnmodifiableConfig);
|
||||
|
||||
httpMaxRequests = builder
|
||||
.comment("""
|
||||
@@ -395,8 +399,8 @@ public final class ConfigSpec {
|
||||
// HTTP
|
||||
CoreConfig.httpEnabled = httpEnabled.get();
|
||||
CoreConfig.httpWebsocketEnabled = httpWebsocketEnabled.get();
|
||||
CoreConfig.httpRules = httpRules.get().stream()
|
||||
.map(AddressRuleConfig::parseRule).filter(Objects::nonNull).toList();
|
||||
|
||||
CoreConfig.httpRules = httpRules.get().stream().map(AddressRuleConfig::parseRule).toList();
|
||||
|
||||
CoreConfig.httpMaxRequests = httpMaxRequests.get();
|
||||
CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get();
|
||||
|
||||
@@ -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 final class RecipeModHelpers {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
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 class UpgradeRecipeGenerator<T> {
|
||||
|
||||
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 class UpgradeRecipeGenerator<T> {
|
||||
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 class UpgradeRecipeGenerator<T> {
|
||||
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 class UpgradeRecipeGenerator<T> {
|
||||
} 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 class UpgradeRecipeGenerator<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable ITurtleUpgrade right) {
|
||||
private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) {
|
||||
var item = (TurtleItem) stack.getItem();
|
||||
return item.create(
|
||||
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
|
||||
@@ -223,7 +227,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
);
|
||||
}
|
||||
|
||||
private static ItemStack pocketWith(ItemStack stack, @Nullable IPocketUpgrade back) {
|
||||
private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) {
|
||||
var item = (PocketComputerItem) stack.getItem();
|
||||
return item.create(
|
||||
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
|
||||
@@ -272,7 +276,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
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 @@ public class UpgradeRecipeGenerator<T> {
|
||||
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))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,23 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
class GenericPeripheral implements IDynamicPeripheral {
|
||||
public final class GenericPeripheral implements IDynamicPeripheral {
|
||||
private final BlockEntity tile;
|
||||
private final Direction side;
|
||||
|
||||
private final String type;
|
||||
private final Set<String> additionalTypes;
|
||||
private final BlockEntity tile;
|
||||
private final List<SaturatedMethod> methods;
|
||||
|
||||
GenericPeripheral(BlockEntity tile, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) {
|
||||
GenericPeripheral(BlockEntity tile, Direction side, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) {
|
||||
this.side = side;
|
||||
var type = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(tile.getType());
|
||||
this.tile = tile;
|
||||
this.type = name != null ? name : type.toString();
|
||||
@@ -32,6 +36,10 @@ class GenericPeripheral implements IDynamicPeripheral {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public Direction side() {
|
||||
return side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getMethodNames() {
|
||||
var names = new String[methods.size()];
|
||||
@@ -54,7 +62,6 @@ class GenericPeripheral implements IDynamicPeripheral {
|
||||
return additionalTypes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getTarget() {
|
||||
return tile;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.NamedMethod;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A builder for a {@link GenericPeripheral}.
|
||||
* <p>
|
||||
* This handles building a list of {@linkplain SaturatedMethod methods} and computing the appropriate
|
||||
* {@link PeripheralType} from the {@linkplain NamedMethod#genericType() methods' peripheral types}.
|
||||
* <p>
|
||||
* See the platform-specific peripheral providers for the usage of this.
|
||||
*/
|
||||
final class GenericPeripheralBuilder {
|
||||
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||
|
||||
private @Nullable String name;
|
||||
private final Set<String> additionalTypes = new HashSet<>(0);
|
||||
private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
|
||||
|
||||
GenericPeripheralBuilder(MinecraftServer server) {
|
||||
peripheralMethods = ServerContext.get(server).peripheralMethods();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
|
||||
if (methods.isEmpty()) return null;
|
||||
|
||||
methods.trimToSize();
|
||||
return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods);
|
||||
}
|
||||
|
||||
boolean addMethods(Object target) {
|
||||
return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> {
|
||||
methods.add(new SaturatedMethod(target, name, method));
|
||||
|
||||
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
|
||||
// don't change).
|
||||
var type = info == null ? null : info.genericType();
|
||||
if (type != null && type.getPrimaryType() != null) {
|
||||
var primaryType = type.getPrimaryType();
|
||||
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
|
||||
}
|
||||
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,20 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.core.asm.NamedMethod;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
|
||||
/**
|
||||
* A {@link PeripheralMethod} along with the method's target.
|
||||
*/
|
||||
final class SaturatedMethod {
|
||||
private final Object target;
|
||||
private final String name;
|
||||
private final PeripheralMethod method;
|
||||
|
||||
SaturatedMethod(Object target, NamedMethod<PeripheralMethod> method) {
|
||||
SaturatedMethod(Object target, String name, PeripheralMethod method) {
|
||||
this.target = target;
|
||||
name = method.getName();
|
||||
this.method = method.getMethod();
|
||||
this.name = name;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException {
|
||||
|
||||
@@ -16,10 +16,12 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.NotAttachedException;
|
||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||
import dan200.computercraft.core.apis.PeripheralAPI;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.core.util.LuaUtil;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -284,7 +286,8 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
||||
|
||||
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
|
||||
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) {
|
||||
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName);
|
||||
var methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral);
|
||||
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods);
|
||||
peripherals.put(periphName, wrapper);
|
||||
wrapper.attach();
|
||||
}
|
||||
@@ -314,7 +317,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
||||
private volatile boolean attached;
|
||||
private final Set<String> mounts = new HashSet<>();
|
||||
|
||||
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name) {
|
||||
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map<String, PeripheralMethod> methods) {
|
||||
this.element = element;
|
||||
this.peripheral = peripheral;
|
||||
this.computer = computer;
|
||||
@@ -322,7 +325,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
||||
|
||||
type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
|
||||
additionalTypes = peripheral.getAdditionalTypes();
|
||||
methodMap = PeripheralAPI.getMethods(peripheral);
|
||||
methodMap = methods;
|
||||
}
|
||||
|
||||
public void attach() {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Shared constants for {@linkplain PlatformHelper#createFakePlayer(ServerLevel, GameProfile) fake player}
|
||||
* implementations.
|
||||
*
|
||||
* @see net.minecraft.server.level.ServerPlayer
|
||||
* @see net.minecraft.world.entity.player.Player
|
||||
*/
|
||||
final class FakePlayerConstants {
|
||||
private FakePlayerConstants() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum distance this player can reach.
|
||||
* <p>
|
||||
* This is used in the override of {@link net.minecraft.world.entity.player.Player#mayUseItemAt(BlockPos, Direction, ItemStack)},
|
||||
* to prevent the fake player reaching more than 2 blocks away.
|
||||
*/
|
||||
static final double MAX_REACH = 2;
|
||||
}
|
||||
@@ -69,6 +69,13 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.apis;
|
||||
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.entity.player.Player;
|
||||
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 class PocketAPI implements ILuaAPI {
|
||||
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 class PocketAPI implements ILuaAPI {
|
||||
|
||||
computer.setUpgrade(null);
|
||||
|
||||
storeItem(player, previousUpgrade.getCraftingItem().copy());
|
||||
storeItem(player, previousUpgrade.getUpgradeItem());
|
||||
|
||||
return new Object[]{ true };
|
||||
}
|
||||
@@ -105,13 +107,13 @@ public class PocketAPI implements ILuaAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable IPocketUpgrade findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable IPocketUpgrade previous) {
|
||||
private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) {
|
||||
for (var i = 0; i < inv.size(); i++) {
|
||||
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);
|
||||
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.core;
|
||||
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;
|
||||
@@ -104,12 +105,13 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,13 +121,11 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
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.apis.PocketAPI;
|
||||
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 class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
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 class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
};
|
||||
}
|
||||
|
||||
public ItemStack create(int id, @Nullable String label, int colour, @Nullable IPocketUpgrade upgrade) {
|
||||
public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
var result = new ItemStack(this);
|
||||
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;
|
||||
}
|
||||
@@ -208,7 +213,9 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
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.
|
||||
@@ -245,7 +252,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
|
||||
return create(
|
||||
getComputerID(stack), getLabel(stack), getColour(stack),
|
||||
family, getUpgrade(stack)
|
||||
family, getUpgradeWithData(stack)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,20 +302,27 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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 final class PocketComputerUpgradeRecipe extends CustomRecipe {
|
||||
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());
|
||||
|
||||
@@ -555,7 +555,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
* @cc.usage Refuel a turtle from the currently selected slot.
|
||||
* <pre>{@code
|
||||
* local level = turtle.getFuelLevel()
|
||||
* if new_level == "unlimited" then error("Turtle does not need fuel", 0) end
|
||||
* if level == "unlimited" then error("Turtle does not need fuel", 0) end
|
||||
*
|
||||
* local ok, err = turtle.refuel()
|
||||
* if ok then
|
||||
|
||||
@@ -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 class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
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 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
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) {
|
||||
|
||||
@@ -13,8 +13,8 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
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;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
@@ -34,7 +34,6 @@ import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.MoverType;
|
||||
import net.minecraft.world.item.DyeColor;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.material.PushReaction;
|
||||
@@ -141,17 +140,16 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
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) {
|
||||
@@ -463,23 +461,6 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable DyeColor getDyeColour() {
|
||||
if (colourHex == -1) return null;
|
||||
var colour = Colour.fromHex(colourHex);
|
||||
return colour == null ? null : DyeColor.byId(15 - colour.ordinal());
|
||||
}
|
||||
|
||||
public void setDyeColour(@Nullable DyeColor dyeColour) {
|
||||
var newColour = -1;
|
||||
if (dyeColour != null) {
|
||||
newColour = Colour.values()[15 - dyeColour.getId()].getHex();
|
||||
}
|
||||
if (colourHex != newColour) {
|
||||
colourHex = newColour;
|
||||
BlockEntityHelpers.updateBlock(owner);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColour(int colour) {
|
||||
if (colour >= 0 && colour <= 0xFFFFFF) {
|
||||
@@ -514,7 +495,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -527,19 +508,18 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
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) {
|
||||
@@ -593,7 +573,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 class TurtleEquipCommand implements TurtleCommand {
|
||||
@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 class TurtleEquipCommand implements TurtleCommand {
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -75,19 +75,7 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deployCopiedItem(
|
||||
ItemStack stack, ITurtleAccess turtle, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
|
||||
) {
|
||||
// Create a fake player, and orient it appropriately
|
||||
var playerPosition = turtle.getPosition().relative(direction);
|
||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, playerPosition, direction);
|
||||
turtlePlayer.loadInventory(stack);
|
||||
var result = deploy(stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage);
|
||||
turtlePlayer.player().getInventory().clearContent();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean deploy(
|
||||
public static boolean deploy(
|
||||
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction,
|
||||
@Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
|
||||
) {
|
||||
|
||||
@@ -7,12 +7,14 @@ package dan200.computercraft.shared.turtle.inventory;
|
||||
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;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -27,7 +29,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 +46,29 @@ class UpgradeContainer implements Container {
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(int slot) {
|
||||
var upgrade = turtle.getUpgrade(getSide(slot));
|
||||
var side = getSide(slot);
|
||||
var upgrade = turtle.getUpgradeWithData(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);
|
||||
// We don't want to return getUpgradeItem directly here, as we'd end up recreating the stack each tick. To
|
||||
// avoid that, we maintain a simple cache.
|
||||
if (upgrade.equals(lastUpgrade.get(slot))) return lastStack.get(slot);
|
||||
|
||||
var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getCraftingItem().copy();
|
||||
lastUpgrade.set(slot, upgrade);
|
||||
return setUpgradeStack(slot, upgrade);
|
||||
}
|
||||
|
||||
private ItemStack setUpgradeStack(int slot, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||
var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getUpgradeItem();
|
||||
lastUpgrade.set(slot, UpgradeData.copyOf(upgrade));
|
||||
lastStack.set(slot, stack);
|
||||
return stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(int slot, ItemStack itemStack) {
|
||||
turtle.setUpgrade(getSide(slot), TurtleUpgrades.instance().get(itemStack));
|
||||
var upgrade = TurtleUpgrades.instance().get(itemStack);
|
||||
turtle.setUpgradeWithData(getSide(slot), upgrade);
|
||||
setUpgradeStack(slot, upgrade);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,12 +8,14 @@ import dan200.computercraft.annotations.ForgeOverride;
|
||||
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 class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
|
||||
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 class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
|
||||
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 class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
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 class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
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 class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
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) {
|
||||
|
||||
@@ -38,8 +38,8 @@ public class TurtleOverlayRecipe extends ShapelessRecipe {
|
||||
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
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ package dan200.computercraft.shared.turtle.recipes;
|
||||
|
||||
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 final class TurtleUpgradeRecipe extends CustomRecipe {
|
||||
// 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
|
||||
|
||||
@@ -9,6 +9,7 @@ import dan200.computercraft.api.turtle.*;
|
||||
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 class TurtleModem extends AbstractTurtleUpgrade {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getPersistedData(CompoundTag upgradeData) {
|
||||
return new CompoundTag();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,22 @@ import dan200.computercraft.shared.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
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.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
@@ -32,46 +38,127 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
|
||||
import javax.annotation.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 {
|
||||
protected static final TurtleCommandResult UNBREAKABLE = TurtleCommandResult.failure("Cannot break unbreakable block");
|
||||
protected static final TurtleCommandResult INEFFECTIVE = TurtleCommandResult.failure("Cannot break block with this tool");
|
||||
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 damageMulitiplier;
|
||||
@Nullable
|
||||
final TagKey<Block> breakable;
|
||||
final boolean allowEnchantments;
|
||||
final TurtleToolDurability consumeDurability;
|
||||
final @Nullable TagKey<Block> breakable;
|
||||
|
||||
public TurtleTool(ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, @Nullable TagKey<Block> breakable) {
|
||||
public TurtleTool(
|
||||
ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier,
|
||||
boolean allowEnchantments, TurtleToolDurability consumeDurability, @Nullable TagKey<Block> breakable
|
||||
) {
|
||||
super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem));
|
||||
item = toolItem;
|
||||
this.damageMulitiplier = damageMulitiplier;
|
||||
this.allowEnchantments = allowEnchantments;
|
||||
this.consumeDurability = consumeDurability;
|
||||
this.breakable = breakable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemSuitable(ItemStack stack) {
|
||||
var tag = stack.getTag();
|
||||
if (tag == null || tag.isEmpty()) return true;
|
||||
if (consumeDurability == TurtleToolDurability.NEVER && stack.isDamaged()) return false;
|
||||
if (!allowEnchantments && isEnchanted(stack)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check we've not got anything vaguely interesting on the item. We allow other mods to add their
|
||||
// own NBT, with the understanding such details will be lost to the mist of time.
|
||||
if (stack.isDamaged() || stack.isEnchanted() || stack.hasCustomHoverName()) return false;
|
||||
if (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()) {
|
||||
return false;
|
||||
private static boolean isEnchanted(ItemStack stack) {
|
||||
return !stack.isEmpty() && isEnchanted(stack.getTag());
|
||||
}
|
||||
|
||||
private static boolean isEnchanted(@Nullable CompoundTag tag) {
|
||||
if (tag == null || tag.isEmpty()) return false;
|
||||
return (tag.contains(ItemStack.TAG_ENCH, TAG_LIST) && !tag.getList(ItemStack.TAG_ENCH, TAG_COMPOUND).isEmpty())
|
||||
|| (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.
|
||||
var item = super.getUpgradeItem(upgradeData).copy();
|
||||
item.setTag(upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) ? upgradeData.getCompound(TAG_ITEM_TAG).copy() : null);
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) {
|
||||
return getUpgradeItem(turtle.getUpgradeNBTData(side));
|
||||
}
|
||||
|
||||
private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) {
|
||||
var upgradeData = turtle.getUpgradeNBTData(side);
|
||||
|
||||
var useDurability = switch (consumeDurability) {
|
||||
case NEVER -> false;
|
||||
case WHEN_ENCHANTED ->
|
||||
upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) && isEnchanted(upgradeData.getCompound(TAG_ITEM_TAG));
|
||||
case ALWAYS -> true;
|
||||
};
|
||||
if (!useDurability) return;
|
||||
|
||||
// If the tool has broken, remove the upgrade!
|
||||
if (stack.isEmpty()) {
|
||||
turtle.setUpgradeWithData(side, null);
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
// If the tool has changed, no clue what's going on.
|
||||
if (stack.getItem() != item.getItem()) return;
|
||||
|
||||
var itemTag = stack.getTag();
|
||||
|
||||
// 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 (itemTag == null) {
|
||||
upgradeData.remove(TAG_ITEM_TAG);
|
||||
} else {
|
||||
upgradeData.put(TAG_ITEM_TAG, itemTag);
|
||||
}
|
||||
|
||||
turtle.updateUpgradeNBTData(side);
|
||||
}
|
||||
|
||||
private <T> T withEquippedItem(ITurtleAccess turtle, TurtleSide side, Direction direction, Function<TurtlePlayer, T> action) {
|
||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtle.getPosition(), direction);
|
||||
turtlePlayer.loadInventory(getToolStack(turtle, side));
|
||||
|
||||
var result = action.apply(turtlePlayer);
|
||||
|
||||
setToolStack(turtle, side, turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND));
|
||||
turtlePlayer.player().getInventory().clearContent();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
|
||||
return switch (verb) {
|
||||
case ATTACK -> attack(turtle, direction);
|
||||
case DIG -> dig(turtle, direction);
|
||||
case ATTACK -> attack(turtle, side, direction);
|
||||
case DIG -> dig(turtle, side, direction);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,16 +173,14 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attack an entity. This is a <em>very</em> cut down version of {@link Player#attack(Entity)}, which doesn't handle
|
||||
* enchantments, knockback, etc... Unfortunately we can't call attack directly as damage calculations are rather
|
||||
* different (and we don't want to play sounds/particles).
|
||||
* 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.
|
||||
* @see Player#attack(Entity)
|
||||
*/
|
||||
private TurtleCommandResult attack(ITurtleAccess turtle, Direction direction) {
|
||||
private TurtleCommandResult attack(ITurtleAccess turtle, TurtleSide side, Direction direction) {
|
||||
// Create a fake player, and orient it appropriately
|
||||
var world = turtle.getLevel();
|
||||
var position = turtle.getPosition();
|
||||
@@ -107,10 +192,11 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
var turtlePos = player.position();
|
||||
var rayDir = player.getViewVector(1.0f);
|
||||
var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null);
|
||||
var attacked = false;
|
||||
if (hit instanceof EntityHitResult entityHit) {
|
||||
// Load up the turtle's inventory
|
||||
var stackCopy = item.copy();
|
||||
turtlePlayer.loadInventory(stackCopy);
|
||||
var stack = getToolStack(turtle, side);
|
||||
turtlePlayer.loadInventory(stack);
|
||||
|
||||
var hitEntity = entityHit.getEntity();
|
||||
|
||||
@@ -118,66 +204,125 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle));
|
||||
|
||||
// Attack the entity
|
||||
var attacked = false;
|
||||
var result = PlatformHelper.get().canAttackEntity(player, hitEntity);
|
||||
if (result.consumesAction()) {
|
||||
attacked = true;
|
||||
} else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) {
|
||||
var damage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier;
|
||||
if (damage > 0.0f) {
|
||||
var source = player.damageSources().playerAttack(player);
|
||||
if (hitEntity instanceof ArmorStand) {
|
||||
// Special case for armor stands: attack twice to guarantee destroy
|
||||
hitEntity.hurt(source, damage);
|
||||
if (hitEntity.isAlive()) hitEntity.hurt(source, damage);
|
||||
attacked = true;
|
||||
} else {
|
||||
if (hitEntity.hurt(source, damage)) attacked = true;
|
||||
}
|
||||
}
|
||||
attacked = attack(player, direction, hitEntity);
|
||||
}
|
||||
|
||||
// Stop claiming drops
|
||||
TurtleUtil.stopConsuming(turtle);
|
||||
|
||||
// Put everything we collected into the turtles inventory, then return
|
||||
// Put everything we collected into the turtles inventory.
|
||||
setToolStack(turtle, side, player.getItemInHand(InteractionHand.MAIN_HAND));
|
||||
player.getInventory().clearContent();
|
||||
if (attacked) return TurtleCommandResult.success();
|
||||
}
|
||||
|
||||
return TurtleCommandResult.failure("Nothing to attack here");
|
||||
return attacked ? TurtleCommandResult.success() : TurtleCommandResult.failure("Nothing to attack here");
|
||||
}
|
||||
|
||||
private TurtleCommandResult dig(ITurtleAccess turtle, Direction direction) {
|
||||
if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deployCopiedItem(item.copy(), turtle, direction, null, null)) {
|
||||
return TurtleCommandResult.success();
|
||||
/**
|
||||
* Attack an entity. This is a copy of {@link Player#attack(Entity)}, with some unwanted features removed (sweeping
|
||||
* edge). This is a little limited.
|
||||
* <p>
|
||||
* Ideally we'd use attack directly (if other mods mixin to that method, we won't support their features).
|
||||
* Unfortunately,that doesn't give us any feedback to whether the attack occurred or not (and we don't want to play
|
||||
* sounds/particles).
|
||||
*
|
||||
* @param player The fake player doing the attacking.
|
||||
* @param direction The direction the turtle is attacking.
|
||||
* @param entity The entity to attack.
|
||||
* @return Whether we attacked or not.
|
||||
* @see Player#attack(Entity)
|
||||
*/
|
||||
private boolean attack(ServerPlayer player, Direction direction, Entity entity) {
|
||||
var baseDamage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier;
|
||||
var bonusDamage = EnchantmentHelper.getDamageBonus(
|
||||
player.getItemInHand(InteractionHand.MAIN_HAND), entity instanceof LivingEntity target ? target.getMobType() : MobType.UNDEFINED
|
||||
);
|
||||
var damage = baseDamage + bonusDamage;
|
||||
if (damage <= 0) return false;
|
||||
|
||||
var knockBack = EnchantmentHelper.getKnockbackBonus(player);
|
||||
|
||||
// We follow the logic in Player.attack of setting the entity on fire before attacking, so it's burning when it
|
||||
// (possibly) dies.
|
||||
var fireAspect = EnchantmentHelper.getFireAspect(player);
|
||||
var onFire = false;
|
||||
if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) {
|
||||
onFire = true;
|
||||
target.setSecondsOnFire(1);
|
||||
}
|
||||
|
||||
var level = (ServerLevel) turtle.getLevel();
|
||||
var turtlePosition = turtle.getPosition();
|
||||
var source = player.damageSources().playerAttack(player);
|
||||
if (!entity.hurt(source, damage)) {
|
||||
// If we failed to damage the entity, undo us setting the entity on fire.
|
||||
if (onFire) entity.clearFire();
|
||||
return false;
|
||||
}
|
||||
|
||||
var blockPosition = turtlePosition.relative(direction);
|
||||
// Special case for armor stands: attack twice to guarantee destroy
|
||||
if (entity.isAlive() && entity instanceof ArmorStand) entity.hurt(source, damage);
|
||||
|
||||
// Apply knockback
|
||||
if (knockBack > 0) {
|
||||
if (entity instanceof LivingEntity target) {
|
||||
target.knockback(knockBack * 0.5, -direction.getStepX(), -direction.getStepZ());
|
||||
} else {
|
||||
entity.push(direction.getStepX() * knockBack * 0.5, 0.1, direction.getStepZ() * knockBack * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply remaining enchantments
|
||||
if (entity instanceof LivingEntity target) EnchantmentHelper.doPostHurtEffects(target, player);
|
||||
EnchantmentHelper.doPostDamageEffects(player, entity);
|
||||
|
||||
// Damage the original item stack.
|
||||
if (entity instanceof LivingEntity target) {
|
||||
player.getItemInHand(InteractionHand.MAIN_HAND).hurtEnemy(target, player);
|
||||
}
|
||||
|
||||
// Apply fire aspect
|
||||
if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) {
|
||||
target.setSecondsOnFire(4 * fireAspect);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction direction) {
|
||||
var level = (ServerLevel) turtle.getLevel();
|
||||
|
||||
var blockPosition = turtle.getPosition().relative(direction);
|
||||
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
|
||||
return TurtleCommandResult.failure("Nothing to dig here");
|
||||
}
|
||||
|
||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtlePosition, direction);
|
||||
turtlePlayer.loadInventory(item.copy());
|
||||
return withEquippedItem(turtle, side, direction, turtlePlayer -> {
|
||||
var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND);
|
||||
|
||||
// Check if we can break the block
|
||||
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
||||
if (!breakable.isSuccess()) return breakable;
|
||||
// Right-click the block when using a shovel/hoe.
|
||||
if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) {
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
|
||||
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
|
||||
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
|
||||
TurtleUtil.stopConsuming(turtle);
|
||||
// Check if we can break the block
|
||||
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
||||
if (!breakable.isSuccess()) return breakable;
|
||||
|
||||
// Check spawn protection
|
||||
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block");
|
||||
// And break it!
|
||||
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
|
||||
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
|
||||
TurtleUtil.stopConsuming(turtle);
|
||||
|
||||
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block");
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
||||
return state.is(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE)
|
||||
return
|
||||
state.is(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE)
|
||||
// Allow breaking any "instabreak" block.
|
||||
|| state.getDestroySpeed(reader, pos) == 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.shared.turtle.upgrades;
|
||||
|
||||
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;
|
||||
@@ -28,6 +29,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
||||
var toolItem = GsonHelper.getAsItem(object, "item");
|
||||
var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem);
|
||||
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);
|
||||
|
||||
TagKey<Block> breakable = null;
|
||||
if (object.has("breakable")) {
|
||||
@@ -35,7 +38,7 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
||||
breakable = TagKey.create(Registries.BLOCK, tag);
|
||||
}
|
||||
|
||||
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, breakable);
|
||||
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowEnchantments, consumeDurability, breakable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,9 +49,11 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
||||
// 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();
|
||||
var allowsEnchantments = buffer.readBoolean();
|
||||
var consumesDurability = buffer.readEnum(TurtleToolDurability.class);
|
||||
|
||||
var breakable = buffer.readBoolean() ? TagKey.create(Registries.BLOCK, buffer.readResourceLocation()) : null;
|
||||
return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, breakable);
|
||||
return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, allowsEnchantments, consumesDurability, breakable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,6 +62,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
||||
RegistryWrappers.writeId(buffer, RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
|
||||
buffer.writeItem(upgrade.item);
|
||||
buffer.writeFloat(upgrade.damageMulitiplier);
|
||||
buffer.writeBoolean(upgrade.allowEnchantments);
|
||||
buffer.writeEnum(upgrade.consumeDurability);
|
||||
buffer.writeBoolean(upgrade.breakable != null);
|
||||
if (upgrade.breakable != null) buffer.writeResourceLocation(upgrade.breakable.location());
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.util;
|
||||
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.io.OutputStream;
|
||||
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));
|
||||
|
||||
@@ -217,5 +217,15 @@
|
||||
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. Cela désactive également les programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
|
||||
"gui.computercraft.config.peripheral.modem_range.tooltip": "La portée des modems sans fil à basse altitude par temps dégagé, en mètres.\nPlage : 0 ~ 100000",
|
||||
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "La limite de la quantité de données du moniteur pouvant être envoyées *par tick*. Note :\n - La bande passante est mesurée avant la compression, donc les données envoyées\n au client sont plus petites.\n - Cela ignore le nombre de joueurs auxquels un paquet est envoyé. La mise à jour d'un\n moniteur pour un joueur consomme la même limite de bande passante que l'envoi à 20.\n - Un moniteur de taille normale envoie ~25ko de données. Ainsi, la valeur par défaut (1Mo) permet \n à environ 40 moniteurs d'être mis à jour en un seul tick.\nMettre à 0 pour désactiver.\nPlage : > 0",
|
||||
"gui.computercraft.config.http.rules.tooltip": "Une liste de règles qui contrôlent le comportement de l'API \"http\" pour des domaines\nou des IP spécifiques. Chaque règle est un élément avec un 'hôte' à comparer et une série\nde propriétés. Les règles sont évaluées dans l'ordre, ce qui signifie que les règles antérieures\nremplacent les suivantes.\nL'hôte peut être un nom de domaine (\"pastebin.com\"), un astérisque (\"*.pastebin.com\") ou\nune notation CIDR (\"127.0.0.0/8\").\nS'il n'y a pas de règles, le domaine est bloqué."
|
||||
"gui.computercraft.config.http.rules.tooltip": "Une liste de règles qui contrôlent le comportement de l'API \"http\" pour des domaines\nou des IP spécifiques. Chaque règle est un élément avec un 'hôte' à comparer et une série\nde propriétés. Les règles sont évaluées dans l'ordre, ce qui signifie que les règles antérieures\nremplacent les suivantes.\nL'hôte peut être un nom de domaine (\"pastebin.com\"), un astérisque (\"*.pastebin.com\") ou\nune notation CIDR (\"127.0.0.0/8\").\nS'il n'y a pas de règles, le domaine est bloqué.",
|
||||
"gui.computercraft.config.http.proxy.host.tooltip": "Le nom d'hôte ou l'adresse IP du serveur proxy.",
|
||||
"gui.computercraft.config.http.proxy.tooltip": "Tunnelise les requêtes HTTP et websocket via un serveur proxy. Affecte uniquement\nles règles HTTP avec \"use_proxy\" défini sur true (désactivé par défaut).\nSi l'authentification est requise pour le proxy, créez un fichier \"computercraft-proxy.pw\"\ndans le même dossier que \"computercraft-server.toml\", contenant le\nnom d'utilisateur et mot de passe séparés par deux-points, par ex. \"monutilisateur:monmotdepasse\". Pour\nProxy SOCKS4, seul le nom d'utilisateur est requis.",
|
||||
"gui.computercraft.config.upload_max_size.tooltip": "La taille limite de téléversement de fichier, en octets. Doit être compris entre 1 Kio et 16 Mio.\nGardez à l'esprit que les téléversements sont traités en un seul clic - les fichiers volumineux ou\nde mauvaises performances réseau peuvent bloquer le thread du réseau. Et attention à l'espace disque !\nPlage : 1024 ~ 16777216",
|
||||
"gui.computercraft.config.http.proxy": "Proxy",
|
||||
"gui.computercraft.config.http.proxy.host": "Nom d'hôte",
|
||||
"gui.computercraft.config.http.proxy.port": "Port",
|
||||
"gui.computercraft.config.http.proxy.port.tooltip": "Le port du serveur proxy.\nPlage : 1 ~ 65536",
|
||||
"gui.computercraft.config.http.proxy.type": "Type de proxy",
|
||||
"gui.computercraft.config.http.proxy.type.tooltip": "Le type de proxy à utiliser.\nValeurs autorisées : HTTP, HTTPS, SOCKS4, SOCKS5",
|
||||
"gui.computercraft.config.upload_max_size": "Taille limite de téléversement de fichiers (octets)"
|
||||
}
|
||||
|
||||
@@ -177,5 +177,23 @@
|
||||
"gui.computercraft.config.turtle.need_fuel": "Включить механику топлива",
|
||||
"gui.computercraft.config.turtle.normal_fuel_limit": "Лимит топлива Черепашек",
|
||||
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Лимит топлива для Черепашек.\nОграничение: > 0",
|
||||
"gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками."
|
||||
"gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками.",
|
||||
"gui.computercraft.config.http.proxy.port": "Порт",
|
||||
"gui.computercraft.config.http.proxy.port.tooltip": "Порт прокси-сервера.\nДиапазон: 1 ~ 65536",
|
||||
"gui.computercraft.config.http.proxy.host": "Имя хоста",
|
||||
"gui.computercraft.config.http.proxy": "Proxy",
|
||||
"gui.computercraft.config.http.proxy.host.tooltip": "Имя хоста или IP-адрес прокси-сервера.",
|
||||
"gui.computercraft.config.http.proxy.tooltip": "Туннелирует HTTP-запросы и запросы websocket через прокси-сервер. Влияет только на HTTP\nправила с параметром \"use_proxy\" в значении true (отключено по умолчанию).\nЕсли для прокси-сервера требуется аутентификация, создайте \"computercraft-proxy.pw\"\nфайл в том же каталоге, что и \"computercraft-server.toml\", содержащий имя\nпользователя и пароль, разделенные двоеточием, например \"myuser:mypassword\". Для\nпрокси-серверов SOCKS4 требуется только имя пользователя.",
|
||||
"gui.computercraft.config.http.proxy.type": "Тип прокси-сервера",
|
||||
"gui.computercraft.config.http.proxy.type.tooltip": "Тип используемого прокси-сервера.\nДопустимые значения: HTTP, HTTPS, SOCKS4, SOCKS5",
|
||||
"gui.computercraft.upload.no_response.msg": "Ваш компьютер не использовал переданные вами файлы. Возможно, вам потребуется запустить программу %s и повторить попытку.",
|
||||
"tracking_field.computercraft.max": "%s (максимальное)",
|
||||
"tracking_field.computercraft.count": "%s (количество)",
|
||||
"gui.computercraft.config.http.rules": "Разрешающие/запрещающие правила",
|
||||
"gui.computercraft.config.http.websocket_enabled": "Включить веб-сокеты",
|
||||
"gui.computercraft.config.http.websocket_enabled.tooltip": "Включить использование http веб-сокетов. Для этого необходимо, чтобы параметр «http_enable» был true.",
|
||||
"gui.computercraft.config.log_computer_errors": "Регистрировать ошибки компьютера",
|
||||
"gui.computercraft.config.log_computer_errors.tooltip": "Регистрировать исключения, вызванные периферийными устройствами и другими объектами Lua. Это облегчает\nдля авторам модов устранение проблем, но может привести к спаму в логах, если люди будут использовать\nглючные методы.",
|
||||
"gui.computercraft.config.maximum_open_files": "Максимальное количество файлов, открытых на одном компьютере",
|
||||
"gui.computercraft.config.http.rules.tooltip": "Список правил, которые контролируют поведение «http» API для определенных доменов или\nIP-адресов. Каждое правило представляет собой элемент с «узлом» для сопоставления и набором\nсвойств. Правила оцениваются по порядку, то есть более ранние правила перевешивают\nболее поздние.\nХост может быть доменным именем (\"pastebin.com\"), wildcard-сертификатом (\"*.pastebin.com\") или\nнотацией CIDR (\"127.0.0.0/8\").\nЕсли правил нет, домен блокируется."
|
||||
}
|
||||
|
||||
@@ -59,6 +59,11 @@ import java.util.function.Predicate;
|
||||
|
||||
@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");
|
||||
|
||||
@@ -7,9 +7,13 @@ package dan200.computercraft.gametest
|
||||
import dan200.computercraft.api.detail.BasicItemDetailProvider
|
||||
import dan200.computercraft.api.detail.VanillaDetailRegistries
|
||||
import dan200.computercraft.api.lua.ObjectArguments
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade
|
||||
import dan200.computercraft.api.turtle.TurtleSide
|
||||
import dan200.computercraft.api.upgrades.UpgradeData
|
||||
import dan200.computercraft.core.apis.PeripheralAPI
|
||||
import dan200.computercraft.gametest.api.*
|
||||
import dan200.computercraft.gametest.core.TestHooks
|
||||
import dan200.computercraft.impl.TurtleUpgrades
|
||||
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
|
||||
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
|
||||
import dan200.computercraft.shared.ModRegistry
|
||||
@@ -30,13 +34,17 @@ import net.minecraft.world.entity.EntityType
|
||||
import net.minecraft.world.entity.item.PrimedTnt
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.enchantment.Enchantments
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.FenceBlock
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.hamcrest.Matchers.array
|
||||
import org.hamcrest.Matchers.instanceOf
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class Turtle_Test {
|
||||
@@ -78,6 +86,26 @@ class Turtle_Test {
|
||||
thenExecute { helper.assertBlockPresent(Blocks.LAVA, BlockPos(2, 2, 2)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that calling [net.minecraft.world.item.Item.use] will not place blocks too far away.
|
||||
*
|
||||
* This is caused by items using [net.minecraft.world.item.Item.getPlayerPOVHitResult] to perform a ray trace, which
|
||||
* ignores turtle's reduced reach distance.
|
||||
*
|
||||
* @see [#1497](https://github.com/cc-tweaked/CC-Tweaked/issues/1497)
|
||||
*/
|
||||
@GameTest
|
||||
fun Place_use_reach_limit(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
turtle.placeDown(ObjectArguments()).await()
|
||||
.assertArrayEquals(true, message = "Placed water")
|
||||
}
|
||||
thenExecute {
|
||||
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 2))
|
||||
helper.assertBlockHas(BlockPos(2, 5, 2), BlockStateProperties.WATERLOGGED, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks turtles can place when waterlogged.
|
||||
*
|
||||
@@ -151,6 +179,85 @@ class Turtle_Test {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Digging using a pickaxe with `{"consumesDurability": "always"}`, consumes durability.
|
||||
*/
|
||||
@GameTest
|
||||
fun Dig_consume_durability(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer { turtle.dig(Optional.empty()).await() }
|
||||
thenExecute {
|
||||
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 3))
|
||||
helper.assertContainerExactly(BlockPos(2, 2, 2), listOf(ItemStack(Items.COBBLESTONE)))
|
||||
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access
|
||||
val upgrade = turtle.getUpgrade(TurtleSide.LEFT)
|
||||
assertEquals(TurtleUpgrades.instance().get("cctest:wooden_pickaxe"), upgrade, "Upgrade is a wooden pickaxe")
|
||||
|
||||
val item = ItemStack(Items.WOODEN_PICKAXE)
|
||||
item.damageValue = 1
|
||||
helper.assertUpgradeItem(item, turtle.getUpgradeWithData(TurtleSide.LEFT)!!)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Digging using a pickaxe with `{"consumesDurability": "always"}` and no durability removes the tool.
|
||||
*/
|
||||
@GameTest
|
||||
fun Dig_breaks_tool(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer { turtle.dig(Optional.empty()).await() }
|
||||
thenExecute {
|
||||
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 3))
|
||||
helper.assertContainerExactly(BlockPos(2, 2, 2), listOf(ItemStack(Items.COBBLESTONE)))
|
||||
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access
|
||||
val upgrade = turtle.getUpgrade(TurtleSide.LEFT)
|
||||
assertEquals(null, upgrade, "Upgrade broke")
|
||||
|
||||
helper.assertUpgradeItem(
|
||||
ItemStack(Items.WOODEN_PICKAXE),
|
||||
UpgradeData.ofDefault(TurtleUpgrades.instance().get("cctest:wooden_pickaxe")),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Digging using a silk-touch enchanted pickaxe with `{"consumesDurability": "when_enchanted"}`, consumes durability
|
||||
* uses silk touch.
|
||||
*/
|
||||
@GameTest
|
||||
fun Dig_enchanted_consume_durability(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer { turtle.dig(Optional.empty()).await() }
|
||||
thenExecute {
|
||||
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 3))
|
||||
helper.assertContainerExactly(BlockPos(2, 2, 2), listOf(ItemStack(Items.STONE)))
|
||||
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()).access
|
||||
val upgrade = turtle.getUpgrade(TurtleSide.LEFT)
|
||||
assertEquals(
|
||||
TurtleUpgrades.instance().get("cctest:netherite_pickaxe"),
|
||||
upgrade,
|
||||
"Upgrade is a netherite pickaxe",
|
||||
)
|
||||
|
||||
val item = ItemStack(Items.NETHERITE_PICKAXE)
|
||||
item.damageValue = 1
|
||||
item.enchant(Enchantments.SILK_TOUCH, 1)
|
||||
item.setRepairCost(1)
|
||||
|
||||
helper.assertUpgradeItem(item, turtle.getUpgradeWithData(TurtleSide.LEFT)!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun GameTestHelper.assertUpgradeItem(expected: ItemStack, upgrade: UpgradeData<ITurtleUpgrade>) {
|
||||
if (!ItemStack.matches(expected, upgrade.upgradeItem)) {
|
||||
fail("Invalid upgrade item\n Expected => ${expected.tag}\n Actual => ${upgrade.upgradeItem.tag}")
|
||||
}
|
||||
|
||||
if (!ItemStack.matches(ItemStack(expected.item), upgrade.upgrade.craftingItem)) {
|
||||
fail("Original upgrade item has changed (is now ${upgrade.upgrade.craftingItem})")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks turtles can place monitors
|
||||
*
|
||||
@@ -466,7 +573,7 @@ class Turtle_Test {
|
||||
fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
|
||||
val testInfo = (helper as GameTestHelperAccessor).testInfo as GameTestInfoAccessor
|
||||
|
||||
val events = mutableListOf<Pair<String, String>>()
|
||||
val events = CopyOnWriteArrayList<Pair<String, String>>()
|
||||
var running = false
|
||||
thenStartComputer("listen") {
|
||||
running = true
|
||||
@@ -485,15 +592,13 @@ class Turtle_Test {
|
||||
turtle.back().await().assertArrayEquals(true, message = "Moved turtle forward")
|
||||
TestHooks.LOG.info("[{}] Finished turtle at {}", testInfo, testInfo.`computercraft$getTick`())
|
||||
}
|
||||
thenIdle(4) // Should happen immediately, but computers might be slow.
|
||||
thenExecute {
|
||||
assertEquals(
|
||||
listOf(
|
||||
"peripheral_detach" to "right",
|
||||
"peripheral" to "right",
|
||||
),
|
||||
events,
|
||||
thenWaitUntil {
|
||||
val expected = listOf(
|
||||
"peripheral_detach" to "right",
|
||||
"peripheral" to "right",
|
||||
)
|
||||
|
||||
if (events != expected) helper.fail("Expected $expected, but received $events")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "computercraft:tool",
|
||||
"item": "minecraft:netherite_pickaxe",
|
||||
"allowEnchantments": true,
|
||||
"consumeDurability": "when_enchanted"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"type": "computercraft:tool",
|
||||
"item": "minecraft:wooden_pickaxe",
|
||||
"consumeDurability": "always"
|
||||
}
|
||||
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_breaks_tool.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_breaks_tool.snbt
generated
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
DataVersion: 3218,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.dig_breaks_tool", LeftUpgrade: "cctest:wooden_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 58}}, On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
||||
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_consume_durability.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_consume_durability.snbt
generated
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
DataVersion: 3218,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.dig_consume_durability", LeftUpgrade: "cctest:wooden_pickaxe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
{
|
||||
DataVersion: 3337,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.dig_enchanted_consume_durability", LeftUpgrade: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:silk_touch", lvl: 1s}], RepairCost: 1}}, On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
||||
139
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_use_reach_limit.snbt
generated
Normal file
139
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_use_reach_limit.snbt
generated
Normal file
@@ -0,0 +1,139 @@
|
||||
{
|
||||
DataVersion: 3337,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 1, 2], state: "minecraft:air"},
|
||||
{pos: [2, 1, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:ladder{facing:north,waterlogged:false}"},
|
||||
{pos: [2, 2, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 3, 2], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 3, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 3, 2], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 3, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 4, 2], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 4, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 4, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:water_bucket"}], Label: "turtle_test.place_use_reach_limit", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 4, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 4, 2], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 4, 3], state: "minecraft:light_gray_stained_glass"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:light_gray_stained_glass",
|
||||
"minecraft:air",
|
||||
"minecraft:ladder{facing:north,waterlogged:false}",
|
||||
"computercraft:turtle_normal{facing:north,waterlogged:false}"
|
||||
]
|
||||
}
|
||||
@@ -479,9 +479,11 @@ public interface IArguments {
|
||||
* yourself.
|
||||
*
|
||||
* @return An {@link IArguments} instance which can escape the current scope. May be {@code this}.
|
||||
* @throws LuaException For the same reasons as {@link #get(int)}.
|
||||
* @throws LuaException For the same reasons as {@link #get(int)}.
|
||||
* @throws IllegalStateException If marking these arguments as escaping outside the scope of the original function.
|
||||
*/
|
||||
default IArguments escapes() throws LuaException {
|
||||
// TODO(1.21.0): Make this return void, require that it mutates this.
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,26 @@
|
||||
|
||||
package dan200.computercraft.core;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.core.asm.GenericMethod;
|
||||
import dan200.computercraft.core.asm.LuaMethodSupplier;
|
||||
import dan200.computercraft.core.asm.PeripheralMethodSupplier;
|
||||
import dan200.computercraft.core.computer.ComputerThread;
|
||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
|
||||
import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler;
|
||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.lua.MachineEnvironment;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
|
||||
import javax.annotation.CheckReturnValue;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -20,27 +33,24 @@ public final class ComputerContext {
|
||||
private final GlobalEnvironment globalEnvironment;
|
||||
private final ComputerThread computerScheduler;
|
||||
private final MainThreadScheduler mainThreadScheduler;
|
||||
private final ILuaMachine.Factory factory;
|
||||
private final ILuaMachine.Factory luaFactory;
|
||||
private final List<ILuaAPIFactory> apiFactories;
|
||||
private final MethodSupplier<LuaMethod> luaMethods;
|
||||
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||
|
||||
public ComputerContext(
|
||||
ComputerContext(
|
||||
GlobalEnvironment globalEnvironment, ComputerThread computerScheduler,
|
||||
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory factory
|
||||
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory,
|
||||
List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods,
|
||||
MethodSupplier<PeripheralMethod> peripheralMethods
|
||||
) {
|
||||
this.globalEnvironment = globalEnvironment;
|
||||
this.computerScheduler = computerScheduler;
|
||||
this.mainThreadScheduler = mainThreadScheduler;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default {@link ComputerContext} with the given global environment.
|
||||
*
|
||||
* @param environment The current global environment.
|
||||
* @param threads The number of threads to use for the {@link #computerScheduler()}
|
||||
* @param mainThreadScheduler The main thread scheduler to use.
|
||||
*/
|
||||
public ComputerContext(GlobalEnvironment environment, int threads, MainThreadScheduler mainThreadScheduler) {
|
||||
this(environment, new ComputerThread(threads), mainThreadScheduler, CobaltLuaMachine::new);
|
||||
this.luaFactory = luaFactory;
|
||||
this.apiFactories = apiFactories;
|
||||
this.luaMethods = luaMethods;
|
||||
this.peripheralMethods = peripheralMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +87,35 @@ public final class ComputerContext {
|
||||
* @return The current Lua machine factory.
|
||||
*/
|
||||
public ILuaMachine.Factory luaFactory() {
|
||||
return factory;
|
||||
return luaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional APIs to inject into each computer.
|
||||
*
|
||||
* @return All available API factories.
|
||||
*/
|
||||
public List<ILuaAPIFactory> apiFactories() {
|
||||
return apiFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MethodSupplier} used to find methods on Lua values.
|
||||
*
|
||||
* @return The {@link LuaMethod} method supplier.
|
||||
* @see MachineEnvironment#luaMethods()
|
||||
*/
|
||||
public MethodSupplier<LuaMethod> luaMethods() {
|
||||
return luaMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MethodSupplier} used to find methods on peripherals.
|
||||
*
|
||||
* @return The {@link PeripheralMethod} method supplier.
|
||||
*/
|
||||
public MethodSupplier<PeripheralMethod> peripheralMethods() {
|
||||
return peripheralMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,4 +144,119 @@ public final class ComputerContext {
|
||||
throw new IllegalStateException("Failed to shutdown ComputerContext in time.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@linkplain Builder builder} for a computer context.
|
||||
*
|
||||
* @param environment The {@linkplain ComputerContext#globalEnvironment() global environment} for this context.
|
||||
* @return The builder for a new context.
|
||||
*/
|
||||
public static Builder builder(GlobalEnvironment environment) {
|
||||
return new Builder(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for a {@link ComputerContext}.
|
||||
*
|
||||
* @see ComputerContext#builder(GlobalEnvironment)
|
||||
*/
|
||||
public static class Builder {
|
||||
private final GlobalEnvironment environment;
|
||||
private int threads = 1;
|
||||
private @Nullable MainThreadScheduler mainThreadScheduler;
|
||||
private @Nullable ILuaMachine.Factory luaFactory;
|
||||
private @Nullable List<ILuaAPIFactory> apiFactories;
|
||||
private @Nullable List<GenericMethod> genericMethods;
|
||||
|
||||
Builder(GlobalEnvironment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of threads the {@link ComputerThread} will use.
|
||||
*
|
||||
* @param threads The number of threads to use.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#computerScheduler()
|
||||
*/
|
||||
public Builder computerThreads(int threads) {
|
||||
if (threads < 1) throw new IllegalArgumentException("Threads must be >= 1");
|
||||
this.threads = threads;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MainThreadScheduler} for this context.
|
||||
*
|
||||
* @param scheduler The main thread scheduler.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#mainThreadScheduler()
|
||||
*/
|
||||
public Builder mainThreadScheduler(MainThreadScheduler scheduler) {
|
||||
Objects.requireNonNull(scheduler);
|
||||
if (mainThreadScheduler != null) throw new IllegalStateException("Main-thread scheduler already specified");
|
||||
mainThreadScheduler = scheduler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ILuaMachine.Factory} for this context.
|
||||
*
|
||||
* @param factory The Lua machine factory.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#luaFactory()
|
||||
*/
|
||||
public Builder luaFactory(ILuaMachine.Factory factory) {
|
||||
Objects.requireNonNull(factory);
|
||||
if (luaFactory != null) throw new IllegalStateException("Main-thread scheduler already specified");
|
||||
luaFactory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the additional {@linkplain ILuaAPIFactory APIs} to add to each computer.
|
||||
*
|
||||
* @param apis A list of API factories.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#apiFactories()
|
||||
*/
|
||||
public Builder apiFactories(Collection<ILuaAPIFactory> apis) {
|
||||
Objects.requireNonNull(apis);
|
||||
if (apiFactories != null) throw new IllegalStateException("Main-thread scheduler already specified");
|
||||
apiFactories = List.copyOf(apis);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the set of {@link GenericMethod}s used by the {@linkplain MethodSupplier method suppliers}.
|
||||
*
|
||||
* @param genericMethods A list of API factories.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#luaMethods()
|
||||
* @see ComputerContext#peripheralMethods()
|
||||
*/
|
||||
public Builder genericMethods(Collection<GenericMethod> genericMethods) {
|
||||
Objects.requireNonNull(genericMethods);
|
||||
if (this.genericMethods != null) throw new IllegalStateException("Main-thread scheduler already specified");
|
||||
this.genericMethods = List.copyOf(genericMethods);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ComputerContext}.
|
||||
*
|
||||
* @return The newly created context.
|
||||
*/
|
||||
public ComputerContext build() {
|
||||
return new ComputerContext(
|
||||
environment,
|
||||
new ComputerThread(threads),
|
||||
mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler,
|
||||
luaFactory == null ? CobaltLuaMachine::new : luaFactory,
|
||||
apiFactories == null ? List.of() : apiFactories,
|
||||
LuaMethodSupplier.create(genericMethods == null ? List.of() : genericMethods),
|
||||
PeripheralMethodSupplier.create(genericMethods == null ? List.of() : genericMethods)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import dan200.computercraft.core.computer.ComputerEnvironment;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.metrics.OperationTimer;
|
||||
import dan200.computercraft.core.metrics.Metric;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
import dan200.computercraft.core.metrics.OperationTimer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -132,7 +132,8 @@ public class OSAPI implements ILuaAPI {
|
||||
* @param name The name of the event to queue.
|
||||
* @param args The parameters of the event.
|
||||
* @cc.tparam string name The name of the event to queue.
|
||||
* @cc.param ... The parameters of the event.
|
||||
* @cc.param ... The parameters of the event. These can be any primitive type (boolean, number, string) as well as
|
||||
* tables. Other types (like functions), as well as metatables, will not be preserved.
|
||||
* @cc.see os.pullEvent To pull the event queued
|
||||
*/
|
||||
@LuaFunction
|
||||
|
||||
@@ -7,13 +7,12 @@ package dan200.computercraft.core.apis;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.NotAttachedException;
|
||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.core.util.LuaUtil;
|
||||
|
||||
@@ -44,7 +43,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
|
||||
additionalTypes = peripheral.getAdditionalTypes();
|
||||
|
||||
methodMap = PeripheralAPI.getMethods(peripheral);
|
||||
methodMap = peripheralMethods.getSelfMethods(peripheral);
|
||||
}
|
||||
|
||||
public IPeripheral getPeripheral() {
|
||||
@@ -172,11 +171,13 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
|
||||
private final IAPIEnvironment environment;
|
||||
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||
private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
|
||||
private boolean running;
|
||||
|
||||
public PeripheralAPI(IAPIEnvironment environment) {
|
||||
public PeripheralAPI(IAPIEnvironment environment, MethodSupplier<PeripheralMethod> peripheralMethods) {
|
||||
this.environment = environment;
|
||||
this.peripheralMethods = peripheralMethods;
|
||||
this.environment.setPeripheralChangeListener(this);
|
||||
running = false;
|
||||
}
|
||||
@@ -315,21 +316,4 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, PeripheralMethod> getMethods(IPeripheral peripheral) {
|
||||
var dynamicMethods = peripheral instanceof IDynamicPeripheral
|
||||
? Objects.requireNonNull(((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null")
|
||||
: LuaMethod.EMPTY_METHODS;
|
||||
|
||||
var methods = PeripheralMethod.GENERATOR.getMethods(peripheral.getClass());
|
||||
|
||||
Map<String, PeripheralMethod> methodMap = new HashMap<>(methods.size() + dynamicMethods.length);
|
||||
for (var i = 0; i < dynamicMethods.length; i++) {
|
||||
methodMap.put(dynamicMethods[i], PeripheralMethod.DYNAMIC.get(i));
|
||||
}
|
||||
for (var method : methods) {
|
||||
methodMap.put(method.getName(), method.getMethod());
|
||||
}
|
||||
return methodMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class TableHelper {
|
||||
|
||||
public static Optional<Double> optRealField(Map<?, ?> table, String key) throws LuaException {
|
||||
var value = table.get(key);
|
||||
if(value == null) {
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(getRealField(table, key));
|
||||
|
||||
@@ -27,7 +27,6 @@ import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
import io.netty.handler.traffic.AbstractTrafficShapingHandler;
|
||||
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -193,7 +192,7 @@ public final class NetworkUtils {
|
||||
* @return The SSL handler.
|
||||
* @see io.netty.handler.ssl.SslHandler
|
||||
*/
|
||||
private static SslHandler makeSslHandler(SocketChannel ch, @NotNull SslContext sslContext, int timeout, String peerHost, int peerPort) {
|
||||
private static SslHandler makeSslHandler(SocketChannel ch, SslContext sslContext, int timeout, String peerHost, int peerPort) {
|
||||
var handler = sslContext.newHandler(ch.alloc(), peerHost, peerPort);
|
||||
if (timeout > 0) handler.setHandshakeTimeoutMillis(timeout);
|
||||
return handler;
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A predicate on an address. Matches against a domain and an ip address.
|
||||
@@ -19,8 +20,6 @@ import java.util.regex.Pattern;
|
||||
* @see AddressRule#apply(Iterable, String, InetSocketAddress) for the actual handling of this rule.
|
||||
*/
|
||||
interface AddressPredicate {
|
||||
Logger LOG = LoggerFactory.getLogger(AddressPredicate.class);
|
||||
|
||||
default boolean matches(String domain) {
|
||||
return false;
|
||||
}
|
||||
@@ -51,28 +50,25 @@ interface AddressPredicate {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static HostRange parse(String addressStr, String prefixSizeStr) {
|
||||
int prefixSize;
|
||||
try {
|
||||
prefixSize = Integer.parseInt(prefixSizeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.error(
|
||||
"Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
|
||||
throw new InvalidRuleException(String.format(
|
||||
"Invalid host host '%s': Cannot extract size of CIDR mask from '%s'.",
|
||||
addressStr + '/' + prefixSizeStr, prefixSizeStr
|
||||
);
|
||||
return null;
|
||||
));
|
||||
}
|
||||
|
||||
InetAddress address;
|
||||
try {
|
||||
address = InetAddresses.forString(addressStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error(
|
||||
"Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.",
|
||||
addressStr + '/' + prefixSizeStr, prefixSizeStr
|
||||
);
|
||||
return null;
|
||||
throw new InvalidRuleException(String.format(
|
||||
"Invalid host '%s': Cannot extract IP address from '%s'.",
|
||||
addressStr + '/' + prefixSizeStr, addressStr
|
||||
));
|
||||
}
|
||||
|
||||
// Mask the bytes of the IP address.
|
||||
@@ -112,16 +108,38 @@ interface AddressPredicate {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class PrivatePattern implements AddressPredicate {
|
||||
static final PrivatePattern INSTANCE = new PrivatePattern();
|
||||
|
||||
private static final Set<InetAddress> additionalAddresses = Arrays.stream(new String[]{
|
||||
// Block various cloud providers internal IPs.
|
||||
"100.100.100.200", // Alibaba
|
||||
"192.0.0.192", // Oracle
|
||||
}).map(InetAddresses::forString).collect(Collectors.toUnmodifiableSet());
|
||||
|
||||
@Override
|
||||
public boolean matches(InetAddress socketAddress) {
|
||||
return socketAddress.isAnyLocalAddress()
|
||||
|| socketAddress.isLoopbackAddress()
|
||||
|| socketAddress.isLinkLocalAddress()
|
||||
|| socketAddress.isSiteLocalAddress();
|
||||
return
|
||||
socketAddress.isAnyLocalAddress() // 0.0.0.0, ::0
|
||||
|| socketAddress.isLoopbackAddress() // 127.0.0.0/8, ::1
|
||||
|| socketAddress.isLinkLocalAddress() // 169.254.0.0/16, fe80::/10
|
||||
|| socketAddress.isSiteLocalAddress() // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10
|
||||
|| socketAddress.isMulticastAddress() // 224.0.0.0/4, ff00::/8
|
||||
|| isUniqueLocalAddress(socketAddress) // fd00::/8
|
||||
|| additionalAddresses.contains(socketAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an IP address lives inside the ULA address range.
|
||||
*
|
||||
* @param address The IP address to test.
|
||||
* @return Whether this address sits in the ULA address range.
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Unique_local_address">Unique local address on Wikipedia</a>
|
||||
*/
|
||||
private boolean isUniqueLocalAddress(InetAddress address) {
|
||||
// ULA is actually defined as fc00::/7 (so both fc00::/8 and fd00::/8). However, only the latter is actually
|
||||
// defined right now, so let's be conservative.
|
||||
return address instanceof Inet6Address && (address.getAddress()[0] & 0xff) == 0xfd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,14 +35,13 @@ public final class AddressRule {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parse(String filter, OptionalInt port, PartialOptions partial) {
|
||||
var cidr = filter.indexOf('/');
|
||||
if (cidr >= 0) {
|
||||
var addressStr = filter.substring(0, cidr);
|
||||
var prefixSizeStr = filter.substring(cidr + 1);
|
||||
var range = HostRange.parse(addressStr, prefixSizeStr);
|
||||
return range == null ? null : new AddressRule(range, port, partial);
|
||||
return new AddressRule(range, port, partial);
|
||||
} else if (filter.equalsIgnoreCase("$private")) {
|
||||
return new AddressRule(PrivatePattern.INSTANCE, port, partial);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/**
|
||||
* Throw when a {@link AddressRule} cannot be parsed.
|
||||
*
|
||||
* @see AddressRule#parse(String, OptionalInt, PartialOptions)
|
||||
* @see AddressPredicate.HostRange#parse(String, String)
|
||||
*/
|
||||
public class InvalidRuleException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1303376302865132758L;
|
||||
|
||||
public InvalidRuleException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import dan200.computercraft.core.apis.HTTPAPI;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.HandleGeneric;
|
||||
import dan200.computercraft.core.asm.ObjectSource;
|
||||
import dan200.computercraft.core.methods.ObjectSource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Type;
|
||||
@@ -20,17 +19,14 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
public final class Generator<T> {
|
||||
final class Generator<T> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Generator.class);
|
||||
|
||||
private static final AtomicInteger METHOD_ID = new AtomicInteger();
|
||||
@@ -54,10 +50,6 @@ public final class Generator<T> {
|
||||
|
||||
private final Function<T, T> wrap;
|
||||
|
||||
private final LoadingCache<Class<?>, List<NamedMethod<T>>> classCache = CacheBuilder
|
||||
.newBuilder()
|
||||
.build(CacheLoader.from(catching(this::build, Collections.emptyList())));
|
||||
|
||||
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder
|
||||
.newBuilder()
|
||||
.build(CacheLoader.from(catching(this::build, Optional.empty())));
|
||||
@@ -74,58 +66,8 @@ public final class Generator<T> {
|
||||
this.methodDesc = methodDesc.toString();
|
||||
}
|
||||
|
||||
public List<NamedMethod<T>> getMethods(Class<?> klass) {
|
||||
try {
|
||||
return classCache.get(klass);
|
||||
} catch (ExecutionException e) {
|
||||
LOG.error("Error getting methods for {}.", klass.getName(), e.getCause());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private List<NamedMethod<T>> build(Class<?> klass) {
|
||||
ArrayList<NamedMethod<T>> methods = null;
|
||||
for (var method : klass.getMethods()) {
|
||||
var annotation = method.getAnnotation(LuaFunction.class);
|
||||
if (annotation == null) continue;
|
||||
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
LOG.warn("LuaFunction method {}.{} should be an instance method.", method.getDeclaringClass(), method.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
var instance = methodCache.getUnchecked(method).orElse(null);
|
||||
if (instance == null) continue;
|
||||
|
||||
if (methods == null) methods = new ArrayList<>();
|
||||
addMethod(methods, method, annotation, null, instance);
|
||||
}
|
||||
|
||||
for (var method : GenericMethod.all()) {
|
||||
if (!method.target.isAssignableFrom(klass)) continue;
|
||||
|
||||
var instance = methodCache.getUnchecked(method.method).orElse(null);
|
||||
if (instance == null) continue;
|
||||
|
||||
if (methods == null) methods = new ArrayList<>();
|
||||
addMethod(methods, method.method, method.annotation, method.peripheralType, instance);
|
||||
}
|
||||
|
||||
if (methods == null) return Collections.emptyList();
|
||||
methods.trimToSize();
|
||||
return Collections.unmodifiableList(methods);
|
||||
}
|
||||
|
||||
private void addMethod(List<NamedMethod<T>> methods, Method method, LuaFunction annotation, @Nullable PeripheralType genericType, T instance) {
|
||||
var names = annotation.value();
|
||||
var isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
||||
if (names.length == 0) {
|
||||
methods.add(new NamedMethod<>(method.getName(), instance, isSimple, genericType));
|
||||
} else {
|
||||
for (var name : names) {
|
||||
methods.add(new NamedMethod<>(name, instance, isSimple, genericType));
|
||||
}
|
||||
}
|
||||
Optional<T> getMethod(Method method) {
|
||||
return methodCache.getUnchecked(method);
|
||||
}
|
||||
|
||||
private Optional<T> build(Method method) {
|
||||
@@ -336,7 +278,7 @@ public final class Generator<T> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("Guava")
|
||||
private static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) {
|
||||
static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) {
|
||||
return x -> {
|
||||
try {
|
||||
return function.apply(x);
|
||||
|
||||
@@ -14,16 +14,14 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A generic method is a method belonging to a {@link GenericSource} with a known target.
|
||||
*/
|
||||
public class GenericMethod {
|
||||
public final class GenericMethod {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GenericMethod.class);
|
||||
|
||||
final Method method;
|
||||
@@ -31,37 +29,24 @@ public class GenericMethod {
|
||||
final Class<?> target;
|
||||
final @Nullable PeripheralType peripheralType;
|
||||
|
||||
private static final List<GenericSource> sources = new ArrayList<>();
|
||||
private static @Nullable List<GenericMethod> cache;
|
||||
|
||||
GenericMethod(Method method, LuaFunction annotation, Class<?> target, @Nullable PeripheralType peripheralType) {
|
||||
private GenericMethod(Method method, LuaFunction annotation, Class<?> target, @Nullable PeripheralType peripheralType) {
|
||||
this.method = method;
|
||||
this.annotation = annotation;
|
||||
this.target = target;
|
||||
this.peripheralType = peripheralType;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all public static methods annotated with {@link LuaFunction} which belong to a {@link GenericSource}.
|
||||
*
|
||||
* @param source The given generic source.
|
||||
* @return All available generic methods.
|
||||
*/
|
||||
static List<GenericMethod> all() {
|
||||
if (cache != null) return cache;
|
||||
return cache = sources.stream().flatMap(GenericMethod::getMethods).toList();
|
||||
}
|
||||
|
||||
public static synchronized void register(GenericSource source) {
|
||||
Objects.requireNonNull(source, "Source cannot be null");
|
||||
|
||||
if (cache != null) {
|
||||
LOG.warn("Registering a generic source {} after cache has been built. This source will be ignored.", cache);
|
||||
}
|
||||
|
||||
sources.add(source);
|
||||
}
|
||||
|
||||
private static Stream<GenericMethod> getMethods(GenericSource source) {
|
||||
public static Stream<GenericMethod> getMethods(GenericSource source) {
|
||||
Class<?> klass = source.getClass();
|
||||
var type = source instanceof GenericPeripheral generic ? generic.getType() : null;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ package dan200.computercraft.core.asm;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public final class IntCache<T> {
|
||||
final class IntCache<T> {
|
||||
private final IntFunction<T> factory;
|
||||
private volatile Object[] cache = new Object[16];
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public interface LuaMethod {
|
||||
Generator<LuaMethod> GENERATOR = new Generator<>(LuaMethod.class, Collections.singletonList(ILuaContext.class),
|
||||
m -> (target, context, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, args.escapes())))
|
||||
);
|
||||
|
||||
IntCache<LuaMethod> DYNAMIC = new IntCache<>(
|
||||
method -> (instance, context, args) -> ((IDynamicLuaObject) instance).callMethod(context, method, args)
|
||||
);
|
||||
|
||||
String[] EMPTY_METHODS = new String[0];
|
||||
|
||||
MethodResult apply(Object target, ILuaContext context, IArguments args) throws LuaException;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.IDynamicLuaObject;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.core.ComputerContext;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Provides a {@link MethodSupplier} for {@link LuaMethod}s.
|
||||
* <p>
|
||||
* This is used by {@link ComputerContext} to construct {@linkplain ComputerContext#peripheralMethods() the context-wide
|
||||
* method supplier}. It should not be used directly.
|
||||
*/
|
||||
public final class LuaMethodSupplier {
|
||||
private static final Generator<LuaMethod> GENERATOR = new Generator<>(LuaMethod.class, List.of(ILuaContext.class),
|
||||
m -> (target, context, args) -> {
|
||||
var escArgs = args.escapes();
|
||||
return context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, escArgs)));
|
||||
}
|
||||
);
|
||||
private static final IntCache<LuaMethod> DYNAMIC = new IntCache<>(
|
||||
method -> (instance, context, args) -> ((IDynamicLuaObject) instance).callMethod(context, method, args)
|
||||
);
|
||||
|
||||
private LuaMethodSupplier() {
|
||||
}
|
||||
|
||||
public static MethodSupplier<LuaMethod> create(List<GenericMethod> genericMethods) {
|
||||
return new MethodSupplierImpl<>(genericMethods, GENERATOR, DYNAMIC, x -> x instanceof IDynamicLuaObject dynamic
|
||||
? Objects.requireNonNull(dynamic.getMethodNames(), "Dynamic methods cannot be null")
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.NamedMethod;
|
||||
import dan200.computercraft.core.methods.ObjectSource;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static dan200.computercraft.core.asm.Generator.catching;
|
||||
|
||||
final class MethodSupplierImpl<T> implements MethodSupplier<T> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodSupplierImpl.class);
|
||||
|
||||
private final List<GenericMethod> genericMethods;
|
||||
private final Generator<T> generator;
|
||||
private final IntCache<T> dynamic;
|
||||
private final Function<Object, String[]> dynamicMethods;
|
||||
|
||||
private final LoadingCache<Class<?>, List<NamedMethod<T>>> classCache = CacheBuilder
|
||||
.newBuilder()
|
||||
.build(CacheLoader.from(catching(this::getMethodsImpl, List.of())));
|
||||
|
||||
MethodSupplierImpl(
|
||||
List<GenericMethod> genericMethods,
|
||||
Generator<T> generator,
|
||||
IntCache<T> dynamic,
|
||||
Function<Object, String[]> dynamicMethods
|
||||
) {
|
||||
this.genericMethods = genericMethods;
|
||||
this.generator = generator;
|
||||
this.dynamic = dynamic;
|
||||
this.dynamicMethods = dynamicMethods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forEachSelfMethod(Object object, UntargetedConsumer<T> consumer) {
|
||||
var methods = getMethods(object.getClass());
|
||||
for (var method : methods) consumer.accept(method.name(), method.method(), method);
|
||||
|
||||
var dynamicMethods = this.dynamicMethods.apply(object);
|
||||
if (dynamicMethods != null) {
|
||||
for (var i = 0; i < dynamicMethods.length; i++) consumer.accept(dynamicMethods[i], dynamic.get(i), null);
|
||||
}
|
||||
|
||||
return !methods.isEmpty() || dynamicMethods != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forEachMethod(Object object, TargetedConsumer<T> consumer) {
|
||||
var methods = getMethods(object.getClass());
|
||||
for (var method : methods) consumer.accept(object, method.name(), method.method(), method);
|
||||
|
||||
var hasMethods = !methods.isEmpty();
|
||||
|
||||
if (object instanceof ObjectSource source) {
|
||||
for (var extra : source.getExtra()) {
|
||||
var extraMethods = getMethods(extra.getClass());
|
||||
if (!extraMethods.isEmpty()) hasMethods = true;
|
||||
for (var method : extraMethods) consumer.accept(object, method.name(), method.method(), method);
|
||||
}
|
||||
}
|
||||
|
||||
var dynamicMethods = this.dynamicMethods.apply(object);
|
||||
if (dynamicMethods != null) {
|
||||
hasMethods = true;
|
||||
for (var i = 0; i < dynamicMethods.length; i++) {
|
||||
consumer.accept(object, dynamicMethods[i], dynamic.get(i), null);
|
||||
}
|
||||
}
|
||||
|
||||
return hasMethods;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<NamedMethod<T>> getMethods(Class<?> klass) {
|
||||
try {
|
||||
return classCache.get(klass);
|
||||
} catch (ExecutionException e) {
|
||||
LOG.error("Error getting methods for {}.", klass.getName(), e.getCause());
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private List<NamedMethod<T>> getMethodsImpl(Class<?> klass) {
|
||||
ArrayList<NamedMethod<T>> methods = null;
|
||||
|
||||
// Find all methods on the current class
|
||||
for (var method : klass.getMethods()) {
|
||||
var annotation = method.getAnnotation(LuaFunction.class);
|
||||
if (annotation == null) continue;
|
||||
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
LOG.warn("LuaFunction method {}.{} should be an instance method.", method.getDeclaringClass(), method.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
var instance = generator.getMethod(method).orElse(null);
|
||||
if (instance == null) continue;
|
||||
|
||||
if (methods == null) methods = new ArrayList<>();
|
||||
addMethod(methods, method, annotation, null, instance);
|
||||
}
|
||||
|
||||
// Inject generic methods
|
||||
for (var method : genericMethods) {
|
||||
if (!method.target.isAssignableFrom(klass)) continue;
|
||||
|
||||
var instance = generator.getMethod(method.method).orElse(null);
|
||||
if (instance == null) continue;
|
||||
|
||||
if (methods == null) methods = new ArrayList<>();
|
||||
addMethod(methods, method.method, method.annotation, method.peripheralType, instance);
|
||||
}
|
||||
|
||||
if (methods == null) return List.of();
|
||||
methods.trimToSize();
|
||||
return Collections.unmodifiableList(methods);
|
||||
}
|
||||
|
||||
private void addMethod(List<NamedMethod<T>> methods, Method method, LuaFunction annotation, @Nullable PeripheralType genericType, T instance) {
|
||||
var names = annotation.value();
|
||||
var isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
|
||||
if (names.length == 0) {
|
||||
methods.add(new NamedMethod<>(method.getName(), instance, isSimple, genericType));
|
||||
} else {
|
||||
for (var name : names) {
|
||||
methods.add(new NamedMethod<>(name, instance, isSimple, genericType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class NamedMethod<T> {
|
||||
private final String name;
|
||||
private final T method;
|
||||
private final boolean nonYielding;
|
||||
|
||||
private final @Nullable PeripheralType genericType;
|
||||
|
||||
NamedMethod(String name, T method, boolean nonYielding, @Nullable PeripheralType genericType) {
|
||||
this.name = name;
|
||||
this.method = method;
|
||||
this.nonYielding = nonYielding;
|
||||
this.genericType = genericType;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public T getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public boolean nonYielding() {
|
||||
return nonYielding;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PeripheralType getGenericType() {
|
||||
return genericType;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* A Lua object which exposes additional methods.
|
||||
* <p>
|
||||
* This can be used to merge multiple objects together into one. Ideally this'd be part of the API, but I'm not entirely
|
||||
* happy with the interface - something I'd like to think about first.
|
||||
*/
|
||||
public interface ObjectSource {
|
||||
Iterable<Object> getExtra();
|
||||
|
||||
static <T> void allMethods(Generator<T> generator, Object object, BiConsumer<Object, NamedMethod<T>> accept) {
|
||||
for (var method : generator.getMethods(object.getClass())) accept.accept(object, method);
|
||||
|
||||
if (object instanceof ObjectSource source) {
|
||||
for (var extra : source.getExtra()) {
|
||||
for (var method : generator.getMethods(extra.getClass())) accept.accept(extra, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface PeripheralMethod {
|
||||
Generator<PeripheralMethod> GENERATOR = new Generator<>(PeripheralMethod.class, Arrays.asList(ILuaContext.class, IComputerAccess.class),
|
||||
m -> (target, context, computer, args) -> context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, computer, args.escapes())))
|
||||
);
|
||||
|
||||
IntCache<PeripheralMethod> DYNAMIC = new IntCache<>(
|
||||
method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args)
|
||||
);
|
||||
|
||||
MethodResult apply(Object target, ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.asm;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
import dan200.computercraft.core.ComputerContext;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Provides a {@link MethodSupplier} for {@link PeripheralMethod}s.
|
||||
* <p>
|
||||
* This is used by {@link ComputerContext} to construct {@linkplain ComputerContext#peripheralMethods() the context-wide
|
||||
* method supplier}. It should not be used directly.
|
||||
*/
|
||||
public class PeripheralMethodSupplier {
|
||||
private static final Generator<PeripheralMethod> GENERATOR = new Generator<>(PeripheralMethod.class, List.of(ILuaContext.class, IComputerAccess.class),
|
||||
m -> (target, context, computer, args) -> {
|
||||
var escArgs = args.escapes();
|
||||
return context.executeMainThreadTask(() -> ResultHelpers.checkNormalResult(m.apply(target, context, computer, escArgs)));
|
||||
}
|
||||
);
|
||||
private static final IntCache<PeripheralMethod> DYNAMIC = new IntCache<>(
|
||||
method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args)
|
||||
);
|
||||
|
||||
public static MethodSupplier<PeripheralMethod> create(List<GenericMethod> genericMethods) {
|
||||
return new MethodSupplierImpl<>(genericMethods, GENERATOR, DYNAMIC, x -> x instanceof IDynamicPeripheral dynamic
|
||||
? Objects.requireNonNull(dynamic.getMethodNames(), "Dynamic methods cannot be null")
|
||||
: null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.lua.MachineEnvironment;
|
||||
import dan200.computercraft.core.lua.MachineException;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
@@ -61,6 +63,7 @@ final class ComputerExecutor {
|
||||
private final MetricsObserver metrics;
|
||||
private final List<ILuaAPI> apis = new ArrayList<>();
|
||||
private final ComputerThread scheduler;
|
||||
private final MethodSupplier<LuaMethod> luaMethods;
|
||||
final TimeoutState timeout;
|
||||
|
||||
private @Nullable FileSystem fileSystem;
|
||||
@@ -168,6 +171,7 @@ final class ComputerExecutor {
|
||||
metrics = computerEnvironment.getMetrics();
|
||||
luaFactory = context.luaFactory();
|
||||
scheduler = context.computerScheduler();
|
||||
luaMethods = context.luaMethods();
|
||||
timeout = new TimeoutState(scheduler);
|
||||
|
||||
var environment = computer.getEnvironment();
|
||||
@@ -176,12 +180,12 @@ final class ComputerExecutor {
|
||||
apis.add(new TermAPI(environment));
|
||||
apis.add(new RedstoneAPI(environment));
|
||||
apis.add(new FSAPI(environment));
|
||||
apis.add(new PeripheralAPI(environment));
|
||||
apis.add(new PeripheralAPI(environment, context.peripheralMethods()));
|
||||
apis.add(new OSAPI(environment));
|
||||
if (CoreConfig.httpEnabled) apis.add(new HTTPAPI(environment));
|
||||
|
||||
// Load in the externally registered APIs.
|
||||
for (var factory : ApiFactories.getAll()) {
|
||||
for (var factory : context.apiFactories()) {
|
||||
var system = new ComputerSystem(environment);
|
||||
var api = factory.create(system);
|
||||
if (api != null) apis.add(new ApiWrapper(api, system));
|
||||
@@ -382,6 +386,7 @@ final class ComputerExecutor {
|
||||
return luaFactory.create(new MachineEnvironment(
|
||||
new LuaContext(computer), metrics, timeout,
|
||||
() -> apis.stream().map(api -> api instanceof ApiWrapper wrapper ? wrapper.getDelegate() : api).iterator(),
|
||||
luaMethods,
|
||||
computer.getGlobalEnvironment().getHostString()
|
||||
), bios);
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.squiddev.cobalt.LuaError;
|
||||
|
||||
@@ -10,9 +10,9 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaFunction;
|
||||
import dan200.computercraft.core.CoreConfig;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.asm.ObjectSource;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.core.util.SanitisedError;
|
||||
import org.slf4j.Logger;
|
||||
@@ -39,6 +39,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
private final TimeoutState timeout;
|
||||
private final Runnable timeoutListener = this::updateTimeout;
|
||||
private final ILuaContext context;
|
||||
private final MethodSupplier<LuaMethod> luaMethods;
|
||||
|
||||
private final LuaState state;
|
||||
private final LuaThread mainRoutine;
|
||||
@@ -51,6 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws MachineException, IOException {
|
||||
timeout = environment.timeout();
|
||||
context = environment.context();
|
||||
luaMethods = environment.luaMethods();
|
||||
|
||||
// Create an environment to run in
|
||||
var state = this.state = LuaState.builder()
|
||||
@@ -161,28 +163,13 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
|
||||
@Nullable
|
||||
private LuaTable wrapLuaObject(Object object) {
|
||||
var dynamicMethods = object instanceof IDynamicLuaObject dynamic
|
||||
? Objects.requireNonNull(dynamic.getMethodNames(), "Methods cannot be null")
|
||||
: LuaMethod.EMPTY_METHODS;
|
||||
|
||||
var table = new LuaTable();
|
||||
for (var i = 0; i < dynamicMethods.length; i++) {
|
||||
var method = dynamicMethods[i];
|
||||
table.rawset(method, new ResultInterpreterFunction(this, LuaMethod.DYNAMIC.get(i), object, context, method));
|
||||
}
|
||||
var found = luaMethods.forEachMethod(object, (target, name, method, info) ->
|
||||
table.rawset(name, info != null && info.nonYielding()
|
||||
? new BasicFunction(this, method, target, context, name)
|
||||
: new ResultInterpreterFunction(this, method, target, context, name)));
|
||||
|
||||
ObjectSource.allMethods(LuaMethod.GENERATOR, object, (instance, method) ->
|
||||
table.rawset(method.getName(), method.nonYielding()
|
||||
? new BasicFunction(this, method.getMethod(), instance, context, method.getName())
|
||||
: new ResultInterpreterFunction(this, method.getMethod(), instance, context, method.getName())));
|
||||
|
||||
try {
|
||||
if (table.next(Constants.NIL).first().isNil()) return null;
|
||||
} catch (LuaError ignored) {
|
||||
// next should never throw on nil.
|
||||
}
|
||||
|
||||
return table;
|
||||
return found ? table : null;
|
||||
}
|
||||
|
||||
private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,7 @@ import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
* @param timeout The current timeout state. This should be used by the machine to interrupt its execution.
|
||||
* @param apis APIs to inject into the global environment. Each API should be converted into a Lua object
|
||||
* (following the same rules as any other value), and then set to all names in {@link ILuaAPI#getNames()}.
|
||||
* @param luaMethods A {@link MethodSupplier} to find methods on returned values.
|
||||
* @param hostString A {@linkplain GlobalEnvironment#getHostString() host string} to identify the current environment.
|
||||
* @see ILuaMachine.Factory
|
||||
*/
|
||||
@@ -26,6 +29,7 @@ public record MachineEnvironment(
|
||||
MetricsObserver metrics,
|
||||
TimeoutState timeout,
|
||||
Iterable<ILuaAPI> apis,
|
||||
MethodSupplier<LuaMethod> luaMethods,
|
||||
String hostString
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.squiddev.cobalt.*;
|
||||
|
||||
@@ -191,6 +191,7 @@ final class VarargArguments implements IArguments {
|
||||
@Override
|
||||
public IArguments escapes() {
|
||||
if (escapes) return this;
|
||||
if (isClosed()) throw new IllegalStateException("Cannot call escapes after IArguments has been closed.");
|
||||
|
||||
var cache = this.cache;
|
||||
var typeNames = this.typeNames;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.methods;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
|
||||
/**
|
||||
* A basic Lua function (i.e. one not associated with a peripheral) on some object (such as a {@link IDynamicLuaObject}
|
||||
* or {@link ILuaAPI}.
|
||||
* <p>
|
||||
* This interface is not typically implemented yourself, but instead generated from a {@link LuaFunction}-annotated
|
||||
* method.
|
||||
*
|
||||
* @see NamedMethod
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LuaMethod {
|
||||
/**
|
||||
* Apply this method.
|
||||
*
|
||||
* @param target The object instance that this method targets.
|
||||
* @param context The Lua context for this function call.
|
||||
* @param args Arguments to this function.
|
||||
* @return The return call of this function.
|
||||
* @throws LuaException Thrown by the underlying method call.
|
||||
* @see IDynamicLuaObject#callMethod(ILuaContext, int, IArguments)
|
||||
*/
|
||||
MethodResult apply(Object target, ILuaContext context, IArguments args) throws LuaException;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.methods;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Finds methods available on an object and yields them.
|
||||
*
|
||||
* @param <T> The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}.
|
||||
*/
|
||||
public interface MethodSupplier<T> {
|
||||
/**
|
||||
* Iterate over methods available on an object, ignoring {@link ObjectSource}s.
|
||||
*
|
||||
* @param object The object to find methods for.
|
||||
* @param consumer The consumer to call for each method.
|
||||
* @return Whether any methods were found.
|
||||
*/
|
||||
boolean forEachSelfMethod(Object object, UntargetedConsumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Generate a map of all methods targeting the current object, ignoring {@link ObjectSource}s.
|
||||
*
|
||||
* @param object The object to find methods for.
|
||||
* @return A map of all methods on the object.
|
||||
*/
|
||||
default Map<String, T> getSelfMethods(Object object) {
|
||||
var map = new HashMap<String, T>();
|
||||
forEachSelfMethod(object, (n, m, i) -> map.put(n, m));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all methods on an object, including {@link ObjectSource}s.
|
||||
*
|
||||
* @param object The object to find methods for.
|
||||
* @param consumer The consumer to call for each method.
|
||||
* @return Whether any methods were found.
|
||||
*/
|
||||
boolean forEachMethod(Object object, TargetedConsumer<T> consumer);
|
||||
|
||||
/**
|
||||
* A function which is called for each method on an object.
|
||||
*
|
||||
* @param <T> The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}.
|
||||
* @see #forEachSelfMethod(Object, UntargetedConsumer)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface UntargetedConsumer<T> {
|
||||
/**
|
||||
* Consume a method on an object.
|
||||
*
|
||||
* @param name The name of this method.
|
||||
* @param method The actual method definition.
|
||||
* @param info Additional information about the method, such as whether it will yield. May be {@code null}.
|
||||
*/
|
||||
void accept(String name, T method, @Nullable NamedMethod<T> info);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function which is called for each method on an object and possibly nested objects.
|
||||
*
|
||||
* @param <T> The type of method, such as {@link LuaMethod} or {@link PeripheralMethod}.
|
||||
* @see #forEachMethod(Object, TargetedConsumer)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface TargetedConsumer<T> {
|
||||
/**
|
||||
* Consume a method on an object.
|
||||
*
|
||||
* @param object The object this method targets, should be passed to the method's {@code apply(...)} function.
|
||||
* @param name The name of this method.
|
||||
* @param method The actual method definition.
|
||||
* @param info Additional information about the method, such as whether it will yield. May be {@code null}.
|
||||
*/
|
||||
void accept(Object object, String name, T method, @Nullable NamedMethod<T> info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.methods;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A method generated from a {@link LuaFunction}.
|
||||
*
|
||||
* @param name The name of this method.
|
||||
* @param method The underlying method implementation.
|
||||
* @param nonYielding If this method is guaranteed to never yield, and will always return a
|
||||
* {@linkplain MethodResult#of(Object...) basic result}.
|
||||
* @param genericType The peripheral type of this method. This is only set if this is a method on a
|
||||
* {@link GenericPeripheral}.
|
||||
* @param <T> The type of method, either a {@link LuaMethod} or {@link PeripheralMethod}.
|
||||
*/
|
||||
public record NamedMethod<T>(String name, T method, boolean nonYielding, @Nullable PeripheralType genericType) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.methods;
|
||||
|
||||
/**
|
||||
* A Lua object which exposes additional methods.
|
||||
* <p>
|
||||
* This can be used to merge multiple objects together into one. Ideally this'd be part of the API, but I'm not entirely
|
||||
* happy with the interface - something I'd like to think about first.
|
||||
*/
|
||||
public interface ObjectSource {
|
||||
Iterable<Object> getExtra();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.methods;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
/**
|
||||
* A Lua function associated with some peripheral.
|
||||
* <p>
|
||||
* This interface is not typically implemented yourself, but instead generated from a {@link LuaFunction}-annotated
|
||||
* method.
|
||||
*
|
||||
* @see NamedMethod
|
||||
* @see IPeripheral
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PeripheralMethod {
|
||||
/**
|
||||
* Apply this method.
|
||||
*
|
||||
* @param target The object instance that this method targets.
|
||||
* @param context The Lua context for this function call.
|
||||
* @param computer The interface to the computer that is making the call.
|
||||
* @param args Arguments to this function.
|
||||
* @return The return call of this function.
|
||||
* @throws LuaException Thrown by the underlying method call.
|
||||
* @see IDynamicPeripheral#callMethod(IComputerAccess, ILuaContext, int, IArguments)
|
||||
*/
|
||||
MethodResult apply(Object target, ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException;
|
||||
}
|
||||
@@ -2,13 +2,32 @@
|
||||
--
|
||||
-- SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
--- Read and write configuration options for CraftOS and your programs.
|
||||
--
|
||||
-- By default, the settings API will load its configuration from the
|
||||
-- `/.settings` file. One can then use @{settings.save} to update the file.
|
||||
--
|
||||
-- @module settings
|
||||
-- @since 1.78
|
||||
--[[- Read and write configuration options for CraftOS and your programs.
|
||||
|
||||
When a computer starts, it reads the current value of settings from the
|
||||
`/.settings` file. These values then may be @{settings.get|read} or
|
||||
@{settings.set|modified}.
|
||||
|
||||
:::caution
|
||||
Calling @{settings.set} does _not_ update the settings file by default. You
|
||||
_must_ call @{settings.save} to persist values.
|
||||
:::
|
||||
|
||||
@module settings
|
||||
@since 1.78
|
||||
@usage Define an basic setting `123` and read its value.
|
||||
|
||||
settings.define("my.setting", {
|
||||
description = "An example setting",
|
||||
default = 123,
|
||||
type = number,
|
||||
})
|
||||
print("my.setting = " .. settings.get("my.setting")) -- 123
|
||||
|
||||
You can then use the `set` program to change its value (e.g. `set my.setting 456`),
|
||||
and then re-run the `example` program to check it has changed.
|
||||
|
||||
]]
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua")
|
||||
local type, expect, field = type, expect.expect, expect.field
|
||||
@@ -92,13 +111,19 @@ local function set_value(name, new)
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the value of a setting.
|
||||
--
|
||||
-- @tparam string name The name of the setting to set
|
||||
-- @param value The setting's value. This cannot be `nil`, and must be
|
||||
-- serialisable by @{textutils.serialize}.
|
||||
-- @throws If this value cannot be serialised
|
||||
-- @see settings.unset
|
||||
--[[- Set the value of a setting.
|
||||
|
||||
:::caution
|
||||
Calling @{settings.set} does _not_ update the settings file by default. You
|
||||
_must_ call @{settings.save} to persist values.
|
||||
:::
|
||||
|
||||
@tparam string name The name of the setting to set
|
||||
@param value The setting's value. This cannot be `nil`, and must be
|
||||
serialisable by @{textutils.serialize}.
|
||||
@throws If this value cannot be serialised
|
||||
@see settings.unset
|
||||
]]
|
||||
function set(name, value)
|
||||
expect(1, name, "string")
|
||||
expect(2, value, "number", "string", "boolean", "table")
|
||||
|
||||
@@ -131,11 +131,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, nHeight do
|
||||
tLines[y] = {
|
||||
text = sEmptyText,
|
||||
textColor = sEmptyTextColor,
|
||||
backgroundColor = sEmptyBackgroundColor,
|
||||
}
|
||||
tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||
end
|
||||
|
||||
for i = 0, 15 do
|
||||
@@ -165,7 +161,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
local function redrawLine(n)
|
||||
local tLine = tLines[n]
|
||||
parent.setCursorPos(nX, nY + n - 1)
|
||||
parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor)
|
||||
parent.blit(tLine[1], tLine[2], tLine[3])
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
@@ -188,9 +184,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
-- Modify line
|
||||
local tLine = tLines[nCursorY]
|
||||
if nStart == 1 and nEnd == nWidth then
|
||||
tLine.text = sText
|
||||
tLine.textColor = sTextColor
|
||||
tLine.backgroundColor = sBackgroundColor
|
||||
tLine[1] = sText
|
||||
tLine[2] = sTextColor
|
||||
tLine[3] = sBackgroundColor
|
||||
else
|
||||
local sClippedText, sClippedTextColor, sClippedBackgroundColor
|
||||
if nStart < 1 then
|
||||
@@ -210,9 +206,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
sClippedBackgroundColor = sBackgroundColor
|
||||
end
|
||||
|
||||
local sOldText = tLine.text
|
||||
local sOldTextColor = tLine.textColor
|
||||
local sOldBackgroundColor = tLine.backgroundColor
|
||||
local sOldText = tLine[1]
|
||||
local sOldTextColor = tLine[2]
|
||||
local sOldBackgroundColor = tLine[3]
|
||||
local sNewText, sNewTextColor, sNewBackgroundColor
|
||||
if nStart > 1 then
|
||||
local nOldEnd = nStart - 1
|
||||
@@ -231,9 +227,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
|
||||
end
|
||||
|
||||
tLine.text = sNewText
|
||||
tLine.textColor = sNewTextColor
|
||||
tLine.backgroundColor = sNewBackgroundColor
|
||||
tLine[1] = sNewText
|
||||
tLine[2] = sNewTextColor
|
||||
tLine[3] = sNewBackgroundColor
|
||||
end
|
||||
|
||||
-- Redraw line
|
||||
@@ -280,11 +276,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, nHeight do
|
||||
tLines[y] = {
|
||||
text = sEmptyText,
|
||||
textColor = sEmptyTextColor,
|
||||
backgroundColor = sEmptyBackgroundColor,
|
||||
}
|
||||
local line = tLines[y]
|
||||
line[1] = sEmptyText
|
||||
line[2] = sEmptyTextColor
|
||||
line[3] = sEmptyBackgroundColor
|
||||
end
|
||||
if bVisible then
|
||||
redraw()
|
||||
@@ -295,14 +290,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
|
||||
function window.clearLine()
|
||||
if nCursorY >= 1 and nCursorY <= nHeight then
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
tLines[nCursorY] = {
|
||||
text = sEmptyText,
|
||||
textColor = sEmptyTextColor,
|
||||
backgroundColor = sEmptyBackgroundColor,
|
||||
}
|
||||
local line = tLines[nCursorY]
|
||||
line[1] = sEmptySpaceLine
|
||||
line[2] = tEmptyColorLines[nTextColor]
|
||||
line[3] = tEmptyColorLines[nBackgroundColor]
|
||||
if bVisible then
|
||||
redrawLine(nCursorY)
|
||||
updateCursorColor()
|
||||
@@ -431,11 +422,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
if y >= 1 and y <= nHeight then
|
||||
tNewLines[newY] = tLines[y]
|
||||
else
|
||||
tNewLines[newY] = {
|
||||
text = sEmptyText,
|
||||
textColor = sEmptyTextColor,
|
||||
backgroundColor = sEmptyBackgroundColor,
|
||||
}
|
||||
tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||
end
|
||||
end
|
||||
tLines = tNewLines
|
||||
@@ -478,7 +465,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
error("Line is out of range.", 2)
|
||||
end
|
||||
|
||||
return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
|
||||
local line = tLines[y]
|
||||
return line[1], line[2], line[3]
|
||||
end
|
||||
|
||||
-- Other functions
|
||||
@@ -574,26 +562,22 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, new_height do
|
||||
if y > nHeight then
|
||||
tNewLines[y] = {
|
||||
text = sEmptyText,
|
||||
textColor = sEmptyTextColor,
|
||||
backgroundColor = sEmptyBackgroundColor,
|
||||
}
|
||||
tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||
else
|
||||
local tOldLine = tLines[y]
|
||||
if new_width == nWidth then
|
||||
tNewLines[y] = tOldLine
|
||||
elseif new_width < nWidth then
|
||||
tNewLines[y] = {
|
||||
text = string_sub(tOldLine.text, 1, new_width),
|
||||
textColor = string_sub(tOldLine.textColor, 1, new_width),
|
||||
backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width),
|
||||
string_sub(tOldLine[1], 1, new_width),
|
||||
string_sub(tOldLine[2], 1, new_width),
|
||||
string_sub(tOldLine[3], 1, new_width),
|
||||
}
|
||||
else
|
||||
tNewLines[y] = {
|
||||
text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width),
|
||||
textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
|
||||
backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
|
||||
tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
|
||||
tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
|
||||
tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
# New features in CC: Tweaked 1.106.0
|
||||
|
||||
* Numerous documentation improvements (MCJack123, znepb, penguinencounter).
|
||||
* Port `fs.find` to Lua. This also allows using `?` as a wildcard.
|
||||
* Computers cursors now glow in the dark.
|
||||
* Allow changing turtle upgrades from the GUI.
|
||||
* Add option to serialize Unicode strings to JSON (MCJack123).
|
||||
* Small optimisations to the `window` API.
|
||||
* Turtle upgrades can now preserve NBT from upgrade item stack and when broken.
|
||||
* Add support for tool enchantments and durability via datapacks. This is disabled for the built-in tools.
|
||||
|
||||
Several bug fixes:
|
||||
* Fix turtles rendering incorrectly when upside down.
|
||||
* Fix misplaced calls to IArguments.escapes.
|
||||
* Lua REPL no longer accepts `)(` as a valid expression.
|
||||
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
|
||||
* Fix turtle being able to place water buckets outside its reach distance.
|
||||
* Fix private several IP address ranges not being blocked by the `$private` rule.
|
||||
* Improve permission checks in the `/computercraft` command.
|
||||
|
||||
# New features in CC: Tweaked 1.105.0
|
||||
|
||||
* Optimise JSON string parsing.
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
New features in CC: Tweaked 1.105.0
|
||||
New features in CC: Tweaked 1.106.0
|
||||
|
||||
* Optimise JSON string parsing.
|
||||
* Add `colors.fromBlit` (Erb3).
|
||||
* Upload file size limit is now configurable (khankul).
|
||||
* Wired cables no longer have a distance limit.
|
||||
* Java methods now coerce values to strings consistently with Lua.
|
||||
* Add custom timeout support to the HTTP API.
|
||||
* Support custom proxies for HTTP requests (Lemmmy).
|
||||
* The `speaker` program now errors when playing HTML files.
|
||||
* `edit` now shows an error message when editing read-only files.
|
||||
* Update Ukranian translation (SirEdvin).
|
||||
* Numerous documentation improvements (MCJack123, znepb, penguinencounter).
|
||||
* Port `fs.find` to Lua. This also allows using `?` as a wildcard.
|
||||
* Computers cursors now glow in the dark.
|
||||
* Allow changing turtle upgrades from the GUI.
|
||||
* Add option to serialize Unicode strings to JSON (MCJack123).
|
||||
* Small optimisations to the `window` API.
|
||||
* Turtle upgrades can now preserve NBT from upgrade item stack and when broken.
|
||||
* Add support for tool enchantments and durability via datapacks. This is disabled for the built-in tools.
|
||||
|
||||
Several bug fixes:
|
||||
* Allow GPS hosts to only be 1 block apart.
|
||||
* Fix "Turn On"/"Turn Off" buttons being inverted in the computer GUI (Erb3).
|
||||
* Fix arrow keys not working in the printout UI.
|
||||
* Several documentation fixes (zyxkad, Lupus590, Commandcracker).
|
||||
* Fix monitor renderer debug text always being visible on Forge.
|
||||
* Fix crash when another mod changes the LoggerContext.
|
||||
* Fix the `monitor_renderer` option not being present in Fabric config files.
|
||||
* Pasting on MacOS/OSX now uses Cmd+V rather than Ctrl+V.
|
||||
* Fix turtles placing blocks upside down when at y<0.
|
||||
* Fix turtles rendering incorrectly when upside down.
|
||||
* Fix misplaced calls to IArguments.escapes.
|
||||
* Lua REPL no longer accepts `)(` as a valid expression.
|
||||
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
|
||||
* Fix turtle being able to place water buckets outside its reach distance.
|
||||
* Fix private several IP address ranges not being blocked by the `$private` rule.
|
||||
* Improve permission checks in the `/computercraft` command.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
||||
@@ -21,6 +21,8 @@ local error_printer = require "cc.internal.error_printer"
|
||||
local error_sentinel = {}
|
||||
|
||||
local function make_context(input)
|
||||
expect(1, input, "string")
|
||||
|
||||
local context = {}
|
||||
|
||||
local lines = { 1 }
|
||||
@@ -73,8 +75,9 @@ local function parse(input, start_symbol)
|
||||
expect(2, start_symbol, "number")
|
||||
|
||||
local context = make_context(input)
|
||||
function context.report(msg)
|
||||
expect(1, msg, "table")
|
||||
function context.report(msg, ...)
|
||||
expect(1, msg, "table", "function")
|
||||
if type(msg) == "function" then msg = msg(...) end
|
||||
error_printer(context, msg)
|
||||
error(error_sentinel)
|
||||
end
|
||||
@@ -110,8 +113,9 @@ local function parse_repl(input)
|
||||
local context = make_context(input)
|
||||
|
||||
local last_error = nil
|
||||
function context.report(msg)
|
||||
expect(1, msg, "table")
|
||||
function context.report(msg, ...)
|
||||
expect(1, msg, "table", "function")
|
||||
if type(msg) == "function" then msg = msg(...) end
|
||||
last_error = msg
|
||||
error(error_sentinel)
|
||||
end
|
||||
|
||||
@@ -96,7 +96,7 @@ local function lex_number(context, str, start)
|
||||
local contents = sub(str, start, pos - 1)
|
||||
if not tonumber(contents) then
|
||||
-- TODO: Separate error for "2..3"?
|
||||
context.report(errors.malformed_number(start, pos - 1))
|
||||
context.report(errors.malformed_number, start, pos - 1)
|
||||
end
|
||||
|
||||
return tokens.NUMBER, pos - 1
|
||||
@@ -118,14 +118,14 @@ local function lex_string(context, str, start_pos, quote)
|
||||
return tokens.STRING, pos
|
||||
elseif c == "\n" or c == "\r" or c == "" then
|
||||
-- We don't call newline here, as that's done for the next token.
|
||||
context.report(errors.unfinished_string(start_pos, pos, quote))
|
||||
context.report(errors.unfinished_string, start_pos, pos, quote)
|
||||
return tokens.STRING, pos - 1
|
||||
elseif c == "\\" then
|
||||
c = sub(str, pos + 1, pos + 1)
|
||||
if c == "\n" or c == "\r" then
|
||||
pos = newline(context, str, pos + 1, c)
|
||||
elseif c == "" then
|
||||
context.report(errors.unfinished_string_escape(start_pos, pos, quote))
|
||||
context.report(errors.unfinished_string_escape, start_pos, pos, quote)
|
||||
return tokens.STRING, pos
|
||||
elseif c == "z" then
|
||||
pos = pos + 2
|
||||
@@ -133,7 +133,7 @@ local function lex_string(context, str, start_pos, quote)
|
||||
local next_pos, _, c = find(str, "([%S\r\n])", pos)
|
||||
|
||||
if not next_pos then
|
||||
context.report(errors.unfinished_string(start_pos, #str, quote))
|
||||
context.report(errors.unfinished_string, start_pos, #str, quote)
|
||||
return tokens.STRING, #str
|
||||
end
|
||||
|
||||
@@ -196,7 +196,7 @@ local function lex_long_str(context, str, start, len)
|
||||
elseif c == "[" then
|
||||
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
|
||||
if ok and boundary_pos - pos == len and len == 1 then
|
||||
context.report(errors.nested_long_str(pos, boundary_pos))
|
||||
context.report(errors.nested_long_str, pos, boundary_pos)
|
||||
end
|
||||
|
||||
pos = boundary_pos
|
||||
@@ -238,12 +238,12 @@ local function lex_token(context, str, pos)
|
||||
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos)
|
||||
if end_pos then return tokens.STRING, end_pos end
|
||||
|
||||
context.report(errors.unfinished_long_string(pos, boundary_pos, boundary_pos - pos))
|
||||
context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos)
|
||||
return tokens.ERROR, #str
|
||||
elseif pos + 1 == boundary_pos then -- Just a "["
|
||||
return tokens.OSQUARE, pos
|
||||
else -- Malformed long string, for instance "[="
|
||||
context.report(errors.malformed_long_string(pos, boundary_pos, boundary_pos - pos))
|
||||
context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos)
|
||||
return tokens.ERROR, boundary_pos
|
||||
end
|
||||
|
||||
@@ -260,7 +260,7 @@ local function lex_token(context, str, pos)
|
||||
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos)
|
||||
if end_pos then return tokens.COMMENT, end_pos end
|
||||
|
||||
context.report(errors.unfinished_long_comment(pos, boundary_pos, boundary_pos - comment_pos))
|
||||
context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos)
|
||||
return tokens.ERROR, #str
|
||||
end
|
||||
end
|
||||
@@ -317,18 +317,18 @@ local function lex_token(context, str, pos)
|
||||
if end_pos - pos <= 3 then
|
||||
local contents = sub(str, pos, end_pos)
|
||||
if contents == "&&" then
|
||||
context.report(errors.wrong_and(pos, end_pos))
|
||||
context.report(errors.wrong_and, pos, end_pos)
|
||||
return tokens.AND, end_pos
|
||||
elseif contents == "||" then
|
||||
context.report(errors.wrong_or(pos, end_pos))
|
||||
context.report(errors.wrong_or, pos, end_pos)
|
||||
return tokens.OR, end_pos
|
||||
elseif contents == "!=" or contents == "<>" then
|
||||
context.report(errors.wrong_ne(pos, end_pos))
|
||||
context.report(errors.wrong_ne, pos, end_pos)
|
||||
return tokens.NE, end_pos
|
||||
end
|
||||
end
|
||||
|
||||
context.report(errors.unexpected_character(pos))
|
||||
context.report(errors.unexpected_character, pos)
|
||||
return tokens.ERROR, end_pos
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user