Merge branch 'mc-1.19.x' into mc-1.20.x

This commit is contained in:
Jonathan Coates 2023-06-20 08:59:06 +01:00
commit ebaf49508f
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
65 changed files with 1040 additions and 267 deletions

View File

@ -104,6 +104,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
tasks.processResources {
exclude("**/*.license")
exclude(".cache")
}
tasks.withType(AbstractArchiveTask::class.java).configureEach {

View File

@ -57,7 +57,7 @@ fabric-loom = "1.1.10"
forgeGradle = "5.1.+"
githubRelease = "2.2.12"
ideaExt = "1.1.6"
illuaminate = "0.1.0-24-gdb28902"
illuaminate = "0.1.0-28-ga7efd71"
librarian = "1.+"
minotaur = "2.+"
mixinGradle = "0.7.+"

View File

@ -24,6 +24,7 @@
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -104,7 +105,7 @@ public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser,
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
@Override
public final CompletableFuture<?> run(CachedOutput cache) {
public CompletableFuture<?> run(CachedOutput cache) {
var base = output.getOutputFolder().resolve("data");
Set<ResourceLocation> seen = new HashSet<>();
@ -127,7 +128,7 @@ public final CompletableFuture<?> run(CachedOutput cache) {
}
});
this.upgrades = upgrades;
this.upgrades = Collections.unmodifiableList(upgrades);
return Util.sequenceFailFast(futures);
}

View File

@ -25,9 +25,11 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
private static final int TEX_WIDTH = 254;
private static final int TEX_WIDTH = 278;
private static final int TEX_HEIGHT = 217;
private static final int FULL_TEX_SIZE = 512;
public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
super(container, player, title, BORDER);
@ -44,15 +46,16 @@ protected TerminalWidget createTerminal() {
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
var advanced = family == ComputerFamily.ADVANCED;
var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT);
graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
// Render selected slot
var slot = getMenu().getSelectedSlot();
if (slot >= 0) {
var slotX = slot % 4;
var slotY = slot / 4;
graphics.blit(texture,
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
0, 217, 24, 24
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
);
}

View File

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model.turtle;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexFormatElement;
import com.mojang.math.Transformation;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
* <p>
* This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
* handle flipping models upside down.
* <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 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;
private @Nullable TransformedQuads cache;
public ModelTransformer(Transformation transformation) {
this.transformation = transformation.getMatrix();
invert = transformation.getMatrix().determinant() < 0;
}
public List<BakedQuad> transform(List<BakedQuad> quads) {
if (quads.isEmpty()) return List.of();
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
// so it's not worth being smarter here.
var cache = this.cache;
if (cache != null && quads.equals(cache.original())) return cache.transformed();
List<BakedQuad> transformed = new ArrayList<>(quads.size());
for (var quad : quads) transformed.add(transformQuad(quad));
this.cache = new TransformedQuads(quads, transformed);
return transformed;
}
private BakedQuad transformQuad(BakedQuad quad) {
var inputData = quad.getVertices();
var outputData = new int[inputData.length];
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);
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
// Apply the matrix to our position
var inPosStart = inStart + POS_OFFSET;
var outPosStart = outStart + POS_OFFSET;
var x = Float.intBitsToFloat(inputData[inPosStart]);
var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
// Transform the position
var pos = new Vector4f(x, y, z, 1);
transformation.transformProject(pos);
outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
}
var direction = Direction.rotate(transformation, quad.getDirection());
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
}
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
}
private static int findOffset(VertexFormat format, VertexFormatElement element) {
var offset = 0;
for (var other : format.getElements()) {
if (other == element) return offset / Integer.BYTES;
offset += element.getByteSize();
}
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
}
}

View File

@ -10,6 +10,7 @@
import com.mojang.math.Transformation;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@ -30,6 +31,7 @@
import net.minecraft.util.RandomSource;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.List;
@ -146,16 +148,30 @@ private void renderModel(PoseStack transform, VertexConsumer renderer, int light
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
}
/**
* Render a block model.
*
* @param transform The current matrix stack.
* @param renderer The buffer to write to.
* @param lightmapCoord The current lightmap coordinate.
* @param overlayLight The overlay light.
* @param model The model to render.
* @param tints Tints for the quads, as an array of RGB values.
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
*/
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
random.setSeed(0);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
for (var facing : DirectionUtil.FACINGS) {
random.setSeed(42);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
}
random.setSeed(42);
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
}
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;
for (var bakedquad : quads) {
var tint = -1;
@ -167,7 +183,50 @@ private static void renderQuads(PoseStack transform, VertexConsumer buffer, int
var r = (float) (tint >> 16 & 255) / 255.0F;
var g = (float) (tint >> 8 & 255) / 255.0F;
var b = (float) (tint & 255) / 255.0F;
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
if (inverted) {
putBulkQuadInvert(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
} else {
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
}
}
}
/**
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} for
* when the matrix is inverted.
*
* @param buffer The buffer to draw to.
* @param pose The current matrix stack.
* @param quad The quad to draw.
* @param red The red tint of this quad.
* @param green The green tint of this quad.
* @param blue The blue tint of this quad.
* @param lightmapCoord The lightmap coordinate
* @param overlayLight The overlay light.
*/
private static void putBulkQuadInvert(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight) {
var matrix = pose.pose();
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
var dirNormal = quad.getDirection().getNormal();
var normal = matrix.transform(new Vector4f(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f)).normalize();
var vertices = quad.getVertices();
for (var vertex : ModelTransformer.ORDER) {
var i = vertex * ModelTransformer.STRIDE;
var x = Float.intBitsToFloat(vertices[i]);
var y = Float.intBitsToFloat(vertices[i + 1]);
var z = Float.intBitsToFloat(vertices[i + 2]);
var transformed = matrix.transform(new Vector4f(x, y, z, 1));
var u = Float.intBitsToFloat(vertices[i + 4]);
var v = Float.intBitsToFloat(vertices[i + 5]);
buffer.vertex(
transformed.x(), transformed.y(), transformed.z(),
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
normal.x(), normal.y(), normal.z()
);
}
}

View File

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.client;
import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import java.util.List;
import java.util.Optional;
/**
* A version of {@link DataProviders} which relies on client-side classes.
* <p>
* This is called from {@link DataProviders#add(DataProviders.GeneratorSink)}.
*/
public final class ClientDataProviders {
private ClientDataProviders() {
}
public static void add(DataProviders.GeneratorSink generator) {
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(new ResourceLocation("blocks"), List.of(
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
));
});
}
}

View File

@ -37,6 +37,14 @@
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
class BlockModelProvider {
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
private static final ModelTemplate COMPUTER_ON = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer_on")),
Optional.empty(),
TextureSlot.FRONT, TextureSlot.SIDE, TextureSlot.TOP, CURSOR
);
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
Optional.empty(),
@ -142,11 +150,18 @@ private static void registerPrinter(BlockModelGenerators generators) {
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
.with(createHorizontalFacingDispatch())
.with(createModelDispatch(ComputerBlock.STATE, state -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block).put(TextureSlot.FRONT, getBlockTexture(block, "_front" + state.getTexture())),
generators.modelOutput
)))
.with(createModelDispatch(ComputerBlock.STATE, state -> switch (state) {
case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block),
generators.modelOutput
);
case ON, BLINKING -> COMPUTER_ON.createWithSuffix(
block, "_" + state.getSerializedName(),
TextureMapping.orientableCube(block).put(CURSOR, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer" + state.getTexture())),
generators.modelOutput
);
}))
);
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
}

View File

@ -4,16 +4,20 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@ -37,11 +41,22 @@ public static void add(GeneratorSink generator) {
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
// Unfortunately we rely on some client-side classes in this code. We just load in the client side data provider
// and invoke that.
try {
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
.getMethod("add", GeneratorSink.class).invoke(null, generator);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
interface GeneratorSink {
public interface GeneratorSink {
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
void lootTable(List<SubProviderEntry> tables);
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);

View File

@ -7,7 +7,6 @@
import net.minecraft.core.NonNullList;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
/**
@ -16,24 +15,6 @@
public interface BasicContainer extends Container {
NonNullList<ItemStack> getContents();
@Override
default int getMaxStackSize() {
return 64;
}
@Override
default void startOpen(Player player) {
}
@Override
default void stopOpen(Player player) {
}
@Override
default boolean canPlaceItem(int slot, ItemStack stack) {
return true;
}
@Override
default int getContainerSize() {
return getContents().size();

View File

@ -190,7 +190,7 @@ public String getType() {
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
* These are:
* <p>
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, @code "flute"},
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, {@code "flute"},
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
*

View File

@ -4,6 +4,7 @@
package dan200.computercraft.shared.turtle.inventory;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
@ -29,12 +30,13 @@ public final class TurtleMenu extends AbstractComputerMenu {
public static final int PLAYER_START_Y = 134;
public static final int TURTLE_START_X = SIDEBAR_WIDTH + 175;
public static final int PLAYER_START_X = SIDEBAR_WIDTH + BORDER;
public static final int UPGRADE_START_X = SIDEBAR_WIDTH + 254;
private final ContainerData data;
private TurtleMenu(
int id, Predicate<Player> canUse, ComputerFamily family, @Nullable ServerComputer computer, @Nullable ComputerContainerData menuData,
Inventory playerInventory, Container inventory, ContainerData data
Inventory playerInventory, Container inventory, Container turtleUpgrades, ContainerData data
) {
super(ModRegistry.Menus.TURTLE.get(), id, canUse, family, computer, menuData);
this.data = data;
@ -58,19 +60,24 @@ private TurtleMenu(
for (var x = 0; x < 9; x++) {
addSlot(new Slot(playerInventory, x, PLAYER_START_X + x * 18, PLAYER_START_Y + 3 * 18 + 5));
}
// Turtle upgrades
addSlot(new UpgradeSlot(turtleUpgrades, TurtleSide.LEFT, 0, UPGRADE_START_X, PLAYER_START_Y + 1));
addSlot(new UpgradeSlot(turtleUpgrades, TurtleSide.RIGHT, 1, UPGRADE_START_X, PLAYER_START_Y + 1 + 18));
}
public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) {
return new TurtleMenu(
// Laziness in turtle.getOwner() is important here!
id, p -> turtle.getOwner().stillValid(p), turtle.getFamily(), turtle.getOwner().createServerComputer(), null,
player, turtle.getInventory(), (SingleContainerData) turtle::getSelectedSlot
player, turtle.getInventory(), new UpgradeContainer(turtle), (SingleContainerData) turtle::getSelectedSlot
);
}
public static TurtleMenu ofMenuData(int id, Inventory player, ComputerContainerData data) {
return new TurtleMenu(
id, x -> true, data.family(), null, data, player, new SimpleContainer(TurtleBlockEntity.INVENTORY_SIZE), new SimpleContainerData(1)
id, x -> true, data.family(), null, data,
player, new SimpleContainer(TurtleBlockEntity.INVENTORY_SIZE), new SimpleContainer(2), new SimpleContainerData(1)
);
}

View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.turtle.inventory;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.TurtleUpgrades;
import net.minecraft.core.NonNullList;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import java.util.Arrays;
import java.util.List;
/**
* A fake {@link Container} which exposes the {@linkplain ITurtleAccess#getUpgrade(TurtleSide) upgrades} a turtle has.
*
* @see TurtleMenu
* @see UpgradeSlot
*/
class UpgradeContainer implements Container {
private static final int SIZE = 2;
private final ITurtleAccess turtle;
private final List<ITurtleUpgrade> lastUpgrade = Arrays.asList(null, null);
private final NonNullList<ItemStack> lastStack = NonNullList.withSize(2, ItemStack.EMPTY);
UpgradeContainer(ITurtleAccess turtle) {
this.turtle = turtle;
}
private TurtleSide getSide(int slot) {
return switch (slot) {
case 0 -> TurtleSide.LEFT;
case 1 -> TurtleSide.RIGHT;
default -> throw new IllegalArgumentException("Invalid slot " + slot);
};
}
@Override
public ItemStack getItem(int slot) {
var upgrade = turtle.getUpgrade(getSide(slot));
// We don't want to return getCraftingItem directly here, as consumers may mutate the stack (they shouldn't!,
// but if they do it's a pain to track down). To avoid recreating the stack each tick, we maintain a simple
// cache.
if (upgrade == lastUpgrade.get(slot)) return lastStack.get(slot);
var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getCraftingItem().copy();
lastUpgrade.set(slot, upgrade);
lastStack.set(slot, stack);
return stack;
}
@Override
public void setItem(int slot, ItemStack itemStack) {
turtle.setUpgrade(getSide(slot), TurtleUpgrades.instance().get(itemStack));
}
@Override
public int getContainerSize() {
return SIZE;
}
@Override
public int getMaxStackSize() {
return 1;
}
@Override
public boolean isEmpty() {
for (var i = 0; i < SIZE; i++) {
if (!getItem(i).isEmpty()) return false;
}
return true;
}
@Override
public ItemStack removeItem(int slot, int count) {
return count <= 0 ? ItemStack.EMPTY : removeItemNoUpdate(slot);
}
@Override
public ItemStack removeItemNoUpdate(int slot) {
var current = getItem(slot);
setItem(slot, ItemStack.EMPTY);
return current;
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(Player player) {
return true;
}
@Override
public void clearContent() {
for (var i = 0; i < SIZE; i++) setItem(i, ItemStack.EMPTY);
}
}

View File

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.turtle.inventory;
import com.mojang.datafixers.util.Pair;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.TurtleUpgrades;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
/**
* A slot in the turtle UI which holds the turtle's current upgrade.
*
* @see TurtleMenu
*/
public class UpgradeSlot extends Slot {
public static final ResourceLocation LEFT_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/turtle_upgrade_left");
public static final ResourceLocation RIGHT_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/turtle_upgrade_right");
private final TurtleSide side;
public UpgradeSlot(Container container, TurtleSide side, int slot, int xPos, int yPos) {
super(container, slot, xPos, yPos);
this.side = side;
}
@Override
public boolean mayPlace(ItemStack stack) {
return TurtleUpgrades.instance().get(stack) != null;
}
@Override
public int getMaxStackSize() {
return 1;
}
@Nullable
@Override
public Pair<ResourceLocation, ResourceLocation> getNoItemIcon() {
return Pair.of(InventoryMenu.BLOCK_ATLAS, side == TurtleSide.LEFT ? LEFT_UPGRADE : RIGHT_UPGRADE);
}
}

View File

@ -115,5 +115,67 @@
"upgrade.minecraft.diamond_pickaxe.adjective": "Добывающая",
"upgrade.minecraft.diamond_shovel.adjective": "Копающая",
"upgrade.minecraft.diamond_sword.adjective": "Боевая",
"gui.computercraft.pocket_computer_overlay": "Карманный компьютер открыт. Чтобы закрыть, нажми ESC."
"gui.computercraft.pocket_computer_overlay": "Карманный компьютер открыт. Чтобы закрыть, нажми ESC.",
"gui.computercraft.config.command_require_creative.tooltip": "Требовать творческий режим и права оператора для взаимодействия с\nкомандными компьютерами. Это поведение по умолчанию для Командных блоков ванильной игры.",
"gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.",
"gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.\nОграничение: > 1",
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "Идеальный максимум времени, которое отведено компьютеру на выполнение задач, в миллисекундах.\nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.\nОграничение: > 1",
"gui.computercraft.config.execution.max_main_global_time.tooltip": "Максимум времени, которое может быть потрачено на выполнение задач за один тик, в \nмиллисекундах. \nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.\nОграничение: > 1",
"gui.computercraft.config.http.bandwidth.global_download": "Глобальный лимит на скачивание",
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "Количество байтов, которое можно скачать за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)\nОграничение: > 1",
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Количество байтов, которое можно загрузить за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)\nОграничение: > 1",
"tracking_field.computercraft.http_requests.name": "HTTP запросы",
"tracking_field.computercraft.turtle_ops.name": "Операции Черепашек",
"gui.computercraft.config.http.enabled.tooltip": "Включить API \"http\" на Компьютерах. Это также отключает программы \"pastebin\" и \"wget\", \nкоторые нужны многим пользователям. Рекомендуется оставить это включенным и использовать \nконфиг \"rules\" для более тонкой настройки.",
"gui.computercraft.config.http.max_websockets.tooltip": "Количество одновременно открытых веб-сокетов, которые может иметь компьютер. Установите на 0 для неограниченных веб-сокетов.\nОграничение: > 1",
"gui.computercraft.config.term_sizes": "Размер терминала",
"gui.computercraft.config.term_sizes.computer.height": "Высота терминала",
"gui.computercraft.config.term_sizes.monitor.height": "Максимальная высота монитора",
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Ограничение: 1 ~ 32",
"gui.computercraft.config.turtle.advanced_fuel_limit": "Лимит топлива Продвинутых Черепашек",
"gui.computercraft.config.turtle.need_fuel.tooltip": "Устанавливает, нуждаются ли Черепашки в топливе для передвижения.",
"gui.computercraft.terminal": "Компьютерный терминал",
"tracking_field.computercraft.computer_tasks.name": "Задачи",
"tracking_field.computercraft.server_tasks.name": "Серверные задачи",
"gui.computercraft.upload.no_response": "Перенос файлов",
"tracking_field.computercraft.avg": "%s (среднее)",
"gui.computercraft.config.command_require_creative": "Для использования командных компьютеров нужен творческий режим",
"gui.computercraft.config.computer_space_limit": "Лимит места на компьютерах (в байтах)",
"gui.computercraft.config.computer_space_limit.tooltip": "Лимит места на дисках компьютеров и черепашек, в байтах.",
"gui.computercraft.config.default_computer_settings": "Настройки Компьютера по умолчанию",
"gui.computercraft.config.disable_lua51_features": "Отключить функции Lua 5.1",
"gui.computercraft.config.disable_lua51_features.tooltip": "Поставьте, чтобы отключить функции из Lua 5.1, которые будут убраны в будущих\nобновлениях. Полезно для того, чтобы улучшить совместимость вперед ваших программ.",
"gui.computercraft.config.execution": "Выполнение",
"gui.computercraft.config.execution.computer_threads": "Потоки компьютера",
"gui.computercraft.config.execution.max_main_global_time": "Глобальный лимит времени на тик сервера",
"gui.computercraft.config.execution.tooltip": "Контролирует поведение выполнения задач компьютеров. Эта настройка преднезначается для \nтонкой настройки серверов, и в основном не должна быть изменена.",
"gui.computercraft.config.floppy_space_limit": "Лимит места на дискетах (байты)",
"gui.computercraft.config.floppy_space_limit.tooltip": "Лимит места для хранения информации на дискетах, в байтах.",
"gui.computercraft.config.http": "HTTP",
"gui.computercraft.config.http.bandwidth": "Пропускная способность",
"gui.computercraft.config.http.bandwidth.global_upload": "Глобальный лимит загрузки",
"gui.computercraft.config.http.bandwidth.tooltip": "Ограничивает пропускную способность, используемую компьютерами.",
"gui.computercraft.config.http.enabled": "Включить HTTP API",
"gui.computercraft.config.http.max_requests": "Максимум одновременных запросов",
"gui.computercraft.config.http.max_requests.tooltip": "Количество http-запросов, которые компьютер может сделать одновременно. Дополнительные запросы \nбудут поставлены в очередь, и отправлены когда существующие запросы будут выполнены. Установите на 0 для \nнеограниченных запросов.\nОграничение: > 0",
"gui.computercraft.config.http.max_websockets": "Максимум одновременных веб-сокетов",
"gui.computercraft.config.term_sizes.computer": "Компьютер",
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.term_sizes.computer.tooltip": "Размер терминала на компьютерах.",
"gui.computercraft.config.term_sizes.computer.width": "Ширина терминала",
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.term_sizes.monitor": "Монитор",
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Ограничение: 1 ~ 32",
"gui.computercraft.config.term_sizes.monitor.tooltip": "Максимальный размер мониторов (в блоках).",
"gui.computercraft.config.term_sizes.monitor.width": "Максимальная ширина мониторов",
"gui.computercraft.config.term_sizes.pocket_computer.height": "Высота терминала",
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.term_sizes.pocket_computer.width": "Ширина терминала",
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Ограничение: 1 ~ 255",
"gui.computercraft.config.turtle": "Черепашки",
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "Лимит топлива для Продвинутых Черепашек.\nОграничение: > 0",
"gui.computercraft.config.turtle.need_fuel": "Включить механику топлива",
"gui.computercraft.config.turtle.normal_fuel_limit": "Лимит топлива Черепашек",
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Лимит топлива для Черепашек.\nОграничение: > 0",
"gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками."
}

View File

@ -0,0 +1,39 @@
{
"parent": "minecraft:block/block",
"render_type": "cutout",
"textures": {
"particle": "#front"
},
"display": {
"firstperson_righthand": {
"rotation": [ 0, 135, 0 ],
"translation": [ 0, 0, 0 ],
"scale": [ 0.40, 0.40, 0.40 ]
}
},
"elements": [
{
"from": [ 0, 0, 0 ],
"to": [ 16, 16, 16 ],
"faces": {
"down": { "texture": "#top", "cullface": "down" },
"up": { "texture": "#top", "cullface": "up" },
"north": { "texture": "#front", "cullface": "north" },
"south": { "texture": "#side", "cullface": "south" },
"west": { "texture": "#side", "cullface": "west" },
"east": { "texture": "#side", "cullface": "east" }
}
},
{
"from": [ 0, 0, 0 ],
"to": [ 16, 16, 16 ],
"faces": {
"north": {
"texture": "#cursor",
"cullface": "north",
"forge_data": {"block_light": 15, "sky_light": 15}
}
}
}
]
}

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

View File

@ -1,6 +0,0 @@
{
"animation": {
"frametime": 8,
"frames": [ 0, 1 ]
}
}

View File

@ -1,6 +0,0 @@
{
"animation": {
"frametime": 8,
"frames": [ 0, 1 ]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1004 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0

View File

@ -443,28 +443,6 @@ public final Object getFreeSpace(String path) throws LuaException {
}
}
/**
* Searches for files matching a string with wildcards.
* <p>
* This string is formatted like a normal path string, but can include any
* number of wildcards ({@code *}) to look for files matching anything.
* For example, <code>rom/&#42;/command*</code> will look for any path starting with
* {@code command} inside any subdirectory of {@code /rom}.
*
* @param path The wildcard-qualified path to search for.
* @return A list of paths that match the search string.
* @throws LuaException If the path doesn't exist.
* @cc.since 1.6
*/
@LuaFunction
public final String[] find(String path) throws LuaException {
try (var ignored = environment.time(Metrics.FS_OPS)) {
return getFileSystem().find(path);
} catch (FileSystemException e) {
throw new LuaException(e.getMessage());
}
}
/**
* Returns the capacity of the drive the path is located on.
*

View File

@ -166,47 +166,6 @@ public synchronized String[] list(String path) throws FileSystemException {
return array;
}
private void findIn(String dir, List<String> matches, Pattern wildPattern) throws FileSystemException {
var list = list(dir);
for (var entry : list) {
var entryPath = dir.isEmpty() ? entry : dir + "/" + entry;
if (wildPattern.matcher(entryPath).matches()) {
matches.add(entryPath);
}
if (isDir(entryPath)) {
findIn(entryPath, matches, wildPattern);
}
}
}
public synchronized String[] find(String wildPath) throws FileSystemException {
// Match all the files on the system
wildPath = sanitizePath(wildPath, true);
// If we don't have a wildcard at all just check the file exists
var starIndex = wildPath.indexOf('*');
if (starIndex == -1) {
return exists(wildPath) ? new String[]{ wildPath } : new String[0];
}
// Find the all non-wildcarded directories. For instance foo/bar/baz* -> foo/bar
var prevDir = wildPath.substring(0, starIndex).lastIndexOf('/');
var startDir = prevDir == -1 ? "" : wildPath.substring(0, prevDir);
// If this isn't a directory then just abort
if (!isDir(startDir)) return new String[0];
// Scan as normal, starting from this directory
var wildPattern = Pattern.compile("^\\Q" + wildPath.replaceAll("\\*", "\\\\E[^\\\\/]*\\\\Q") + "\\E$");
List<String> matches = new ArrayList<>();
findIn(startDir, matches, wildPattern);
// Return matches
var array = new String[matches.size()];
matches.toArray(array);
return array;
}
public synchronized boolean exists(String path) throws FileSystemException {
path = sanitizePath(path);
var mount = getMount(path);
@ -400,21 +359,20 @@ private static String sanitizePath(String path) {
private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$");
// IMPORTANT: Both arrays are sorted by ASCII value.
private static final char[] specialChars = new char[]{ '"', '*', ':', '<', '>', '?', '|' };
private static final char[] specialCharsAllowWildcards = new char[]{ '"', ':', '<', '>', '|' };
public static String sanitizePath(String path, boolean allowWildcards) {
// Allow windowsy slashes
path = path.replace('\\', '/');
// Clean the path or illegal characters.
final var specialChars = new char[]{
'"', ':', '<', '>', '?', '|', // Sorted by ascii value (important)
};
var cleanName = new StringBuilder();
var allowedChars = allowWildcards ? specialCharsAllowWildcards : specialChars;
for (var i = 0; i < path.length(); i++) {
var c = path.charAt(i);
if (c >= 32 && Arrays.binarySearch(specialChars, c) < 0 && (allowWildcards || c != '*')) {
cleanName.append(c);
}
if (c >= 32 && Arrays.binarySearch(allowedChars, c) < 0) cleanName.append(c);
}
path = cleanName.toString();

View File

@ -133,6 +133,93 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
return {}
end
local function find_aux(path, parts, i, out)
local part = parts[i]
if not part then
-- If we're at the end of the pattern, ensure our path exists and append it.
if fs.exists(path) then out[#out + 1] = path end
elseif part.exact then
-- If we're an exact match, just recurse into this directory.
return find_aux(fs.combine(path, part.contents), parts, i + 1, out)
else
-- Otherwise we're a pattern. Check we're a directory, then recurse into each
-- matching file.
if not fs.isDir(path) then return end
local files = fs.list(path)
for j = 1, #files do
local file = files[j]
if file:find(part.contents) then find_aux(fs.combine(path, file), parts, i + 1, out) end
end
end
end
local find_escape = {
-- Escape standard Lua pattern characters
["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%",
["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-",
-- Aside from our wildcards.
["*"] = ".*",
["?"] = ".",
}
--[[- Searches for files matching a string with wildcards.
This string looks like a normal path string, but can include wildcards, which
can match multiple paths:
- "?" matches any single character in a file name.
- "*" matches any number of characters.
For example, `rom/*/command*` will look for any path starting with `command`
inside any subdirectory of `/rom`.
Note that these wildcards match a single segment of the path. For instance
`rom/*.lua` will include `rom/startup.lua` but _not_ include `rom/programs/list.lua`.
@tparam string path The wildcard-qualified path to search for.
@treturn { string... } A list of paths that match the search string.
@throws If the supplied path was invalid.
@since 1.6
@changed 1.106.0 Added support for the `?` wildcard.
@usage List all Markdown files in the help folder
fs.find("rom/help/*.md")
]]
function fs.find(pattern)
expect(1, pattern, "string")
pattern = fs.combine(pattern) -- Normalise the path, removing ".."s.
-- If the pattern is trying to search outside the computer root, just abort.
-- This will fail later on anyway.
if pattern == ".." or pattern:sub(1, 3) == "../" then
error("/" .. pattern .. ": Invalid Path", 2)
end
-- If we've no wildcards, just check the file exists.
if not pattern:find("[*?]") then
if fs.exists(pattern) then return { pattern } else return {} end
end
local parts = {}
for part in pattern:gmatch("[^/]+") do
if part:find("[*?]") then
parts[#parts + 1] = {
exact = false,
contents = "^" .. part:gsub(".", find_escape) .. "$",
}
else
parts[#parts + 1] = { exact = true, contents = part }
end
end
local out = {}
find_aux("", parts, 1, out)
return out
end
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and

View File

@ -424,12 +424,31 @@ do
if map[c] == nil then map[c] = hexify(c) end
end
serializeJSONString = function(s)
return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
serializeJSONString = function(s, options)
if options and options.unicode_strings and s:find("[\x80-\xff]") then
local retval = '"'
for _, code in utf8.codes(s) do
if code > 0xFFFF then
-- Encode the codepoint as a UTF-16 surrogate pair
code = code - 0x10000
local high, low = bit32.extract(code, 10, 10) + 0xD800, bit32.extract(code, 0, 10) + 0xDC00
retval = retval .. ("\\u%04X\\u%04X"):format(high, low)
elseif code <= 0x5C and map[string.char(code)] then -- 0x5C = `\`, don't run `string.char` if we don't need to
retval = retval .. map[string.char(code)]
elseif code < 0x20 or code >= 0x7F then
retval = retval .. ("\\u%04X"):format(code)
else
retval = retval .. string.char(code)
end
end
return retval .. '"'
else
return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
end
end
end
local function serializeJSONImpl(t, tTracking, bNBTStyle)
local function serializeJSONImpl(t, tTracking, options)
local sType = type(t)
if t == empty_json_array then return "[]"
elseif t == json_null then return "null"
@ -450,13 +469,14 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
local nObjectSize = 0
local nArraySize = 0
local largestArrayIndex = 0
local bNBTStyle = options and options.nbt_style
for k, v in pairs(t) do
if type(k) == "string" then
local sEntry
if bNBTStyle then
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, options)
else
sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
sEntry = serializeJSONString(k, options) .. ":" .. serializeJSONImpl(v, tTracking, options)
end
if nObjectSize == 0 then
sObjectResult = sObjectResult .. sEntry
@ -473,7 +493,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
if t[k] == nil then --if the array is nil at index k the value is "null" as to keep the unused indexes in between used ones.
sEntry = "null"
else -- if the array index does not point to a nil we serialise it's content.
sEntry = serializeJSONImpl(t[k], tTracking, bNBTStyle)
sEntry = serializeJSONImpl(t[k], tTracking, options)
end
if nArraySize == 0 then
sArrayResult = sArrayResult .. sEntry
@ -492,7 +512,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
end
elseif sType == "string" then
return serializeJSONString(t)
return serializeJSONString(t, options)
elseif sType == "number" or sType == "boolean" then
return tostring(t)
@ -813,32 +833,57 @@ end
unserialise = unserialize -- GB version
--- Returns a JSON representation of the given data.
--
-- This function attempts to guess whether a table is a JSON array or
-- object. However, empty tables are assumed to be empty objects - use
-- @{textutils.empty_json_array} to mark an empty array.
--
-- This is largely intended for interacting with various functions from the
-- @{commands} API, though may also be used in making @{http} requests.
--
-- @param t The value to serialise. Like @{textutils.serialise}, this should not
-- contain recursive tables or functions.
-- @tparam[opt] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
-- instead of standard JSON.
-- @treturn string The JSON representation of the input.
-- @throws If the object contains a value which cannot be
-- serialised. This includes functions and tables which appear multiple
-- times.
-- @usage textutils.serialiseJSON({ values = { 1, "2", true } })
-- @since 1.7
-- @see textutils.json_null Use to serialise a JSON `null` value.
-- @see textutils.empty_json_array Use to serialise a JSON empty array.
function serializeJSON(t, bNBTStyle)
--[[- Returns a JSON representation of the given data.
This function attempts to guess whether a table is a JSON array or
object. However, empty tables are assumed to be empty objects - use
@{textutils.empty_json_array} to mark an empty array.
This is largely intended for interacting with various functions from the
@{commands} API, though may also be used in making @{http} requests.
@param[1] t The value to serialise. Like @{textutils.serialise}, this should not
contain recursive tables or functions.
@tparam[1,opt] { nbt_style? = boolean, unicode_strings? = boolean } options Options for serialisation.
- `nbt_style`: Whether to produce NBT-style JSON (non-quoted keys) instead of standard JSON.
- `unicode_strings`: Whether to treat strings as containing UTF-8 characters instead of
using the default 8-bit character set.
@param[2] t The value to serialise. Like @{textutils.serialise}, this should not
contain recursive tables or functions.
@tparam[2] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
instead of standard JSON.
@treturn string The JSON representation of the input.
@throws If the object contains a value which cannot be serialised. This includes
functions and tables which appear multiple times.
@usage Serialise a simple object
textutils.serialiseJSON({ values = { 1, "2", true } })
@usage Serialise an object to a NBT-style string
textutils.serialiseJSON({ values = { 1, "2", true } }, { nbt_style = true })
@since 1.7
@changed 1.106.0 Added `options` overload and `unicode_strings` option.
@see textutils.json_null Use to serialise a JSON `null` value.
@see textutils.empty_json_array Use to serialise a JSON empty array.
]]
function serializeJSON(t, options)
expect(1, t, "table", "string", "number", "boolean")
expect(2, bNBTStyle, "boolean", "nil")
expect(2, options, "table", "boolean", "nil")
if type(options) == "boolean" then
options = { nbt_style = options }
elseif type(options) == "table" then
field(options, "nbt_style", "boolean", "nil")
field(options, "unicode_strings", "boolean", "nil")
end
local tTracking = {}
return serializeJSONImpl(t, tTracking, bNBTStyle or false)
return serializeJSONImpl(t, tTracking, options)
end
serialiseJSON = serializeJSON -- GB version

View File

@ -7,7 +7,7 @@ # New features in CC: Tweaked 1.105.0
* 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 HTTP files.
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).

View File

@ -7,7 +7,7 @@
* 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 HTTP files.
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).

View File

@ -2,7 +2,7 @@
--
-- SPDX-License-Identifier: MPL-2.0
--- Read and draw nbt ("Nitrogen Fingers Text") images.
--- Read and draw nft ("Nitrogen Fingers Text") images.
--
-- nft ("Nitrogen Fingers Text") is a file format for drawing basic images.
-- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured

View File

@ -87,6 +87,53 @@ describe("The fs library", function()
end)
end)
describe("fs.find", function()
it("fails on invalid paths", function()
expect.error(fs.find, ".."):eq("/..: Invalid Path")
expect.error(fs.find, "../foo/bar"):eq("/../foo/bar: Invalid Path")
end)
it("returns nothing on non-existent files", function()
expect(fs.find("no/such/file")):same {}
expect(fs.find("no/such/*")):same {}
expect(fs.find("no/*/file")):same {}
end)
it("returns a single file", function()
expect(fs.find("rom")):same { "rom" }
expect(fs.find("rom/motd.txt")):same { "rom/motd.txt" }
end)
it("supports the '*' wildcard", function()
expect(fs.find("rom/*")):same {
"rom/apis",
"rom/autorun",
"rom/help",
"rom/modules",
"rom/motd.txt",
"rom/programs",
"rom/startup.lua",
}
expect(fs.find("rom/*/command")):same {
"rom/apis/command",
"rom/modules/command",
"rom/programs/command",
}
expect(fs.find("rom/*/lua*")):same {
"rom/help/lua.txt",
"rom/programs/lua.lua",
}
end)
it("supports the '?' wildcard", function()
expect(fs.find("rom/programs/mo??.lua")):same {
"rom/programs/motd.lua",
"rom/programs/move.lua",
}
end)
end)
describe("fs.combine", function()
it("removes . and ..", function()
expect(fs.combine("./a/b")):eq("a/b")

View File

@ -143,8 +143,10 @@ describe("The textutils library", function()
textutils.serialiseJSON({})
textutils.serialiseJSON(false)
textutils.serialiseJSON("", true)
textutils.serializeJSON("", {})
textutils.serializeJSON(0, { nbt_style = true, unicode_strings = true })
expect.error(textutils.serialiseJSON, nil):eq("bad argument #1 (table, string, number or boolean expected, got nil)")
expect.error(textutils.serialiseJSON, "", 1):eq("bad argument #2 (boolean expected, got number)")
expect.error(textutils.serialiseJSON, "", 1):eq("bad argument #2 (table or boolean expected, got number)")
end)
it("serializes empty arrays", function()
@ -174,6 +176,17 @@ describe("The textutils library", function()
expect(textutils.serializeJSON({ 5, "test", nil, nil, textutils.json_null })):eq('[5,"test",null,null,null]')
expect(textutils.serializeJSON({ nil, nil, nil, nil, "text" })):eq('[null,null,null,null,"text"]')
end)
it("serializes NBT style", function()
expect(textutils.serializeJSON({ test = 2 }, { nbt_style = true })):eq('{test:2}')
expect(textutils.serializeJSON({ test = 2 }, true)):eq('{test:2}') -- old style
end)
it("serializes Unicode strings", function()
expect(textutils.serializeJSON("\u{3053}\u{3093}\u{306B}\u{3061}\u{306F}", { unicode_strings = true })):eq([["\u3053\u3093\u306B\u3061\u306F"]])
expect(textutils.serializeJSON("\u{1f62f}", { unicode_strings = true })):eq([["\uD83D\uDE2F"]])
expect(textutils.serializeJSON("\\\"\u{00ff}\n\"", { unicode_strings = true })):eq('"\\\\\\"\\u00FF\\n\\""')
end)
end)
describe("textutils.unserializeJSON", function()

View File

@ -122,7 +122,7 @@ loom {
register("data") {
configName = "Datagen"
server()
client()
runDir("run/dataGen")
property("cct.pretty-json")
@ -168,7 +168,6 @@ tasks.processResources {
filesMatching("fabric.mod.json") {
expand(mapOf("version" to modVersion))
}
exclude(".cache")
}
tasks.jar {

View File

@ -4,6 +4,7 @@
package dan200.computercraft.client;
import dan200.computercraft.client.model.EmissiveComputerModel;
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
@ -35,9 +36,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));
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());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.MONITOR_NORMAL.get(), RenderType.cutout());
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.MONITOR_ADVANCED.get(), RenderType.cutout());

View File

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

View File

@ -4,84 +4,32 @@
package dan200.computercraft.client.model;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexFormatElement;
import com.mojang.math.Transformation;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
*
* @see ModelTransformer
*/
public class TransformedBakedModel extends CustomBakedModel {
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
private final Matrix4f transformation;
private @Nullable TransformedQuads cache;
private final ModelTransformer transformation;
public TransformedBakedModel(BakedModel model, Transformation transformation) {
super(model);
this.transformation = transformation.getMatrix();
this.transformation = new ModelTransformer(transformation);
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
var cache = this.cache;
var quads = wrapped.getQuads(blockState, face, rand);
if (quads.isEmpty()) return List.of();
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
// so it's not worth being smarter here.
if (cache != null && quads.equals(cache.original())) return cache.transformed();
List<BakedQuad> transformed = new ArrayList<>(quads.size());
for (var quad : quads) transformed.add(transformQuad(quad));
this.cache = new TransformedQuads(quads, transformed);
return transformed;
}
private BakedQuad transformQuad(BakedQuad quad) {
var vertexData = quad.getVertices().clone();
for (var i = 0; i < 4; i++) {
// Apply the matrix to our position
var start = STRIDE * i + POS_OFFSET;
var x = Float.intBitsToFloat(vertexData[start]);
var y = Float.intBitsToFloat(vertexData[start + 1]);
var z = Float.intBitsToFloat(vertexData[start + 2]);
// Transform the position
var pos = new Vector4f(x, y, z, 1);
transformation.transformProject(pos);
vertexData[start] = Float.floatToRawIntBits(pos.x());
vertexData[start + 1] = Float.floatToRawIntBits(pos.y());
vertexData[start + 2] = Float.floatToRawIntBits(pos.z());
}
return new BakedQuad(vertexData, quad.getTintIndex(), quad.getDirection(), quad.getSprite(), quad.isShade());
}
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
}
private static int findOffset(VertexFormat format, VertexFormatElement element) {
var offset = 0;
for (var other : format.getElements()) {
if (other == element) return offset / Integer.BYTES;
offset += element.getByteSize();
}
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
return transformation.transform(wrapped.getQuads(blockState, face, rand));
}
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -0,0 +1,6 @@
{
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
]
}

View File

@ -4,21 +4,25 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
@ -43,6 +47,27 @@ public <T extends DataProvider> T add(DataProvider.Factory<T> factory) {
return generator.addProvider(factory);
}
@Override
public <T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output) {
generator.addProvider((FabricDataOutput out) -> {
var ourType = switch (type) {
case SERVER_DATA -> PackOutput.Target.DATA_PACK;
case CLIENT_RESOURCES -> PackOutput.Target.RESOURCE_PACK;
};
return new FabricCodecDataProvider<T>(out, ourType, directory, codec) {
@Override
public String getName() {
return name;
}
@Override
protected void configure(BiConsumer<ResourceLocation, T> provider) {
output.accept(provider);
}
};
});
}
@Override
public void lootTable(List<LootTableProvider.SubProviderEntry> tables) {
for (var table : tables) {

View File

@ -5,6 +5,7 @@
package dan200.computercraft.client.model;
import com.mojang.math.Transformation;
import dan200.computercraft.client.model.turtle.ModelTransformer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
@ -12,7 +13,6 @@
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.BakedModelWrapper;
import net.minecraftforge.client.model.QuadTransformers;
import net.minecraftforge.client.model.data.ModelData;
import javax.annotation.Nullable;
@ -20,16 +20,15 @@
/**
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
*
* @see ModelTransformer
*/
public class TransformedBakedModel extends BakedModelWrapper<BakedModel> {
private final Transformation transformation;
private final boolean invert;
private @Nullable TransformedQuads cache;
private final ModelTransformer transformation;
public TransformedBakedModel(BakedModel model, Transformation transformation) {
super(model);
this.transformation = transformation;
invert = transformation.getNormalMatrix().determinant() < 0;
this.transformation = new ModelTransformer(transformation);
}
@Override
@ -39,19 +38,6 @@ public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) {
var cache = this.cache;
var quads = originalModel.getQuads(state, side, rand, extraData, renderType);
if (quads.isEmpty()) return List.of();
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
// so it's not worth being smarter here.
if (cache != null && quads.equals(cache.original())) return cache.transformed();
var transformed = QuadTransformers.applying(transformation).process(quads);
this.cache = new TransformedQuads(quads, transformed);
return transformed;
}
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
return transformation.transform(originalModel.getQuads(state, side, rand, extraData, renderType));
}
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_advanced_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_advanced_front",
"side": "computercraft:block/computer_advanced_side",
"top": "computercraft:block/computer_advanced_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_command_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_command_front",
"side": "computercraft:block/computer_command_side",
"top": "computercraft:block/computer_command_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_blink",
"cursor": "computercraft:block/computer_blink",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -1,7 +1,8 @@
{
"parent": "minecraft:block/orientable",
"parent": "computercraft:block/computer_on",
"textures": {
"front": "computercraft:block/computer_normal_front_on",
"cursor": "computercraft:block/computer_on",
"front": "computercraft:block/computer_normal_front",
"side": "computercraft:block/computer_normal_side",
"top": "computercraft:block/computer_normal_top"
}

View File

@ -0,0 +1,6 @@
{
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
]
}

View File

@ -4,16 +4,16 @@
{
"condition": "minecraft:any_of",
"terms": [
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/stronghold_library"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/woodland_mansion"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/stronghold_corridor"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/abandoned_mineshaft"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/simple_dungeon"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/village/village_cartographer"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/igloo_chest"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/jungle_temple"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/desert_pyramid"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/stronghold_crossing"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/stronghold_library"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/woodland_mansion"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/stronghold_corridor"},
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/abandoned_mineshaft"}
{"condition": "forge:loot_table_id", "loot_table_id": "minecraft:chests/stronghold_crossing"}
]
}
],

View File

@ -4,6 +4,8 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.HolderLookup;
@ -14,18 +16,24 @@
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.ItemTagsProvider;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.common.data.BlockTagsProvider;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.common.data.JsonCodecProvider;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
@ -48,6 +56,15 @@ public <T extends DataProvider> T add(DataProvider.Factory<T> factory) {
return generator.addProvider(factory);
}
@Override
public <T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output) {
generator.addProvider(out -> {
Map<ResourceLocation, T> map = new HashMap<>();
output.accept(map::put);
return new JsonCodecProvider<>(out, existingFiles, ComputerCraftAPI.MOD_ID, JsonOps.INSTANCE, type, directory, codec, map);
});
}
@Override
public void lootTable(List<LootTableProvider.SubProviderEntry> tables) {
add(out -> new LootTableProvider(out, Set.of(), tables));