Merge branch 'cc-tweaked:mc-1.19.x' into unicode-proto
This commit is contained in:
commit
0762afe554
|
@ -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
|
||||
|
|
22
README.md
22
README.md
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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):
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -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
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)"
|
||||
}
|
||||
|
|
|
@ -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 ],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "computercraft:tool",
|
||||
"item": "minecraft:netherite_pickaxe",
|
||||
"allowEnchantments": true,
|
||||
"consumeDurability": "when_enchanted"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "computercraft:tool",
|
||||
"item": "minecraft:wooden_pickaxe",
|
||||
"consumeDurability": "always"
|
||||
}
|
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_breaks_tool.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_breaks_tool.snbt
generated
Normal file
|
@ -0,0 +1,138 @@
|
|||
{
|
||||
DataVersion: 3218,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.dig_breaks_tool", LeftUpgrade: "cctest:wooden_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 58}}, On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_consume_durability.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.dig_consume_durability.snbt
generated
Normal file
|
@ -0,0 +1,138 @@
|
|||
{
|
||||
DataVersion: 3218,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.dig_consume_durability", LeftUpgrade: "cctest:wooden_pickaxe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
{
|
||||
DataVersion: 3337,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.dig_enchanted_consume_durability", LeftUpgrade: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:silk_touch", lvl: 1s}], RepairCost: 1}}, On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
40
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_below.snbt
generated
Normal file
40
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_below.snbt
generated
Normal 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"
|
||||
]
|
||||
}
|
40
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_distant.snbt
generated
Normal file
40
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_distant.snbt
generated
Normal 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"
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
"defaultRequire": 1
|
||||
},
|
||||
"client": [
|
||||
"BlockModelAccessor",
|
||||
"BlockRenderDispatcherMixin",
|
||||
"DebugScreenOverlayMixin",
|
||||
"GameRendererMixin",
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)) }
|
||||
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue