Merge branch 'cc-tweaked:mc-1.19.x' into unicode-proto

This commit is contained in:
cvrunmin 2023-07-21 23:12:13 +08:00 committed by GitHub
commit 0762afe554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1632 additions and 578 deletions

View File

@ -6,6 +6,7 @@ Upstream-Contact: Jonathan Coates <git@squiddev.cc>
Files:
projects/common/src/main/resources/assets/computercraft/sounds.json
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/*
projects/common/src/testMod/resources/data/cctest/structures/*
projects/fabric/src/generated/*
projects/forge/src/generated/*
@ -47,6 +48,7 @@ License: MPL-2.0
Files:
doc/logo.png
doc/logo-darkmode.png
projects/common/src/main/resources/assets/computercraft/models/*
projects/common/src/main/resources/assets/computercraft/textures/*
projects/common/src/main/resources/pack.mcmeta

View File

@ -4,7 +4,12 @@
SPDX-License-Identifier: MPL-2.0
-->
# ![CC: Tweaked](doc/logo.png)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./doc/logo-darkmode.png">
<source media="(prefers-color-scheme: light)" srcset="./doc/logo.png">
<img alt="CC: Tweaked" src="./doc/logo.png">
</picture>
[![Current build status](https://github.com/cc-tweaked/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
[![Download CC: Tweaked on CurseForge](https://img.shields.io/static/v1?label=Download&message=CC:%20Tweaked&color=E04E14&logoColor=E04E14&logo=CurseForge)][CurseForge]
[![Download CC: Tweaked on Modrinth](https://img.shields.io/static/v1?label=Download&color=00AF5C&logoColor=00AF5C&logo=Modrinth&message=CC:%20Tweaked)][Modrinth]
@ -44,7 +49,7 @@ ## Using
dependencies {
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
@ -57,6 +62,19 @@ ## Using
}
```
When using ForgeGradle, you may also need to add the following:
```groovy
minecraft {
runs {
configureEach {
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
}
}
}
```
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
exposing more features.

View File

@ -56,13 +56,14 @@ repositories {
if (fg != null) forRepositories(fg.repository)
filter {
includeGroup("org.squiddev")
includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
// Things we mirror
includeGroup("dev.architectury")
includeGroup("dev.emi")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel")
includeGroup("me.shedaniel.cloth")
includeGroup("me.shedaniel")
includeGroup("mezz.jei")
includeModule("com.terraformersmc", "modmenu")
}

View File

@ -13,6 +13,15 @@
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the WebSocket that was closed.
3. <span class="type">@{string}|@{nil}</span>: The [server-provided reason][close_reason]
the websocket was closed. This will be @{nil} if the connection was closed
abnormally.
4. <span class="type">@{number}|@{nil}</span>: The [connection close code][close_code],
indicating why the socket was closed. This will be @{nil} if the connection
was closed abnormally.
[close_reason]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 "The WebSocket Connection Close Reason, RFC 6455"
[close_code]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 "The WebSocket Connection Close Code, RFC 6455"
## Example
Prints a message when a WebSocket is closed (this may take a minute):

BIN
doc/logo-darkmode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.105.0
modVersion=1.106.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.19.4

View File

@ -7,20 +7,20 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/mods.toml
fabric-api = "0.80.0+1.19.4"
fabric-loader = "0.14.19"
fabric-api = "0.86.0+1.19.4"
fabric-loader = "0.14.21"
forge = "45.0.42"
forgeSpi = "6.0.0"
mixin = "0.8.5"
parchment = "2023.03.12"
parchmentMc = "1.19.3"
parchment = "2023.06.26"
parchmentMc = "1.19.4"
# Normal dependencies
asm = "9.3"
autoService = "1.0.1"
checkerFramework = "3.32.0"
cobalt = "0.7.0"
cobalt-next = "0.7.1" # Not a real version, used to constrain the version we accept.
cobalt = "0.7.1"
cobalt-next = "0.7.2" # Not a real version, used to constrain the version we accept.
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "24.0.1"
@ -33,6 +33,7 @@ nightConfig = "3.6.5"
slf4j = "1.7.36"
# Minecraft mods
emi = "1.0.8+1.19.4"
iris = "1.5.2+1.19.4"
jei = "13.1.0.11"
modmenu = "6.1.0-rc.1"
@ -62,7 +63,7 @@ librarian = "1.+"
minotaur = "2.+"
mixinGradle = "0.7.+"
nullAway = "0.9.9"
quiltflower = "1.8.0"
quiltflower = "1.10.0"
spotless = "6.17.0"
taskTree = "2.1.1"
vanillaGradle = "0.2.1-SNAPSHOT"
@ -92,6 +93,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Minecraft mods
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }

View File

@ -11,7 +11,7 @@
/**
* Indicates if an equipped turtle item will consume durability.
*
* @see TurtleUpgradeDataProvider.ToolBuilder#consumesDurability(TurtleToolDurability)
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
*/
public enum TurtleToolDurability implements StringRepresentable {
/**
@ -35,6 +35,7 @@ public enum TurtleToolDurability implements StringRepresentable {
/**
* The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
*/
@SuppressWarnings("deprecation")
public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
TurtleToolDurability(String serialisedName) {

View File

@ -64,7 +64,7 @@ public static class ToolBuilder {
private @Nullable Float damageMultiplier = null;
private @Nullable TagKey<Block> breakable;
private boolean allowEnchantments = false;
private TurtleToolDurability consumesDurability = TurtleToolDurability.NEVER;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
this.id = id;
@ -125,8 +125,8 @@ public ToolBuilder allowEnchantments() {
* @param durability The durability predicate.
* @return The tool builder, for further use.
*/
public ToolBuilder consumesDurability(TurtleToolDurability durability) {
consumesDurability = durability;
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
consumeDurability = durability;
return this;
}
@ -159,8 +159,8 @@ public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
if (allowEnchantments) s.addProperty("allowEnchantments", true);
if (consumesDurability != TurtleToolDurability.NEVER) {
s.addProperty("consumesDurability", consumesDurability.getSerializedName());
if (consumeDurability != TurtleToolDurability.NEVER) {
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
}
}));
}

View File

@ -23,11 +23,7 @@
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
@ -167,5 +163,21 @@ public record Upgrade<R extends UpgradeSerialiser<?>>(
public void add(Consumer<Upgrade<R>> add) {
add.accept(this);
}
/**
* Return a new {@link Upgrade} which requires the given mod to be present.
* <p>
* This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
* this in a multi-loader setup, you must generate resources separately for the two loaders.
*
* @param modId The id of the mod.
* @return A new upgrade instance.
*/
public Upgrade<R> requireMod(String modId) {
return new Upgrade<>(id, serialiser, json -> {
PlatformHelper.get().addRequiredModCondition(json, modId);
serialise.accept(json);
});
}
}
}

View File

@ -4,6 +4,8 @@
package dan200.computercraft.impl;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
@ -63,6 +65,15 @@ default CompoundTag getShareTag(ItemStack item) {
return item.getTag();
}
/**
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
* {@link UpgradeDataProvider}.
*
* @param object The JSON object we're generating.
* @param modId The mod ID that we require.
*/
void addRequiredModCondition(JsonObject object, String modId);
final class Instance {
static final @Nullable PlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;

View File

@ -26,6 +26,7 @@ dependencies {
clientImplementation(clientClasses(project(":common-api")))
compileOnly(libs.bundles.externalMods.common)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
compileOnly(libs.mixin)
annotationProcessorEverywhere(libs.autoService)

View File

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.integration.emi;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dev.emi.emi.api.EmiEntrypoint;
import dev.emi.emi.api.EmiPlugin;
import dev.emi.emi.api.EmiRegistry;
import dev.emi.emi.api.stack.Comparison;
import net.minecraft.world.item.ItemStack;
import java.util.function.BiPredicate;
@EmiEntrypoint
public class EMIComputerCraft implements EmiPlugin {
@Override
public void register(EmiRegistry registry) {
registry.setDefaultComparison(ModRegistry.Items.TURTLE_NORMAL.get(), turtleComparison);
registry.setDefaultComparison(ModRegistry.Items.TURTLE_ADVANCED.get(), turtleComparison);
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
}
private static final Comparison turtleComparison = compareStacks((left, right) ->
left.getItem() instanceof TurtleItem turtle
&& turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
&& turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
private static final Comparison pocketComparison = compareStacks((left, right) ->
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
return Comparison.of((left, right) -> {
ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
});
}
}

View File

@ -26,13 +26,15 @@
* <p>
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
*/
public final class ModelTransformer {
public static final int[] ORDER = new int[]{ 3, 2, 1, 0 };
public class ModelTransformer {
@SuppressWarnings("MutablePublicArray") // It's not nice, but is efficient.
public static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
public static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
private final Matrix4f transformation;
private final boolean invert;
protected final Matrix4f transformation;
protected final boolean invert;
private @Nullable TransformedQuads cache;
public ModelTransformer(Transformation transformation) {
@ -60,7 +62,7 @@ private BakedQuad transformQuad(BakedQuad quad) {
for (var i = 0; i < 4; i++) {
var inStart = STRIDE * i;
// Reverse the order of the quads if we're inverting
var outStart = STRIDE * (invert ? ORDER[i] : i);
var outStart = STRIDE * (invert ? INVERSE_ORDER[i] : i);
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
// Apply the matrix to our position

View File

@ -212,7 +212,7 @@ private static void putBulkQuadInvert(VertexConsumer buffer, PoseStack.Pose pose
var normal = matrix.transform(new Vector4f(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f)).normalize();
var vertices = quad.getVertices();
for (var vertex : ModelTransformer.ORDER) {
for (var vertex : ModelTransformer.INVERSE_ORDER) {
var i = vertex * ModelTransformer.STRIDE;
var x = Float.intBitsToFloat(vertices[i]);

View File

@ -6,6 +6,7 @@
import com.google.gson.JsonObject;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeBase;
@ -20,6 +21,7 @@
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
@ -97,6 +99,12 @@ private void addTranslations() {
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), "Advanced Pocket Computer");
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().getDescriptionId() + ".upgraded", "Advanced %s Pocket Computer");
// Tags (for EMI)
add(ComputerCraftTags.Items.COMPUTER, "Computers");
add(ComputerCraftTags.Items.TURTLE, "Turtles");
add(ComputerCraftTags.Items.WIRED_MODEM, "Wired modems");
add(ComputerCraftTags.Items.MONITOR, "Monitors");
// Turtle/pocket upgrades
add("upgrade.minecraft.diamond_sword.adjective", "Melee");
add("upgrade.minecraft.diamond_shovel.adjective", "Digging");
@ -214,6 +222,7 @@ private void addTranslations() {
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
addConfigEntry(ConfigSpec.disabledGenericMethods, "Disabled generic methods");
addConfigGroup(ConfigSpec.serverSpec, "execution", "Execution");
addConfigEntry(ConfigSpec.computerThreads, "Computer threads");
@ -277,8 +286,8 @@ private Stream<String> getExpectedKeys() {
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
getConfigEntries(ConfigSpec.serverSpec).map(ConfigFile.Entry::translationKey),
getConfigEntries(ConfigSpec.clientSpec).map(ConfigFile.Entry::translationKey)
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
).flatMap(x -> x);
}
@ -298,6 +307,10 @@ private void add(Metric metric, String text) {
add(AggregatedMetric.TRANSLATION_PREFIX + metric.name() + ".name", text);
}
private void add(TagKey<Item> tag, String text) {
add("tag.item." + tag.location().getNamespace() + "." + tag.location().getPath(), text);
}
private void addConfigGroup(ConfigFile spec, String path, String text) {
var entry = spec.getEntry(path);
if (!(entry instanceof ConfigFile.Group)) throw new IllegalArgumentException("Cannot find group " + path);
@ -308,16 +321,4 @@ private void addConfigEntry(ConfigFile.Entry value, String text) {
add(value.translationKey(), text);
add(value.translationKey() + ".tooltip", value.comment());
}
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile spec) {
return spec.entries().flatMap(LanguageProvider::getConfigEntries);
}
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile.Entry entry) {
if (entry instanceof ConfigFile.Value<?>) return Stream.of(entry);
if (entry instanceof ConfigFile.Group group) {
return Stream.concat(Stream.of(entry), group.children().flatMap(LanguageProvider::getConfigEntries));
}
throw new IllegalStateException("Invalid config entry " + entry);
}
}

View File

@ -6,10 +6,12 @@
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.shared.config.ConfigSpec;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
/**
* The global registry for {@link GenericSource}s.
@ -29,6 +31,11 @@ static synchronized void register(GenericSource source) {
}
public static Collection<GenericMethod> getAllMethods() {
return sources.stream().flatMap(GenericMethod::getMethods).toList();
var disabledMethods = Set.copyOf(ConfigSpec.disabledGenericMethods.get());
return sources.stream()
.filter(x -> !disabledMethods.contains(x.id()))
.flatMap(GenericMethod::getMethods)
.filter(x -> !disabledMethods.contains(x.id()))
.toList();
}
}

View File

@ -6,9 +6,12 @@
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;
@ -169,7 +172,10 @@ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
.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");

View File

@ -49,6 +49,29 @@ public boolean test(CommandSourceStack source) {
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();

View File

@ -44,12 +44,12 @@ public static <A extends ArgumentType<?>> void serializeToNetwork(FriendlyByteBu
@SuppressWarnings("unchecked")
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
RegistryWrappers.writeId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
type.serializeToNetwork((T) template, buffer);
}
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
var type = RegistryWrappers.readId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES);
var type = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES);
Objects.requireNonNull(type, "Unknown argument type");
return type.deserializeFromNetwork(buffer);
}

View File

@ -48,11 +48,15 @@ public CommandBuilder<S> requires(Predicate<S> predicate) {
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 @@ private <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany(String name, R
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 @@ private <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany(String name, R
// Chain all of them together!
tail.then(moreArg);
return link(tail);
return buildTail(tail);
};
}
@ -94,20 +98,16 @@ private static <T> List<T> getList(CommandContext<?> context, String name) {
@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();
}
}

View File

@ -10,6 +10,7 @@
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 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 static HelpingArgumentBuilder choice(String literal) {
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 @@ private LiteralCommandNode<CommandSourceStack> buildImpl(String id, String comma
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()) {

View File

@ -54,12 +54,6 @@ non-sealed interface Value<T> extends Entry, Supplier<T> {
* A group of config entries.
*/
non-sealed interface Group extends Entry {
/**
* Get all entries in this group.
*
* @return All child entries.
*/
Stream<Entry> children();
}
/**

View File

@ -35,6 +35,7 @@ public final class ConfigSpec {
public static final ConfigFile.Value<Boolean> logComputerErrors;
public static final ConfigFile.Value<Boolean> commandRequireCreative;
public static final ConfigFile.Value<Integer> uploadMaxSize;
public static final ConfigFile.Value<List<? extends String>> disabledGenericMethods;
public static final ConfigFile.Value<Integer> computerThreads;
public static final ConfigFile.Value<Integer> maxMainGlobalTime;
@ -139,6 +140,19 @@ private ConfigSpec() {
Require players to be in creative mode and be opped in order to interact with
command computers. This is the default behaviour for vanilla's Command blocks.""")
.define("command_require_creative", Config.commandRequireCreative);
disabledGenericMethods = builder
.comment("""
A list of generic methods or method sources to disable. Generic methods are
methods added to a block/block entity when there is no explicit peripheral
provider. This includes inventory methods (i.e. inventory.getItemDetail,
inventory.pushItems), and (if on Forge), the fluid_storage and energy_storage
methods.
Methods in this list can either be a whole group of methods (computercraft:inventory)
or a single method (computercraft:inventory#pushItems).
""")
.worldRestart()
.defineList("disabled_generic_methods", List.of(), x -> x instanceof String);
}
{

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.platform;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.IdMap;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.FriendlyByteBuf;
@ -36,9 +37,7 @@ public final class RegistryWrappers {
public static final RegistryWrapper<RecipeSerializer<?>> RECIPE_SERIALIZERS = PlatformHelper.get().wrap(Registries.RECIPE_SERIALIZER);
public static final RegistryWrapper<MenuType<?>> MENU = PlatformHelper.get().wrap(Registries.MENU);
public interface RegistryWrapper<T> extends Iterable<T> {
int getId(T object);
public interface RegistryWrapper<T> extends IdMap<T> {
ResourceLocation getKey(T object);
T get(ResourceLocation location);
@ -46,8 +45,6 @@ public interface RegistryWrapper<T> extends Iterable<T> {
@Nullable
T tryGet(ResourceLocation location);
T get(int id);
default Stream<T> stream() {
return StreamSupport.stream(spliterator(), false);
}
@ -56,15 +53,6 @@ default Stream<T> stream() {
private RegistryWrappers() {
}
public static <K> void writeId(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
buf.writeVarInt(registry.getId(object));
}
public static <K> K readId(FriendlyByteBuf buf, RegistryWrapper<K> registry) {
var id = buf.readVarInt();
return registry.get(id);
}
public static <K> void writeKey(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
buf.writeResourceLocation(registry.getKey(object));
}

View File

@ -74,7 +74,7 @@ public TurtleCommandResult execute(ITurtleAccess turtle) {
}
}
public static boolean deploy(
private static boolean deploy(
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction,
@Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
) {
@ -140,6 +140,22 @@ private static boolean canDeployOnBlock(
return true;
}
/**
* Calculate where a turtle would interact with a block.
*
* @param position The position of the block.
* @param side The side the turtle is clicking on.
* @return The hit result.
*/
public static BlockHitResult getHitResult(BlockPos position, Direction side) {
var hitX = 0.5 + side.getStepX() * 0.5;
var hitY = 0.5 + side.getStepY() * 0.5;
var hitZ = 0.5 + side.getStepZ() * 0.5;
if (Math.abs(hitY - 0.5) < 0.01) hitY = 0.45;
return new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false);
}
private static boolean deployOnBlock(
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
@Nullable Object[] extraArguments, boolean adjacent, @Nullable ErrorMessage outErrorMessage
@ -149,14 +165,8 @@ private static boolean deployOnBlock(
var playerPosition = position.relative(side);
turtlePlayer.setPosition(turtle, playerPosition, playerDir);
// Calculate where the turtle would hit the block
var hitX = 0.5f + side.getStepX() * 0.5f;
var hitY = 0.5f + side.getStepY() * 0.5f;
var hitZ = 0.5f + side.getStepZ() * 0.5f;
if (Math.abs(hitY - 0.5f) < 0.01f) hitY = 0.45f;
// Check if there's something suitable to place onto
var hit = new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false);
var hit = getHitResult(position, side);
var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit);
if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, adjacent, outErrorMessage)) {
return false;

View File

@ -6,7 +6,6 @@
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.turtle.*;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.TurtleUtil;
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
@ -46,31 +45,33 @@
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;
final boolean allowsEnchantments;
final TurtleToolDurability consumesDurability;
final boolean allowEnchantments;
final TurtleToolDurability consumeDurability;
final @Nullable TagKey<Block> breakable;
public TurtleTool(
ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier,
boolean allowsEnchantments, TurtleToolDurability consumesDurability, @Nullable TagKey<Block> breakable
boolean allowEnchantments, TurtleToolDurability consumeDurability, @Nullable TagKey<Block> breakable
) {
super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem));
item = toolItem;
this.damageMulitiplier = damageMulitiplier;
this.allowsEnchantments = allowsEnchantments;
this.consumesDurability = consumesDurability;
this.allowEnchantments = allowEnchantments;
this.consumeDurability = consumeDurability;
this.breakable = breakable;
}
@Override
public boolean isItemSuitable(ItemStack stack) {
if (consumesDurability == TurtleToolDurability.NEVER && stack.isDamaged()) return false;
if (!allowsEnchantments && isEnchanted(stack)) return false;
if (consumeDurability == TurtleToolDurability.NEVER && stack.isDamaged()) return false;
if (!allowEnchantments && isEnchanted(stack)) return false;
return true;
}
@ -81,37 +82,39 @@ private static boolean isEnchanted(ItemStack stack) {
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());
|| (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty());
}
@Override
public CompoundTag getUpgradeData(ItemStack stack) {
// Just use the current item's tag.
var upgradeData = super.getUpgradeData(stack);
// Store the item's current tag.
var itemTag = stack.getTag();
return itemTag == null ? new CompoundTag() : itemTag;
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);
if (!upgradeData.isEmpty()) item.setTag(upgradeData);
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) {
var item = getCraftingItem();
var tag = turtle.getUpgradeNBTData(side);
if (!tag.isEmpty()) item.setTag(tag);
return item.copy();
return getUpgradeItem(turtle.getUpgradeNBTData(side));
}
private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) {
var tag = turtle.getUpgradeNBTData(side);
var upgradeData = turtle.getUpgradeNBTData(side);
var useDurability = switch (consumesDurability) {
var useDurability = switch (consumeDurability) {
case NEVER -> false;
case WHEN_ENCHANTED -> isEnchanted(tag);
case WHEN_ENCHANTED ->
upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) && isEnchanted(upgradeData.getCompound(TAG_ITEM_TAG));
case ALWAYS -> true;
};
if (!useDurability) return;
@ -128,13 +131,12 @@ private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack
var itemTag = stack.getTag();
// Early return if the item hasn't changed to avoid redundant syncs with the client.
if ((itemTag == null && tag.isEmpty()) || Objects.equals(itemTag, tag)) return;
if (Objects.equals(itemTag, upgradeData.get(TAG_ITEM_TAG))) return;
if (itemTag == null) {
tag.getAllKeys().clear();
upgradeData.remove(TAG_ITEM_TAG);
} else {
for (var key : itemTag.getAllKeys()) tag.put(key, Nullability.assertNonNull(itemTag.get(key)));
tag.getAllKeys().removeIf(x -> !itemTag.contains(x));
upgradeData.put(TAG_ITEM_TAG, itemTag);
}
turtle.updateUpgradeNBTData(side);
@ -292,19 +294,20 @@ private boolean attack(ServerPlayer player, Direction direction, Entity entity)
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");
}
return withEquippedItem(turtle, side, direction, turtlePlayer -> {
var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND);
// Right-click the block when using a shovel/hoe.
if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) {
// Right-click the block when using a shovel/hoe. Important that we do this before checking the block is
// present, as we allow doing these actions from slightly further away.
if (PlatformHelper.get().hasToolUsage(stack) && useTool(level, turtle, turtlePlayer, stack, direction)) {
return TurtleCommandResult.success();
}
var blockPosition = turtle.getPosition().relative(direction);
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
return TurtleCommandResult.failure("Nothing to dig here");
}
// Check if we can break the block
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
if (!breakable.isSuccess()) return breakable;
@ -318,9 +321,34 @@ private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction
});
}
/**
* Attempt to use a tool against a block instead.
*
* @param level The current level.
* @param turtle The current turtle.
* @param turtlePlayer The turtle player, already positioned and with a stack equipped.
* @param stack The current tool's stack.
* @param direction The direction this action occurs in.
* @return Whether the tool was successfully used.
* @see PlatformHelper#hasToolUsage(ItemStack)
*/
private boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer turtlePlayer, ItemStack stack, Direction direction) {
var position = turtle.getPosition().relative(direction);
// Allow digging one extra block below the turtle, as you can't till dirt/flatten grass if there's a block
// above.
if (direction == Direction.DOWN && level.isEmptyBlock(position)) position = position.relative(direction);
if (!level.isInWorldBounds(position) || level.isEmptyBlock(position) || turtlePlayer.isBlockProtected(level, position)) {
return false;
}
var hit = TurtlePlaceCommand.getHitResult(position, direction.getOpposite());
var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit, x -> false);
return result.consumesAction();
}
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;
}

View File

@ -17,6 +17,8 @@
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import java.util.Objects;
public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<TurtleTool> {
public static final TurtleToolSerialiser INSTANCE = new TurtleToolSerialiser();
@ -29,8 +31,8 @@ public TurtleTool fromJson(ResourceLocation id, JsonObject object) {
var toolItem = GsonHelper.getAsItem(object, "item");
var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem);
var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f);
var allowsEnchantments = GsonHelper.getAsBoolean(object, "allowsEnchantments", false);
var consumesDurability = TurtleToolDurability.CODEC.byName(GsonHelper.getAsString(object, "consumesDurability", null), TurtleToolDurability.NEVER);
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")) {
@ -38,13 +40,14 @@ public TurtleTool fromJson(ResourceLocation id, JsonObject object) {
breakable = TagKey.create(Registries.BLOCK, tag);
}
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowsEnchantments, consumesDurability, breakable);
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowEnchantments, consumeDurability, breakable);
}
@Override
public TurtleTool fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
var adjective = buffer.readUtf();
var craftingItem = RegistryWrappers.readId(buffer, RegistryWrappers.ITEMS);
var craftingItem = buffer.readById(RegistryWrappers.ITEMS);
Objects.requireNonNull(craftingItem, "Unknown crafting item");
var toolItem = buffer.readItem();
// 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!
@ -59,11 +62,11 @@ public TurtleTool fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
@Override
public void toNetwork(FriendlyByteBuf buffer, TurtleTool upgrade) {
buffer.writeUtf(upgrade.getUnlocalisedAdjective());
RegistryWrappers.writeId(buffer, RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
buffer.writeId(RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
buffer.writeItem(upgrade.item);
buffer.writeFloat(upgrade.damageMulitiplier);
buffer.writeBoolean(upgrade.allowsEnchantments);
buffer.writeEnum(upgrade.consumesDurability);
buffer.writeBoolean(upgrade.allowEnchantments);
buffer.writeEnum(upgrade.consumeDurability);
buffer.writeBoolean(upgrade.breakable != null);
if (upgrade.breakable != null) buffer.writeResourceLocation(upgrade.breakable.location());
}

View File

@ -7,7 +7,6 @@
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/**
@ -38,12 +37,6 @@ public void setValue(Iterable<K> key, V value) {
getChild(key).current = value;
}
public Stream<V> children() {
return children == null
? Stream.empty()
: children.values().stream().map(x -> x.current).filter(Objects::nonNull);
}
public Stream<V> stream() {
return Stream.concat(
current == null ? Stream.empty() : Stream.of(current),

View File

@ -131,7 +131,7 @@
"tracking_field.computercraft.peripheral.name": "Chiamate alle periferiche",
"tracking_field.computercraft.websocket_incoming.name": "Websocket in arrivo",
"tracking_field.computercraft.websocket_outgoing.name": "Websocket in uscita",
"upgrade.computercraft.speaker.adjective": "Che Produce Rumori",
"upgrade.computercraft.speaker.adjective": "Rumoroso",
"upgrade.computercraft.wireless_modem_advanced.adjective": "Ender",
"upgrade.computercraft.wireless_modem_normal.adjective": "Wireless",
"upgrade.minecraft.crafting_table.adjective": "Artigiana",
@ -176,8 +176,8 @@
"gui.computercraft.config.default_computer_settings.tooltip": "Una lista di impostazioni predefinite per i nuovi computer, separate da virgola.\nEsempio: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\ndisattiverà tutti gli autocompletamenti.",
"gui.computercraft.config.execution.computer_threads.tooltip": "Imposta la quantità di thread che possono eseguire i computer. Un numero più alto significa\nche più computer possono essere eseguiti alla volta, ma può indurre a lag. Alcune mod potrebbero\nnon funzionare con numeri di thread maggiore a 1. Usare con cautela.\nRange: > 1",
"gui.computercraft.config.execution.max_main_global_time.tooltip": "Il limite massimo di tempo che può essere usato per eseguire task in un singolo tick,\nin millisecondi.\nNota, potremmo andare ben sopra questo limite, perché non c'è modo di sapere\nquanto impiega, questa configurazione mira ad essere il limite maggiore del tempo medio.\nRange: > 1",
"gui.computercraft.config.http.enabled.tooltip": "Attiva l'API \"http\" sui computer. Questo disabilita i programmi \"pastebin\" e \"wget\", \nche molti utenti dipendono. È raccomandato lasciarlo attivo ed utlizzare l'opzione \n\"rules\" per imporre controlli più adeguati.",
"gui.computercraft.config.http.rules.tooltip": "Una lista di regole che controllano il comportamento dell'API \"http\" per specifici domini\no indirizzi IP. Ogni regola è un elemento con un 'host' da confrontare, e una serie di\nproprietà. Le regole sono valutate in ordine, cioè le prime regole sovvrascrivono le successive.\nL'host può essere un dominio (\"pastebin.com\"), wildcard (\"*.pastebin.com\") oppure\nuna notazione CIDR (\"127.0.0.0/8\").\nSe non è presente nessuna regola, il dominio è bloccato.",
"gui.computercraft.config.http.enabled.tooltip": "Attiva l'API \"http\" sui computer. Disattivandolo, vengono disattivati anche i programmi \"pastebin\" e \"wget\", \ndi cui molti utenti dipendono. È raccomandato lasciarlo attivo ed utilizzare l'opzione \n\"rules\" per imporre controlli più adeguati.",
"gui.computercraft.config.http.rules.tooltip": "Una lista di regole che controllano il comportamento dell'API \"http\" per specifici domini\no indirizzi IP. Ogni regola corrisponde ad un nome host ed una porta opzionale, e poi imposta varie\nproprietà per la richiesta. Le regole sono valutate in ordine, cioè le prime regole sovvrascrivono le successive.\n\nProprietà valide:\n - \"host\" (richiesto): Il dominio o l'indirizzo IP della regola. Può essere un nome di dominio\n (\"pastebin.com\"), jolly (\"*.pastebin.com\") o notazione CIDR (\"127.0.0.0/8\").\n - \"port\" (opzionale): Corrisponde solo a richieste con una specifica porta, come 80 o 443.\n\n - \"action\" (opzionale): Se consentire o negare la richiesta.\n - \"max_download\" (opzionale): Quantità massima (in byte) che un computer può scaricare in\n questa richiesta.\n - \"max_upload\" (opzionale): Quantità massima (in byte) che un computer può caricare in\n questa richiesta.\n - \"max_websocket_message\" (opzionale): Quantità massima (in byte) che un computer può inviare o\n ricevere in un pacchetto websocket.\n - \"use_proxy\" (opzionale): Attiva l'uso di un proxy HTTP/SOCKS se configurato.",
"gui.computercraft.config.http.tooltip": "Controlla l'API HTTP",
"gui.computercraft.config.http.websocket_enabled.tooltip": "Attiva l'uso di websocket http. Questo richiede che l'opzione \"http_enable\" sia attiva.",
"gui.computercraft.config.log_computer_errors.tooltip": "Registra le eccezioni lanciate dalle periferiche e altri oggetti di Lua. Questo rende più facile\nper gli autori di mod per il debug di problemi, ma potrebbe risultare in spam di log durante\nl'uso di metodi buggati.",
@ -217,5 +217,15 @@
"gui.computercraft.config.upload_nag_delay": "Ritardo nel caricamento",
"gui.computercraft.config.term_sizes.tooltip": "Configura le dimensioni dei terminali di vari computer.\nTerminali più grandi richiedono più banda larga, usa con cautela.",
"gui.computercraft.config.upload_nag_delay.tooltip": "Ritardo in secondi dopo il quale verranno notificate le importazioni non gestite. Imposta a 0 per disattivare.\nRange: 0 ~ 60",
"gui.computercraft.upload.no_response.msg": "Il tuo computer non ha usato i tuoi file trasferiti. Potresti aver bisogno di eseguire il programma %s e riprovare di nuovo."
"gui.computercraft.upload.no_response.msg": "Il tuo computer non ha usato i tuoi file trasferiti. Potresti aver bisogno di eseguire il programma %s e riprovare di nuovo.",
"gui.computercraft.config.upload_max_size.tooltip": "Il limite delle dimensioni dei file da caricare, in byte. Deve essere in range di 1KiB e 16 MiB.\nRicorda che i caricamenti sono processati in un singolo tick - i file grandi o\nuna rete di bassa qualità può bloccare il thread di rete. E ricorda anche lo spazio di archiviazione dei dischi!\nRange: 1024 ~ 16777216",
"gui.computercraft.config.http.proxy": "Proxy",
"gui.computercraft.config.http.proxy.host": "Nome host",
"gui.computercraft.config.http.proxy.host.tooltip": "Il nome dell'host o l'indirizzo IP del server proxy.",
"gui.computercraft.config.http.proxy.port": "Porta",
"gui.computercraft.config.http.proxy.port.tooltip": "La porta del server proxy.\nRange: 1 ~ 65536",
"gui.computercraft.config.http.proxy.tooltip": "Transmetti richieste HTTP e websocket attraverso un server proxy. Ha effetto solo\nsu regole HTTP con \"use_proxy\" attivo (disattivato di default).\nSe l'autenticazione è richiesta per il proxy, creare un file \"computercraft-proxy.pw\"\nnella stessa cartella di \"computercraft-server.toml\", contenendo il nome utente e\npassword separati dal carattere due punti, per esempio: \"myuser:mypassword\".\nI proxy SOCKS4 necessitano solo il nome utente.",
"gui.computercraft.config.http.proxy.type": "Tipo proxy",
"gui.computercraft.config.http.proxy.type.tooltip": "Il tipo di proxy da usare.\nValori consentiti: HTTP, HTTPS, SOCKS4, SOCKS5",
"gui.computercraft.config.upload_max_size": "Limite delle dimensioni dei file da caricare (byte)"
}

View File

@ -1,16 +1,7 @@
{
"parent": "minecraft:block/block",
"parent": "minecraft:block/orientable",
"render_type": "cutout",
"textures": {
"particle": "#front"
},
"display": {
"firstperson_righthand": {
"rotation": [ 0, 135, 0 ],
"translation": [ 0, 0, 0 ],
"scale": [ 0.40, 0.40, 0.40 ]
}
},
"computercraft:emissive_texture": "cursor",
"elements": [
{
"from": [ 0, 0, 0 ],

View File

@ -96,7 +96,12 @@ public <T> T tryGetRegistryObject(ResourceKey<Registry<T>> registry, ResourceLoc
@Override
public boolean shouldLoadResource(JsonObject object) {
throw new UnsupportedOperationException("Cannot use loot conditions");
throw new UnsupportedOperationException("Cannot use resource conditions");
}
@Override
public void addRequiredModCondition(JsonObject object, String modId) {
throw new UnsupportedOperationException("Cannot use resource conditions");
}
@Override

View File

@ -7,9 +7,13 @@
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,6 +34,7 @@
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
@ -140,11 +145,31 @@ fun Gather_lava(helper: GameTestHelper) = helper.sequence {
*/
@GameTest
fun Hoe_dirt(helper: GameTestHelper) = helper.sequence {
thenOnComputer { turtle.dig(Optional.empty()).await().assertArrayEquals(true, message = "Dug with hoe") }
thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 2, 1)) }
}
/**
* Checks turtles can hoe dirt with a block gap below them.
*
* @see [#1527](https://github.com/cc-tweaked/CC-Tweaked/issues/1527)
*/
@GameTest
fun Hoe_dirt_below(helper: GameTestHelper) = helper.sequence {
thenOnComputer { turtle.digDown(Optional.empty()).await().assertArrayEquals(true, message = "Dug with hoe") }
thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 1, 1)) }
}
/**
* Checks turtles cannot hoe dirt with a block gap in front of them.
*/
@GameTest
fun Hoe_dirt_distant(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
turtle.dig(Optional.empty()).await()
.assertArrayEquals(true, message = "Dug with hoe")
.assertArrayEquals(false, "Nothing to dig here", message = "Dug with hoe")
}
thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 2, 1)) }
thenExecute { helper.assertBlockPresent(Blocks.DIRT, BlockPos(1, 2, 2)) }
}
/**
@ -174,6 +199,85 @@ fun Break_cable(helper: GameTestHelper) = helper.sequence {
}
}
/**
* 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
*

View File

@ -0,0 +1,6 @@
{
"type": "computercraft:tool",
"item": "minecraft:netherite_pickaxe",
"allowEnchantments": true,
"consumeDurability": "when_enchanted"
}

View File

@ -0,0 +1,5 @@
{
"type": "computercraft:tool",
"item": "minecraft:wooden_pickaxe",
"consumeDurability": "always"
}

View 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}"
]
}

View 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}"
]
}

View File

@ -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}"
]
}

View File

@ -0,0 +1,40 @@
{
DataVersion: 2730,
size: [3, 3, 3],
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: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:dirt"},
{pos: [1, 0, 2], 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: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], 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: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], 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: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.hoe_dirt_below", LeftUpgrade: "minecraft:diamond_hoe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:dirt",
"computercraft:turtle_normal{facing:south,waterlogged:false}",
"minecraft:air"
]
}

View File

@ -0,0 +1,40 @@
{
DataVersion: 2730,
size: [3, 3, 3],
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: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], 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: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.hoe_dirt_distant", LeftUpgrade: "minecraft:diamond_hoe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:dirt"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], 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: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:dirt",
"computercraft:turtle_normal{facing:south,waterlogged:false}",
"minecraft:air"
]
}

View File

@ -132,7 +132,8 @@ private static long getEpochForCalendar(Calendar c) {
* @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

View File

@ -6,9 +6,14 @@
import com.google.common.net.InetAddresses;
import java.net.Inet4Address;
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.
@ -107,12 +112,47 @@ public boolean matches(InetAddress socketAddress) {
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.
"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
|| isCarrierGradeNatAddress(socketAddress) // 100.64.0.0/10
|| 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;
}
/**
* Determine if an IP address lives within the CGNAT address range (100.64.0.0/10).
*
* @param address The IP address to test.
* @return Whether this address sits in the CGNAT address range.
* @see <a href="https://en.wikipedia.org/wiki/Carrier-grade_NAT">Carrier-grade NAT on Wikipedia</a>
*/
private boolean isCarrierGradeNatAddress(InetAddress address) {
if (!(address instanceof Inet4Address)) return false;
var bytes = address.getAddress();
return bytes[0] == 100 && ((bytes[1] & 0xFF) >= 64 && (bytes[1] & 0xFF) <= 127);
}
}

View File

@ -24,18 +24,24 @@
public final class GenericMethod {
private static final Logger LOG = LoggerFactory.getLogger(GenericMethod.class);
final GenericSource source;
final Method method;
final LuaFunction annotation;
final Class<?> target;
final @Nullable PeripheralType peripheralType;
private GenericMethod(Method method, LuaFunction annotation, Class<?> target, @Nullable PeripheralType peripheralType) {
private GenericMethod(GenericSource source, Method method, LuaFunction annotation, Class<?> target, @Nullable PeripheralType peripheralType) {
this.source = source;
this.method = method;
this.annotation = annotation;
this.target = target;
this.peripheralType = peripheralType;
}
public String id() {
return source.id() + "#" + name();
}
public String name() {
return method.getName();
}
@ -69,7 +75,7 @@ public static Stream<GenericMethod> getMethods(GenericSource source) {
var target = Reflect.getRawType(method, types[0], false);
if (target == null) return null;
return new GenericMethod(method, annotation, target, type);
return new GenericMethod(source, method, annotation, target, type);
})
.filter(Objects::nonNull);
}

View File

@ -76,7 +76,7 @@ public boolean forEachMethod(Object object, TargetedConsumer<T> consumer) {
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);
for (var method : extraMethods) consumer.accept(extra, method.name(), method.method(), method);
}
}

View File

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

View File

@ -1,3 +1,29 @@
# New features in CC: Tweaked 1.106.1
Several bug fixes:
* Block the CGNAT range (100.64.0.0/10) by default.
* Fix conflicts with other mods replacing reach distance.
# 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.

View File

@ -1,25 +1,7 @@
New features in CC: Tweaked 1.105.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).
New features in CC: Tweaked 1.106.1
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.
* Block the CGNAT range (100.64.0.0/10) by default.
* Fix conflicts with other mods replacing reach distance.
Type "help changelog" to see the full version history.

View File

@ -4,6 +4,7 @@
package dan200.computercraft.core;
import com.google.common.base.Splitter;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
@ -346,10 +347,10 @@ public final void start(Map<?, ?> tests) throws LuaException {
var details = (Map<?, ?>) entry.getValue();
var def = (String) details.get("definition");
var parts = name.split("\0");
var parts = Splitter.on('\0').splitToList(name);
var builder = root;
for (var i = 0; i < parts.length - 1; i++) builder = builder.get(parts[i]);
builder.runs(parts[parts.length - 1], def, () -> {
for (var i = 0; i < parts.size() - 1; i++) builder = builder.get(parts.get(i));
builder.runs(parts.get(parts.size() - 1), def, () -> {
// Run it
lock.lockInterruptibly();
try {

View File

@ -31,12 +31,30 @@ public void matchesPort() {
@ValueSource(strings = {
"0.0.0.0", "[::]",
"localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]",
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1"
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1",
// Multicast
"224.0.0.1", "ff02::1",
// CGNAT
"100.64.0.0", "100.127.255.255",
// Cloud metadata providers
"100.100.100.200", // Alibaba
"192.0.0.192", // Oracle
"fd00:ec2::254", // AWS
"169.254.169.254", // AWS, Digital Ocean, GCP, etc..
})
public void blocksLocalDomains(String domain) {
assertEquals(apply(CoreConfig.httpRules, domain, 80).action, Action.DENY);
}
@ParameterizedTest
@ValueSource(strings = {
// Ensure either side of the CGNAT range is allowed.
"100.63.255.255", "100.128.0.0"
})
public void allowsNonLocalDomains(String domain) {
assertEquals(apply(CoreConfig.httpRules, domain, 80).action, Action.ALLOW);
}
private Options apply(Iterable<AddressRule> rules, String host, int port) {
return AddressRule.apply(rules, host, new InetSocketAddress(host, port));
}

View File

@ -58,7 +58,7 @@ public void testDynamicPeripheral() {
@Test
public void testExtra() {
ComputerBootstrap.run("assert(extra.go, 'go')\nassert(extra.go2, 'go2')",
ComputerBootstrap.run("assert(extra.go() == nil, 'go')\nassert(extra.go2() == 456, 'go2')",
x -> x.addApi(new ExtraObject()),
50);
}
@ -163,7 +163,8 @@ public String[] getNames() {
}
@LuaFunction
public final void go2() {
public final int go2() {
return 456;
}
@Override

View File

@ -47,6 +47,7 @@ addRemappedConfiguration("testWithSodium")
addRemappedConfiguration("testWithIris")
dependencies {
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
modImplementation(libs.bundles.externalMods.fabric)
modCompileOnly(libs.bundles.externalMods.fabric.compile) {
exclude("net.fabricmc", "fabric-loader")

View File

@ -4,19 +4,22 @@
package dan200.computercraft.client;
import dan200.computercraft.client.model.EmissiveComputerModel;
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.model.CustomModelLoader;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.model.ModelLoadingRegistry;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.client.player.ClientPickBlockGatherCallback;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.item.ItemStack;
@ -36,9 +39,12 @@ public static void init() {
ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register);
ClientRegistry.registerMainThread();
ModelLoadingRegistry.INSTANCE.registerModelProvider((manager, out) -> ClientRegistry.registerExtraModels(out));
ModelLoadingRegistry.INSTANCE.registerResourceProvider(loader -> (path, ctx) -> TurtleModelLoader.load(loader, path));
ModelLoadingRegistry.INSTANCE.registerResourceProvider(loader -> (path, ctx) -> EmissiveComputerModel.load(loader, path));
PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> {
ClientRegistry.registerExtraModels(context::addModels);
context.resolveModel().register(ctx -> state.loadModel(ctx.id()));
context.modifyModelAfterBake().register((model, ctx) -> state.wrapModel(ctx, model));
});
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_COMMAND.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_ADVANCED.get(), RenderType.cutout());
@ -68,5 +74,7 @@ public static void init() {
return cable.getCloneItemStack(state, hit, level, pos, player);
});
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
}
}

View File

@ -4,24 +4,33 @@
package dan200.computercraft.client.model;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* A {@link BakedModel} formed from two or more other models stitched together.
*/
public class CompositeBakedModel extends CustomBakedModel {
public class CompositeBakedModel extends ForwardingBakedModel {
private final boolean isVanillaAdapter;
private final List<BakedModel> models;
public CompositeBakedModel(List<BakedModel> models) {
super(models.get(0));
wrapped = models.get(0);
isVanillaAdapter = models.stream().allMatch(FabricBakedModel::isVanillaAdapter);
this.models = models;
}
@ -29,6 +38,11 @@ public static BakedModel of(List<BakedModel> models) {
return models.size() == 1 ? models.get(0) : new CompositeBakedModel(models);
}
@Override
public boolean isVanillaAdapter() {
return isVanillaAdapter;
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
@SuppressWarnings({ "unchecked", "rawtypes" })
@ -39,6 +53,16 @@ public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direc
return new ConcatListView(quads);
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
for (var model : models) model.emitBlockQuads(blockView, state, pos, randomSupplier, context);
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
for (var model : models) model.emitItemQuads(stack, randomSupplier, context);
}
private static final class ConcatListView extends AbstractList<BakedQuad> {
private final List<BakedQuad>[] quads;

View File

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Supplier;
/**
* A subclass of {@link ForwardingBakedModel} which doesn't forward rendering.
*/
public abstract class CustomBakedModel extends ForwardingBakedModel {
public CustomBakedModel(BakedModel wrapped) {
this.wrapped = wrapped;
}
@Override
public abstract List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand);
@Override
public final void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
context.bakedModelConsumer().accept(this);
}
@Override
public final void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
context.bakedModelConsumer().accept(this);
}
}

View File

@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.model.turtle.UnbakedTurtleModel;
import dan200.computercraft.mixin.client.BlockModelAccessor;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.FileToIdConverter;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
/**
* Provides custom model loading for various CC models.
* <p>
* This is used from a {@link PreparableModelLoadingPlugin}, which {@linkplain #prepare(ResourceManager, Executor) loads
* data from disk} in parallel with other loader plugins, and then hooks into the model loading pipeline
* ({@link #loadModel(ResourceLocation)}, {@link #wrapModel(ModelModifier.AfterBake.Context, BakedModel)}).
*
* @see EmissiveBakedModel
* @see UnbakedTurtleModel
*/
public final class CustomModelLoader {
private static final Logger LOG = LoggerFactory.getLogger(CustomModelLoader.class);
private static final FileToIdConverter converter = FileToIdConverter.json("models");
private final Map<ResourceLocation, UnbakedModel> models = new HashMap<>();
private final Map<ResourceLocation, String> emissiveModels = new HashMap<>();
private CustomModelLoader() {
}
public static CompletableFuture<CustomModelLoader> prepare(ResourceManager resources, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
var loader = new CustomModelLoader();
for (var resource : resources.listResources("models", x -> x.getNamespace().equals(ComputerCraftAPI.MOD_ID) && x.getPath().endsWith(".json")).entrySet()) {
loader.loadModel(resource.getKey(), resource.getValue());
}
return loader;
}, executor);
}
private void loadModel(ResourceLocation path, Resource resource) {
var id = converter.fileToId(path);
try {
JsonObject model;
try (Reader reader = resource.openAsReader()) {
model = GsonHelper.parse(reader).getAsJsonObject();
}
var loader = GsonHelper.getAsString(model, "loader", null);
if (loader != null) {
var unbaked = switch (loader) {
case ComputerCraftAPI.MOD_ID + ":turtle" -> UnbakedTurtleModel.parse(model);
default -> throw new JsonParseException("Unknown model loader " + loader);
};
models.put(id, unbaked);
}
var emissive = GsonHelper.getAsString(model, "computercraft:emissive_texture", null);
if (emissive != null) emissiveModels.put(id, emissive);
} catch (IllegalArgumentException | IOException | JsonParseException e) {
LOG.error("Couldn't parse model file {} from {}", id, path, e);
}
}
/**
* Load a custom model. This searches for CC models with a custom {@code loader} field.
*
* @param path The path of the model to load.
* @return The unbaked model that has been loaded, or {@code null} if the model should be loaded as a vanilla model.
*/
public @Nullable UnbakedModel loadModel(ResourceLocation path) {
return path.getNamespace().equals(ComputerCraftAPI.MOD_ID) ? models.get(path) : null;
}
/**
* Wrap a baked model.
* <p>
* This just finds models which specify an emissive texture ({@code computercraft:emissive_texture} in the JSON) and
* wraps them in a {@link EmissiveBakedModel}.
*
* @param ctx The current model loading context.
* @param baked The baked model to wrap.
* @return The wrapped model.
*/
public BakedModel wrapModel(ModelModifier.AfterBake.Context ctx, BakedModel baked) {
if (!ctx.id().getNamespace().equals(ComputerCraftAPI.MOD_ID)) return baked;
if (!(ctx.sourceModel() instanceof BlockModel model)) return baked;
var emissive = getEmissive(ctx.id(), model);
return emissive == null ? baked : EmissiveBakedModel.wrap(baked, ctx.textureGetter().apply(model.getMaterial(emissive)));
}
private @Nullable String getEmissive(ResourceLocation id, BlockModel model) {
while (true) {
var emissive = emissiveModels.get(id);
if (emissive != null) return emissive;
id = ((BlockModelAccessor) model).computercraft$getParentLocation();
model = ((BlockModelAccessor) model).computercraft$getParent();
if (id == null || model == null) return null;
}
}
}

View File

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.util.function.Supplier;
/**
* Wraps an arbitrary {@link BakedModel} to render a single texture as emissive.
* <p>
* While Fabric has a quite advanced rendering extension API (including support for custom materials), but unlike Forge
* it doesn't expose this in the model JSON (though externals mods like <a href="https://github.com/vram-guild/json-model-extensions/">JMX</a>
* do handle this).
* <p>
* Instead, we support emissive quads by injecting a {@linkplain CustomModelLoader custom model loader} which wraps the
* baked model in a {@link EmissiveBakedModel}, which renders specific quads as emissive.
*/
public final class EmissiveBakedModel extends ForwardingBakedModel {
private final TextureAtlasSprite emissiveTexture;
private final RenderMaterial defaultMaterial;
private final RenderMaterial emissiveMaterial;
private EmissiveBakedModel(BakedModel wrapped, TextureAtlasSprite emissiveTexture, RenderMaterial defaultMaterial, RenderMaterial emissiveMaterial) {
this.wrapped = wrapped;
this.emissiveTexture = emissiveTexture;
this.defaultMaterial = defaultMaterial;
this.emissiveMaterial = emissiveMaterial;
}
public static BakedModel wrap(BakedModel model, TextureAtlasSprite emissiveTexture) {
var renderer = RendererAccess.INSTANCE.getRenderer();
return renderer == null ? model : new EmissiveBakedModel(
model,
emissiveTexture,
renderer.materialFinder().find(),
renderer.materialFinder().emissive(true).find()
);
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
emitQuads(context, state, randomSupplier.get());
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
emitQuads(context, null, randomSupplier.get());
}
private void emitQuads(RenderContext context, @Nullable BlockState state, RandomSource random) {
var emitter = context.getEmitter();
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
var cullFace = ModelHelper.faceFromIndex(faceIdx);
var quads = wrapped.getQuads(state, cullFace, random);
var count = quads.size();
for (var i = 0; i < count; i++) {
final var q = quads.get(i);
emitter.fromVanilla(q, q.getSprite() == emissiveTexture ? emissiveMaterial : defaultMaterial, cullFace);
emitter.emit();
}
}
}
}

View File

@ -1,172 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Either;
import dan200.computercraft.api.ComputerCraftAPI;
import net.fabricmc.fabric.api.client.model.ModelProviderException;
import net.fabricmc.fabric.api.client.model.ModelResourceProvider;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.RandomSource;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Wraps a computer's {@link BlockModel}/{@link BakedModel} to render the computer's cursor as an emissive quad.
* <p>
* While Fabric has a quite advanced rendering extension API (including support for custom materials), but unlike Forge
* it doesn't expose this in the model JSON (though externals mods like <a href="https://github.com/vram-guild/json-model-extensions/">JMX</a>
* do handle this).
* <p>
* Instead, we support emissive quads by injecting a custom {@linkplain ModelResourceProvider model loader/provider}
* which targets a hard-coded list of computer models, and wraps the returned model in a custom
* {@linkplain FabricBakedModel} implementation which renders specific quads as emissive.
* <p>
* See also the <code>assets/computercraft/models/block/computer_on.json</code> model, which is the base for all
* emissive computer models.
*/
public final class EmissiveComputerModel {
private static final Set<String> MODELS = Set.of(
"item/computer_advanced",
"block/computer_advanced_on",
"block/computer_advanced_blinking",
"item/computer_command",
"block/computer_command_on",
"block/computer_command_blinking",
"item/computer_normal",
"block/computer_normal_on",
"block/computer_normal_blinking"
);
private EmissiveComputerModel() {
}
public static @Nullable UnbakedModel load(ResourceManager resources, ResourceLocation path) throws ModelProviderException {
if (!path.getNamespace().equals(ComputerCraftAPI.MOD_ID) || !MODELS.contains(path.getPath())) return null;
JsonObject json;
try (var reader = resources.openAsReader(new ResourceLocation(path.getNamespace(), "models/" + path.getPath() + ".json"))) {
json = GsonHelper.parse(reader).getAsJsonObject();
} catch (IOException e) {
throw new ModelProviderException("Failed loading model " + path, e);
}
// Parse a subset of the model JSON
var parent = new ResourceLocation(GsonHelper.getAsString(json, "parent"));
Map<String, Either<Material, String>> textures = new HashMap<>();
if (json.has("textures")) {
var jsonObject = GsonHelper.getAsJsonObject(json, "textures");
for (var entry : jsonObject.entrySet()) {
var texture = entry.getValue().getAsString();
textures.put(entry.getKey(), texture.startsWith("#")
? Either.right(texture.substring(1))
: Either.left(new Material(InventoryMenu.BLOCK_ATLAS, new ResourceLocation(texture)))
);
}
}
return new Unbaked(parent, textures);
}
/**
* An {@link UnbakedModel} which wraps the returned model using {@link Baked}.
* <p>
* This subclasses {@link BlockModel} to allow using these models as a parent of other models.
*/
private static final class Unbaked extends BlockModel {
Unbaked(ResourceLocation parent, Map<String, Either<Material, String>> materials) {
super(parent, List.of(), materials, null, null, ItemTransforms.NO_TRANSFORMS, List.of());
}
@Override
public BakedModel bake(ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state, ResourceLocation location) {
var baked = super.bake(baker, spriteGetter, state, location);
if (!hasTexture("cursor")) return baked;
var render = RendererAccess.INSTANCE.getRenderer();
if (render == null) return baked;
return new Baked(
baked,
spriteGetter.apply(getMaterial("cursor")),
render.materialFinder().find(),
render.materialFinder().emissive(0, true).find()
);
}
}
/**
* A {@link FabricBakedModel} which renders quads using the {@code "cursor"} texture as emissive.
*/
private static final class Baked extends ForwardingBakedModel {
private final TextureAtlasSprite cursor;
private final RenderMaterial defaultMaterial;
private final RenderMaterial emissiveMaterial;
Baked(BakedModel wrapped, TextureAtlasSprite cursor, RenderMaterial defaultMaterial, RenderMaterial emissiveMaterial) {
this.wrapped = wrapped;
this.cursor = cursor;
this.defaultMaterial = defaultMaterial;
this.emissiveMaterial = emissiveMaterial;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
emitQuads(context, state, randomSupplier.get());
}
@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
emitQuads(context, null, randomSupplier.get());
}
private void emitQuads(RenderContext context, @Nullable BlockState state, RandomSource random) {
var emitter = context.getEmitter();
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
var cullFace = ModelHelper.faceFromIndex(faceIdx);
var quads = wrapped.getQuads(state, cullFace, random);
var count = quads.size();
for (var i = 0; i < count; i++) {
final var q = quads.get(i);
emitter.fromVanilla(q, q.getSprite() == cursor ? emissiveMaterial : defaultMaterial, cullFace);
emitter.emit();
}
}
}
}
}

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.mojang.math.Transformation;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.core.Direction;
import org.joml.Vector3f;
/**
* Extends {@link ModelTransformer} to also work as a {@link RenderContext.QuadTransform}.
*/
public class FabricModelTransformer extends ModelTransformer implements RenderContext.QuadTransform {
public FabricModelTransformer(Transformation transformation) {
super(transformation);
}
@Override
public boolean transform(MutableQuadView quad) {
var vec3 = new Vector3f();
for (var i = 0; i < 4; i++) {
quad.copyPos(i, vec3);
transformation.transformPosition(vec3);
quad.pos(i, vec3);
}
if (invert) {
swapQuads(quad, 0, 3);
swapQuads(quad, 1, 2);
}
var face = quad.nominalFace();
if (face != null) quad.nominalFace(Direction.rotate(transformation, face));
return true;
}
private static void swapQuads(MutableQuadView quad, int a, int b) {
float aX = quad.x(a), aY = quad.y(a), aZ = quad.z(a), aU = quad.u(a), aV = quad.v(a);
float bX = quad.x(b), bY = quad.y(b), bZ = quad.z(b), bU = quad.u(b), bV = quad.v(b);
quad.pos(b, aX, aY, aZ).uv(b, aU, aV);
quad.pos(a, bX, bY, bZ).uv(a, bU, bV);
}
}

View File

@ -6,30 +6,49 @@
import com.mojang.math.Transformation;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.Supplier;
/**
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
*
* @see ModelTransformer
*/
public class TransformedBakedModel extends CustomBakedModel {
private final ModelTransformer transformation;
public class TransformedBakedModel extends ForwardingBakedModel {
private final FabricModelTransformer transformation;
public TransformedBakedModel(BakedModel model, Transformation transformation) {
super(model);
this.transformation = new ModelTransformer(transformation);
wrapped = model;
this.transformation = new FabricModelTransformer(transformation);
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
return transformation.transform(wrapped.getQuads(blockState, face, rand));
}
@Override
public final void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
context.popTransform();
}
@Override
public final void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
context.pushTransform(transformation);
super.emitItemQuads(stack, randomSupplier, context);
context.popTransform();
}
}

View File

@ -1,82 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import net.fabricmc.fabric.api.client.model.ModelProviderException;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* A model "loader" (the concept doesn't quite exist in the same way as it does on Forge) for turtle item models.
* <p>
* This reads in the associated model file (typically {@code computercraft:block/turtle_xxx}) and wraps it in a
* {@link TurtleModel}.
*/
public final class TurtleModelLoader {
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private TurtleModelLoader() {
}
public static @Nullable UnbakedModel load(ResourceManager resources, ResourceLocation path) throws ModelProviderException {
if (!path.getNamespace().equals(ComputerCraftAPI.MOD_ID)) return null;
if (!path.getPath().equals("item/turtle_normal") && !path.getPath().equals("item/turtle_advanced")) {
return null;
}
try (var reader = resources.openAsReader(new ResourceLocation(path.getNamespace(), "models/" + path.getPath() + ".json"))) {
var modelContents = GsonHelper.parse(reader).getAsJsonObject();
var loader = GsonHelper.getAsString(modelContents, "loader", null);
if (!Objects.equals(loader, ComputerCraftAPI.MOD_ID + ":turtle")) return null;
var model = new ResourceLocation(GsonHelper.getAsString(modelContents, "model"));
return new Unbaked(model);
} catch (IOException e) {
throw new ModelProviderException("Failed loading model " + path, e);
}
}
public static final class Unbaked implements UnbakedModel {
private final ResourceLocation model;
private Unbaked(ResourceLocation model) {
this.model = model;
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(model, COLOUR_TURTLE_MODEL);
}
@Override
public void resolveParents(Function<ResourceLocation, UnbakedModel> function) {
function.apply(model).resolveParents(function);
function.apply(COLOUR_TURTLE_MODEL).resolveParents(function);
}
@Override
public BakedModel bake(ModelBaker bakery, Function<Material, TextureAtlasSprite> spriteGetter, ModelState transform, ResourceLocation location) {
var mainModel = bakery.bake(model, transform);
if (mainModel == null) throw new NullPointerException(model + " failed to bake");
var colourModel = bakery.bake(COLOUR_TURTLE_MODEL, transform);
if (colourModel == null) throw new NullPointerException(COLOUR_TURTLE_MODEL + " failed to bake");
return new TurtleModel(mainModel, colourModel);
}
}
}

View File

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model.turtle;
import com.google.gson.JsonObject;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
/**
* A {@link UnbakedModel} for {@link TurtleModel}s.
* <p>
* This reads in the associated model file (typically {@code computercraft:block/turtle_xxx}) and wraps it in a
* {@link TurtleModel}.
*/
public final class UnbakedTurtleModel implements UnbakedModel {
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private final ResourceLocation model;
private UnbakedTurtleModel(ResourceLocation model) {
this.model = model;
}
public static UnbakedModel parse(JsonObject json) {
var model = new ResourceLocation(GsonHelper.getAsString(json, "model"));
return new UnbakedTurtleModel(model);
}
@Override
public Collection<ResourceLocation> getDependencies() {
return List.of(model, COLOUR_TURTLE_MODEL);
}
@Override
public void resolveParents(Function<ResourceLocation, UnbakedModel> function) {
function.apply(model).resolveParents(function);
function.apply(COLOUR_TURTLE_MODEL).resolveParents(function);
}
@Override
public BakedModel bake(ModelBaker bakery, Function<Material, TextureAtlasSprite> spriteGetter, ModelState transform, ResourceLocation location) {
var mainModel = bakery.bake(model, transform);
if (mainModel == null) throw new NullPointerException(model + " failed to bake");
var colourModel = bakery.bake(COLOUR_TURTLE_MODEL, transform);
if (colourModel == null) throw new NullPointerException(COLOUR_TURTLE_MODEL + " failed to bake");
return new TurtleModel(mainModel, colourModel);
}
}

View File

@ -8,7 +8,6 @@
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.fabricmc.fabric.api.client.model.BakedModelManagerHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
@ -23,7 +22,7 @@ public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
var model = BakedModelManagerHelper.getModel(manager, location);
var model = manager.getModel(location);
return model == null ? manager.getMissingModel() : model;
}
}

View File

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import javax.annotation.Nullable;
@Mixin(BlockModel.class)
public interface BlockModelAccessor {
@Accessor("parentLocation")
@Nullable
ResourceLocation computercraft$getParentLocation();
@Accessor("parent")
@Nullable
BlockModel computercraft$getParent();
}

View File

@ -7,6 +7,7 @@
"defaultRequire": 1
},
"client": [
"BlockModelAccessor",
"BlockRenderDispatcherMixin",
"DebugScreenOverlayMixin",
"GameRendererMixin",

View File

@ -81,6 +81,8 @@
"gui.computercraft.config.default_computer_settings.tooltip": "A comma separated list of default system settings to set on new computers.\nExample: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwill disable all autocompletion.",
"gui.computercraft.config.disable_lua51_features": "Disable Lua 5.1 features",
"gui.computercraft.config.disable_lua51_features.tooltip": "Set this to true to disable Lua 5.1 functions that will be removed in a future\nupdate. Useful for ensuring forward compatibility of your programs now.",
"gui.computercraft.config.disabled_generic_methods": "Disabled generic methods",
"gui.computercraft.config.disabled_generic_methods.tooltip": "A list of generic methods or method sources to disable. Generic methods are\nmethods added to a block/block entity when there is no explicit peripheral\nprovider. This includes inventory methods (i.e. inventory.getItemDetail,\ninventory.pushItems), and (if on Forge), the fluid_storage and energy_storage\nmethods.\nMethods in this list can either be a whole group of methods (computercraft:inventory)\nor a single method (computercraft:inventory#pushItems).\n",
"gui.computercraft.config.execution": "Execution",
"gui.computercraft.config.execution.computer_threads": "Computer threads",
"gui.computercraft.config.execution.computer_threads.tooltip": "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution.\nRange: > 1",
@ -204,6 +206,10 @@
"item.computercraft.printed_pages": "Printed Pages",
"item.computercraft.treasure_disk": "Floppy Disk",
"itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Computers",
"tag.item.computercraft.monitor": "Monitors",
"tag.item.computercraft.turtle": "Turtles",
"tag.item.computercraft.wired_modem": "Wired modems",
"tracking_field.computercraft.avg": "%s (avg)",
"tracking_field.computercraft.computer_tasks.name": "Tasks",
"tracking_field.computercraft.count": "%s (count)",

View File

@ -73,7 +73,7 @@ public void lootTable(List<LootTableProvider.SubProviderEntry> tables) {
for (var table : tables) {
generator.addProvider((FabricDataOutput out) -> new SimpleFabricLootTableProvider(out, table.paramSet()) {
@Override
public void accept(BiConsumer<ResourceLocation, LootTable.Builder> exporter) {
public void generate(BiConsumer<ResourceLocation, LootTable.Builder> exporter) {
table.provider().get().generate(exporter);
}
});

View File

@ -9,24 +9,38 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Item.class)
class ItemMixin {
/**
* Replace the reach distance in {@link Item#getPlayerPOVHitResult(Level, Player, ClipContext.Fluid)}.
*
* @param reach The original reach distance.
* @param level The current level.
* @param player The current player.
* @return The new reach distance.
* @param level The current level.
* @param player The current player.
* @param fluidMode The current clip-context fluid mode.
* @param cir Callback info to store the new reach distance.
* @see FakePlayer#getBlockReach()
*/
@ModifyConstant(method = "getPlayerPOVHitResult", constant = @Constant(doubleValue = 5))
@Inject(method = "getPlayerPOVHitResult", at = @At("HEAD"), cancellable = true)
@SuppressWarnings("UnusedMethod")
private static double getReachDistance(double reach, Level level, Player player) {
return player instanceof FakePlayer fp ? fp.getBlockReach() : reach;
private static void getReachDistance(Level level, Player player, ClipContext.Fluid fluidMode, CallbackInfoReturnable<BlockHitResult> cir) {
// It would theoretically be cleaner to use @ModifyConstant here, but as it's treated as a @Redirect, it doesn't
// compose with other mods. Instead, we replace the method when working with our fake player.
if (player instanceof FakePlayer fp) cir.setReturnValue(getHitResult(level, fp, fluidMode));
}
@Unique
private static BlockHitResult getHitResult(Level level, FakePlayer player, ClipContext.Fluid fluidMode) {
var start = player.getEyePosition();
var reach = player.getBlockReach();
var direction = player.getViewVector(1.0f);
var end = start.add(direction.x() * reach, direction.y() * reach, direction.z() * reach);
return level.clip(new ClipContext(start, end, ClipContext.Block.OUTLINE, fluidMode, player));
}
}

View File

@ -30,7 +30,6 @@
import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.PreparableReloadListener;
@ -100,8 +99,6 @@ public static void init() {
CommonHooks.onDatapackReload((name, listener) -> ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new ReloadListener(name, listener)));
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill);
ComputerCraftAPI.registerGenericSource(new InventoryMethods());

View File

@ -62,12 +62,15 @@ public synchronized void load(Path path) {
if (loadConfig()) config.save();
}
@SuppressWarnings("unchecked")
private Stream<ValueImpl<?>> values() {
return (Stream<ValueImpl<?>>) (Stream<?>) entries.stream().filter(ValueImpl.class::isInstance);
}
public synchronized void unload() {
closeConfig();
entries.stream().forEach(x -> {
if (x instanceof ValueImpl<?> value) value.unload();
});
values().forEach(ValueImpl::unload);
}
@GuardedBy("this")
@ -87,14 +90,15 @@ private synchronized boolean loadConfig() {
config.load();
entries.stream().forEach(x -> {
config.setComment(x.path, x.comment);
if (x instanceof ValueImpl<?> value) value.load(config);
});
var corrected = spec.correct(config, (action, entryPath, oldValue, newValue) -> {
// Ensure the config file matches the spec
var isNewFile = config.isEmpty();
entries.stream().forEach(x -> config.setComment(x.path, x.comment));
var corrected = isNewFile ? spec.correct(config) : spec.correct(config, (action, entryPath, oldValue, newValue) -> {
LOG.warn("Incorrect key {} was corrected from {} to {}", String.join(".", entryPath), oldValue, newValue);
});
// And then load the underlying entries.
values().forEach(x -> x.load(config));
onChange.onConfigChanged(config.getNioPath());
return corrected > 0;
@ -102,7 +106,7 @@ private synchronized boolean loadConfig() {
@Override
public Stream<ConfigFile.Entry> entries() {
return entries.children().map(x -> (ConfigFile.Entry) x);
return entries.stream().map(x -> (ConfigFile.Entry) x);
}
@Nullable
@ -148,7 +152,7 @@ private String takeComment(String suffix) {
public void push(String name) {
var path = getFullPath(name);
var splitPath = SPLITTER.split(path);
entries.setValue(splitPath, new GroupImpl(path, takeComment(), entries.getChild(splitPath)));
entries.setValue(splitPath, new GroupImpl(path, takeComment()));
super.push(name);
}
@ -230,16 +234,8 @@ public final String comment() {
}
private static final class GroupImpl extends Entry implements Group {
private final Trie<String, Entry> children;
private GroupImpl(String path, String comment, Trie<String, Entry> children) {
private GroupImpl(String path, String comment) {
super(path, comment);
this.children = children;
}
@Override
public Stream<ConfigFile.Entry> children() {
return children.children().map(x -> (ConfigFile.Entry) x);
}
}

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.platform;
import com.google.auto.service.AutoService;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.arguments.ArgumentType;
@ -27,6 +28,7 @@
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.fabricmc.fabric.api.registry.FuelRegistry;
import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType;
@ -49,6 +51,7 @@
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
@ -134,6 +137,17 @@ public boolean shouldLoadResource(JsonObject object) {
return ResourceConditions.objectMatchesConditions(object);
}
@Override
public void addRequiredModCondition(JsonObject object, String modId) {
var conditions = GsonHelper.getAsJsonArray(object, ResourceConditions.CONDITIONS_KEY, null);
if (conditions == null) {
conditions = new JsonArray();
object.add(ResourceConditions.CONDITIONS_KEY, conditions);
}
conditions.add(DefaultResourceConditions.allModsLoaded(modId).toJson());
}
@Override
public <T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFunction<BlockPos, BlockState, T> factory, Block block) {
return FabricBlockEntityTypeBuilder.create(factory::apply).addBlock(block).build();
@ -289,7 +303,7 @@ public ServerPlayer createFakePlayer(ServerLevel world, GameProfile name) {
public boolean hasToolUsage(ItemStack stack) {
var item = stack.getItem();
return item instanceof ShovelItem || stack.is(ItemTags.SHOVELS) ||
item instanceof HoeItem || stack.is(ItemTags.HOES);
item instanceof HoeItem || stack.is(ItemTags.HOES);
}
@Override
@ -300,8 +314,8 @@ public InteractionResult canAttackEntity(ServerPlayer player, Entity entity) {
@Override
public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos) {
return UseEntityCallback.EVENT.invoker().interact(player, entity.level, InteractionHand.MAIN_HAND, entity, new EntityHitResult(entity, hitPos)).consumesAction() ||
entity.interactAt(player, hitPos.subtract(entity.position()), InteractionHand.MAIN_HAND).consumesAction() ||
player.interactOn(entity, InteractionHand.MAIN_HAND).consumesAction();
entity.interactAt(player, hitPos.subtract(entity.position()), InteractionHand.MAIN_HAND).consumesAction() ||
player.interactOn(entity, InteractionHand.MAIN_HAND).consumesAction();
}
@Override
@ -323,9 +337,7 @@ private record RegistryWrapperImpl<T>(
) implements RegistryWrappers.RegistryWrapper<T> {
@Override
public int getId(T object) {
var id = registry.getId(object);
if (id == -1) throw new IllegalArgumentException(object + " was not registered in " + name);
return id;
return registry.getId(object);
}
@Override
@ -349,10 +361,13 @@ public T tryGet(ResourceLocation location) {
}
@Override
public T get(int id) {
var object = registry.byId(id);
if (object == null) throw new IllegalArgumentException(id + " was not registered in " + name);
return object;
public @Nullable T byId(int id) {
return registry.byId(id);
}
@Override
public int size() {
return registry.size();
}
@Override

View File

@ -31,6 +31,9 @@
],
"rei_client": [
"dan200.computercraft.client.integration.rei.REIComputerCraft"
],
"emi": [
"dan200.computercraft.client.integration.emi.EMIComputerCraft"
]
},
"mixins": [
@ -46,8 +49,8 @@
}
],
"depends": {
"fabricloader": ">=0.14.17",
"fabric-api": ">=0.80.0",
"fabricloader": ">=0.14.21",
"fabric-api": ">=0.86.0",
"minecraft": ">=1.19.4 <1.20"
},
"accessWidener": "computercraft.accesswidener"

View File

@ -127,6 +127,7 @@ dependencies {
compileOnly(libs.jetbrainsAnnotations)
annotationProcessorEverywhere(libs.autoService)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
libs.bundles.externalMods.forge.compile.get().map { compileOnly(fg.deobf(it)) }
libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }

View File

@ -81,6 +81,8 @@
"gui.computercraft.config.default_computer_settings.tooltip": "A comma separated list of default system settings to set on new computers.\nExample: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwill disable all autocompletion.",
"gui.computercraft.config.disable_lua51_features": "Disable Lua 5.1 features",
"gui.computercraft.config.disable_lua51_features.tooltip": "Set this to true to disable Lua 5.1 functions that will be removed in a future\nupdate. Useful for ensuring forward compatibility of your programs now.",
"gui.computercraft.config.disabled_generic_methods": "Disabled generic methods",
"gui.computercraft.config.disabled_generic_methods.tooltip": "A list of generic methods or method sources to disable. Generic methods are\nmethods added to a block/block entity when there is no explicit peripheral\nprovider. This includes inventory methods (i.e. inventory.getItemDetail,\ninventory.pushItems), and (if on Forge), the fluid_storage and energy_storage\nmethods.\nMethods in this list can either be a whole group of methods (computercraft:inventory)\nor a single method (computercraft:inventory#pushItems).\n",
"gui.computercraft.config.execution": "Execution",
"gui.computercraft.config.execution.computer_threads": "Computer threads",
"gui.computercraft.config.execution.computer_threads.tooltip": "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution.\nRange: > 1",
@ -204,6 +206,10 @@
"item.computercraft.printed_pages": "Printed Pages",
"item.computercraft.treasure_disk": "Floppy Disk",
"itemGroup.computercraft": "ComputerCraft",
"tag.item.computercraft.computer": "Computers",
"tag.item.computercraft.monitor": "Monitors",
"tag.item.computercraft.turtle": "Turtles",
"tag.item.computercraft.wired_modem": "Wired modems",
"tracking_field.computercraft.avg": "%s (avg)",
"tracking_field.computercraft.computer_tasks.name": "Tasks",
"tracking_field.computercraft.count": "%s (count)",

View File

@ -32,7 +32,7 @@ public ForgeConfigSpec spec() {
@Override
public Stream<Entry> entries() {
return entries.children();
return entries.stream();
}
@Nullable
@ -68,7 +68,7 @@ public void push(String name) {
@Override
public void pop() {
var path = new ArrayList<>(groupStack);
entries.setValue(path, new GroupImpl(path, entries.getChild(path)));
entries.setValue(path, new GroupImpl(path));
builder.pop();
super.pop();
@ -129,12 +129,10 @@ public ConfigFile build(ConfigListener onChange) {
private static final class GroupImpl implements ConfigFile.Group {
private final List<String> path;
private final Trie<String, ConfigFile.Entry> entries;
private @Nullable ForgeConfigSpec owner;
private GroupImpl(List<String> path, Trie<String, ConfigFile.Entry> entries) {
private GroupImpl(List<String> path) {
this.path = path;
this.entries = entries;
}
@Override
@ -148,11 +146,6 @@ public String comment() {
if (owner == null) throw new IllegalStateException("Config has not been built yet");
return owner.getLevelComment(path);
}
@Override
public Stream<Entry> children() {
return entries.children();
}
}
private static final class ValueImpl<T> implements ConfigFile.Value<T> {

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.platform;
import com.google.auto.service.AutoService;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.arguments.ArgumentType;
@ -32,6 +33,7 @@
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.*;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
@ -56,7 +58,9 @@
import net.minecraftforge.common.ToolActions;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.conditions.ICondition;
import net.minecraftforge.common.crafting.conditions.ModLoadedCondition;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.common.util.NonNullConsumer;
import net.minecraftforge.event.ForgeEventFactory;
@ -122,6 +126,17 @@ public boolean shouldLoadResource(JsonObject object) {
return ICondition.shouldRegisterEntry(object);
}
@Override
public void addRequiredModCondition(JsonObject object, String modId) {
var conditions = GsonHelper.getAsJsonArray(object, "forge:conditions", null);
if (conditions == null) {
conditions = new JsonArray();
object.add("forge:conditions", conditions);
}
conditions.add(CraftingHelper.serialize(new ModLoadedCondition(modId)));
}
@Override
public <T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFunction<BlockPos, BlockState, T> factory, Block block) {
return new BlockEntityType<>(factory::apply, Set.of(block), null);
@ -344,9 +359,7 @@ private record RegistryWrapperImpl<T>(
) implements RegistryWrappers.RegistryWrapper<T> {
@Override
public int getId(T object) {
var id = registry.getID(object);
if (id == -1) throw new IllegalStateException(object + " was not registered in " + name);
return id;
return registry.getID(object);
}
@Override
@ -370,10 +383,13 @@ public T tryGet(ResourceLocation location) {
}
@Override
public T get(int id) {
var object = registry.getValue(id);
if (object == null) throw new IllegalStateException(id + " was not registered in " + name);
return object;
public @Nullable T byId(int id) {
return registry.getValue(id);
}
@Override
public int size() {
return registry.getKeys().size();
}
@Override