1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-02 22:53:15 +00:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Jonathan Coates
f561572509 Bump CC:T to 1.102.2 2023-01-14 20:07:01 +00:00
Jonathan Coates
edb21f33be Mount drives on the main thread
When the peripheral is attached, we add the computer to the map and
queue the actual disk to be mounted next tick. This avoids the
thread-safety issues with mutating the item (and creating disk ids) that
might be caused by doing it on the computer thread.

The mount is now also managed separately to the MediaStack, as that was
meant to be an immutable snapshot of the item!

Fixes #1282
2023-01-14 17:51:49 +00:00
Jonathan Coates
02b68b259e Correctly track coverage for startup.lua too 2023-01-12 22:26:39 +00:00
Jonathan Coates
28a55349a9 Move coverage to the Java side
While slightly irritating (requires Cobalt magic), it's much, much
faster.
2023-01-12 21:02:33 +00:00
Jonathan Coates
2457a31728 Fix printouts crashing in item frames 2023-01-09 18:28:44 +00:00
19 changed files with 586 additions and 242 deletions

View File

@@ -6,7 +6,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=true
modVersion=1.102.1
modVersion=1.102.2
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.19.3

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.shared.peripheral.diskdrive;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
@@ -47,11 +48,14 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
private final NonNullList<ItemStack> inventory = NonNullList.withSize(1, ItemStack.EMPTY);
private MediaStack media = MediaStack.EMPTY;
private @Nullable Mount mount;
private boolean recordPlaying = false;
// In order to avoid main-thread calls in the peripheral, we set flags to mark which operation should be performed,
// then read them when ticking.
private final AtomicReference<RecordCommand> recordQueued = new AtomicReference<>(null);
private final AtomicBoolean ejectQueued = new AtomicBoolean(false);
private final AtomicBoolean mountQueued = new AtomicBoolean(false);
public DiskDriveBlockEntity(BlockEntityType<DiskDriveBlockEntity> type, BlockPos pos, BlockState state) {
super(type, pos, state);
@@ -109,6 +113,12 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
}
}
}
if (mountQueued.get()) {
synchronized (this) {
mountAll();
}
}
}
@Override
@@ -124,9 +134,9 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
private void updateItem() {
var newDisk = getDiskStack();
if (ItemStack.isSame(newDisk, media.stack)) return;
if (ItemStack.isSameItemSameTags(newDisk, media.stack)) return;
var media = new MediaStack(newDisk.copy());
var media = MediaStack.of(newDisk);
if (newDisk.isEmpty()) {
updateBlockState(DiskDriveState.EMPTY);
@@ -146,12 +156,10 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
recordPlaying = false;
}
mount = null;
this.media = media;
// Mount new disk
if (!this.media.stack.isEmpty()) {
for (var computer : computers.entrySet()) mountDisk(computer.getKey(), computer.getValue(), this.media);
}
mountAll();
}
}
@@ -163,11 +171,30 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
return media;
}
/**
* Set the current disk stack, mounting/unmounting if needed.
*
* @param stack The new disk stack.
*/
void setDiskStack(ItemStack stack) {
setItem(0, stack);
setChanged();
}
/**
* Update the current disk stack, assuming the underlying item does not change. Unlike
* {@link #setDiskStack(ItemStack)} this will not change any mounts.
*
* @param stack The new disk stack.
*/
void updateDiskStack(ItemStack stack) {
setItem(0, stack);
if (!ItemStack.isSameItemSameTags(stack, media.stack)) {
media = MediaStack.of(stack);
super.setChanged();
}
}
@Nullable
String getDiskMountPath(IComputerAccess computer) {
synchronized (this) {
@@ -176,15 +203,21 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
}
}
void mount(IComputerAccess computer) {
/**
* Attach a computer to this disk drive. This sets up the {@link MountInfo} map and flags us to mount next tick. We
* don't mount here, as that might require mutating the current stack.
*
* @param computer The computer to attach.
*/
void attach(IComputerAccess computer) {
synchronized (this) {
var info = new MountInfo();
computers.put(computer, info);
mountDisk(computer, info, media);
mountQueued.set(true);
}
}
void unmount(IComputerAccess computer) {
void detach(IComputerAccess computer) {
synchronized (this) {
unmountDisk(computer, computers.remove(computer));
}
@@ -202,10 +235,35 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
ejectQueued.set(true);
}
/**
* Add our mount to all computers.
*/
@GuardedBy("this")
private void mountDisk(IComputerAccess computer, MountInfo info, MediaStack disk) {
var mount = disk.getMount((ServerLevel) getLevel());
if (mount != null) {
private void mountAll() {
doMountAll();
mountQueued.set(false);
}
/**
* The worker for {@link #mountAll()}. This is responsible for creating the mount and placing it on all computers.
*/
@GuardedBy("this")
private void doMountAll() {
if (computers.isEmpty() || media.media == null) return;
if (mount == null) {
var stack = getDiskStack();
mount = media.media.createDataMount(stack, (ServerLevel) level);
setDiskStack(stack);
}
if (mount == null) return;
for (var entry : computers.entrySet()) {
var computer = entry.getKey();
var info = entry.getValue();
if (info.mountPath != null) continue;
if (mount instanceof WritableMount writable) {
// Try mounting at the lowest numbered "disk" name we can
var n = 1;
@@ -221,11 +279,9 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
n++;
}
}
} else {
info.mountPath = null;
}
computer.queueEvent("disk", computer.getAttachmentName());
computer.queueEvent("disk", computer.getAttachmentName());
}
}
private static void unmountDisk(IComputerAccess computer, MountInfo info) {

View File

@@ -84,11 +84,12 @@ public class DiskDrivePeripheral implements IPeripheral {
var media = diskDrive.getMedia();
if (media.media == null) return;
var stack = media.stack.copy();
// We're on the main thread so the stack and media should be in sync.
var stack = diskDrive.getDiskStack();
if (!media.media.setLabel(stack, label.map(StringUtil::normaliseLabel).orElse(null))) {
throw new LuaException("Disk label cannot be changed");
}
diskDrive.setDiskStack(stack);
diskDrive.updateDiskStack(stack);
}
/**
@@ -178,12 +179,12 @@ public class DiskDrivePeripheral implements IPeripheral {
@Override
public void attach(IComputerAccess computer) {
diskDrive.mount(computer);
diskDrive.attach(computer);
}
@Override
public void detach(IComputerAccess computer) {
diskDrive.unmount(computer);
diskDrive.detach(computer);
}
@Override

View File

@@ -5,10 +5,8 @@
*/
package dan200.computercraft.shared.peripheral.diskdrive;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.impl.MediaProviders;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.ItemStack;
@@ -17,18 +15,22 @@ import javax.annotation.Nullable;
/**
* An immutable snapshot of the current disk. This allows us to read the stack in a thread-safe manner.
*/
class MediaStack {
static final MediaStack EMPTY = new MediaStack(ItemStack.EMPTY);
final class MediaStack {
static final MediaStack EMPTY = new MediaStack(ItemStack.EMPTY, null);
final ItemStack stack;
final @Nullable IMedia media;
@Nullable
private Mount mount;
MediaStack(ItemStack stack) {
private MediaStack(ItemStack stack, @Nullable IMedia media) {
this.stack = stack;
media = MediaProviders.get(stack);
this.media = media;
}
public static MediaStack of(ItemStack stack) {
if (stack.isEmpty()) return EMPTY;
var freshStack = stack.copy();
return new MediaStack(freshStack, MediaProviders.get(freshStack));
}
@Nullable
@@ -40,12 +42,4 @@ class MediaStack {
String getAudioTitle() {
return media != null ? media.getAudioTitle(stack) : null;
}
@Nullable
public Mount getMount(ServerLevel level) {
if (media == null) return null;
if (mount == null) mount = media.createDataMount(stack, level);
return mount;
}
}

View File

@@ -8,6 +8,7 @@ package dan200.computercraft.gametest.core;
import com.mojang.brigadier.CommandDispatcher;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.mixin.gametest.TestCommandAccessor;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.gametest.framework.GameTestRegistry;
@@ -81,6 +82,27 @@ class CCTestCommand {
player.getLevel().addFreshEntity(armorStand);
return 0;
}))
.then(literal("give-computer").executes(context -> {
var player = context.getSource().getPlayerOrException();
var pos = StructureUtils.findNearestStructureBlock(player.blockPosition(), 15, player.getLevel());
if (pos == null) return error(context.getSource(), "No nearby test");
var structureBlock = (StructureBlockEntity) player.getLevel().getBlockEntity(pos);
if (structureBlock == null) return error(context.getSource(), "No nearby structure block");
var info = GameTestRegistry.getTestFunction(structureBlock.getStructurePath());
var item = ModRegistry.Items.COMPUTER_ADVANCED.get().create(1, info.getTestName());
if (!player.getInventory().add(item)) {
var itemEntity = player.drop(item, false);
if (itemEntity != null) {
itemEntity.setNoPickUpDelay();
itemEntity.setOwner(player.getUUID());
}
}
return 1;
}))
);
}

View File

@@ -8,6 +8,7 @@ package dan200.computercraft.gametest
import dan200.computercraft.core.apis.FSAPI
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.media.items.DiskItem
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveState
import dan200.computercraft.test.core.assertArrayEquals
@@ -45,10 +46,14 @@ class Disk_Drive_Test {
thenWaitUntil { helper.assertItemEntityPresent(Items.MUSIC_DISC_13, stackAt, 0.0) }
}
/**
* A mount is initially attached, and then removed when the disk is ejected.
*/
@GameTest
fun Adds_removes_mount(helper: GameTestHelper) = helper.sequence {
thenIdle(2)
thenOnComputer {
thenOnComputer { } // Wait for the computer to start up
thenIdle(2) // Let the disk drive tick once to create the mount
thenOnComputer { // Then actually assert things!
getApi<FSAPI>().getDrive("disk").assertArrayEquals("right")
callPeripheral("right", "ejectDisk")
}
@@ -56,6 +61,18 @@ class Disk_Drive_Test {
thenOnComputer { assertEquals(null, getApi<FSAPI>().getDrive("disk")) }
}
/**
* When creating a new mount, the item is with a new disk ID.
*/
@GameTest
fun Creates_disk_id(helper: GameTestHelper) = helper.sequence {
val drivePos = BlockPos(2, 2, 2)
thenWaitUntil {
val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get())
if (DiskItem.getDiskID(drive.getItem(0)) == -1) helper.fail("Disk has no item", drivePos)
}
}
/**
* Check comparators can read the contents of the disk drive
*/

View File

@@ -63,7 +63,7 @@ class Monitor_Test {
}
/**
* Test
* Test monitors render correctly
*/
@GameTestGenerator
fun Render_monitor_tests(): List<TestFunction> {
@@ -72,7 +72,7 @@ class Monitor_Test {
fun addTest(label: String, renderer: MonitorRenderer, time: Long = Times.NOON, tag: String = TestTags.CLIENT) {
if (!TestTags.isEnabled(tag)) return
val className = Monitor_Test::class.java.simpleName.lowercase()
val className = this::class.java.simpleName.lowercase()
val testName = "$className.render_monitor"
tests.add(

View File

@@ -0,0 +1,53 @@
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.*
import net.minecraft.gametest.framework.GameTestGenerator
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.TestFunction
class Printout_Test {
/**
* Test printouts render correctly
*/
@GameTestGenerator
fun Render_in_frame(): List<TestFunction> {
val tests = mutableListOf<TestFunction>()
fun addTest(label: String, time: Long = Times.NOON, tag: String = TestTags.CLIENT) {
if (!TestTags.isEnabled(tag)) return
val className = this::class.java.simpleName.lowercase()
val testName = "$className.render_in_frame"
tests.add(
TestFunction(
"$testName.$label",
"$testName.$label",
testName,
Timeouts.DEFAULT,
0,
true,
) { renderPrintout(it, time) },
)
}
addTest("noon", Times.NOON)
addTest("midnight", Times.MIDNIGHT)
addTest("sodium", tag = "sodium")
addTest("iris_noon", Times.NOON, tag = "iris")
addTest("iris_midnight", Times.MIDNIGHT, tag = "iris")
return tests
}
private fun renderPrintout(helper: GameTestHelper, time: Long) = helper.sequence {
thenExecute {
helper.level.dayTime = time
helper.positionAtArmorStand()
}
thenScreenshot()
}
}

View File

@@ -80,6 +80,7 @@ object TestHooks {
Monitor_Test::class.java,
Pocket_Computer_Test::class.java,
Printer_Test::class.java,
Printout_Test::class.java,
Recipe_Test::class.java,
Turtle_Test::class.java,
)

View File

@@ -0,0 +1,138 @@
{
DataVersion: 3218,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:computer_advanced{facing:north,state:on}", nbt: {ComputerId: 1, Label: "disk_drive_test.creates_disk_id", On: 1b, id: "computercraft:computer_advanced"}},
{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:disk_drive{facing:north,state:full}", nbt: {Item: {Count: 1b, id: "computercraft:disk", tag: {Color: 1118481}}, id: "computercraft:disk_drive"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{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:air",
"computercraft:computer_advanced{facing:north,state:on}",
"computercraft:disk_drive{facing:north,state:full}"
]
}

View File

@@ -0,0 +1,139 @@
{
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: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:polished_andesite"},
{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:polished_andesite"},
{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: [
{blockPos: [2, 1, 2], pos: [2.583196949396921d, 1.0d, 2.608974919959593d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CustomName: '{"text":"printouttest.in_frame_at_night"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [221.58319694939692d, -58.0d, 92.60897491995959d], Pose: {}, Rotation: [1.3504658f, 6.7031174f], ShowArms: 0b, Small: 0b, UUID: [I; 1854159985, -991539606, -1317309541, 1483112386], id: "minecraft:armor_stand"}},
{blockPos: [2, 2, 3], pos: [2.5d, 2.5d, 3.96875d], nbt: {Air: 300s, Facing: 2b, FallDistance: 0.0f, Fire: -1s, Fixed: 0b, Invisible: 0b, Invulnerable: 0b, Item: {Count: 1b, id: "computercraft:printed_page", tag: {Color0: "eeeeeeeeeeeeeeeeeeeeeeeee", Color1: "eeeeeeeeeeeeeeeeeeeeeeeee", Color10: "eeeeeeeeeeeeeeeeeeeeeeeee", Color11: "eeeeeeeeeeeeeeeeeeeeeeeee", Color12: "eeeeeeeeeeeeeeeeeeeeeeeee", Color13: "eeeeeeeeeeeeeeeeeeeeeeeee", Color14: "eeeeeeeeeeeeeeeeeeeeeeeee", Color15: "eeeeeeeeeeeeeeeeeeeeeeeee", Color16: "eeeeeeeeeeeeeeeeeeeeeeeee", Color17: "eeeeeeeeeeeeeeeeeeeeeeeee", Color18: "eeeeeeeeeeeeeeeeeeeeeeeee", Color19: "eeeeeeeeeeeeeeeeeeeeeeeee", Color2: "eeeeeeeeeeeeeeeeeeeeeeeee", Color20: "eeeeeeeeeeeeeeeeeeeeeeeee", Color3: "eeeeeeeeeeeeeeeeeeeeeeeee", Color4: "eeeeeeeeeeeeeeeeeeeeeeeee", Color5: "eeeeeeeeeeeeeeeeeeeeeeeee", Color6: "eeeeeeeeeeeeeeeeeeeeeeeee", Color7: "eeeeeeeeeeeeeeeeeeeeeeeee", Color8: "eeeeeeeeeeeeeeeeeeeeeeeee", Color9: "eeeeeeeeeeeeeeeeeeeeeeeee", Pages: 1, Text0: "If you're reading this, ", Text1: "the test failed. ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: "a.lua"}}, ItemDropChance: 1.0f, ItemRotation: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [221.5d, -56.5d, 93.96875d], Rotation: [-540.0f, 0.0f], TileX: 221, TileY: -57, TileZ: 93, UUID: [I; 1972443954, 193152445, -1823446000, -1684171214], id: "minecraft:item_frame"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:air"
]
}

View File

@@ -1,140 +0,0 @@
{
DataVersion: 2730,
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:white_concrete"},
{pos: [0, 1, 1], state: "minecraft:white_concrete"},
{pos: [0, 1, 2], state: "minecraft:white_concrete"},
{pos: [0, 1, 3], state: "minecraft:white_concrete"},
{pos: [0, 1, 4], state: "minecraft:white_concrete"},
{pos: [1, 1, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [2, 1, 0], state: "minecraft:white_concrete"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:white_concrete"},
{pos: [3, 1, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [4, 1, 0], state: "minecraft:white_concrete"},
{pos: [4, 1, 1], state: "minecraft:white_concrete"},
{pos: [4, 1, 2], state: "minecraft:white_concrete"},
{pos: [4, 1, 3], state: "minecraft:white_concrete"},
{pos: [4, 1, 4], state: "minecraft:white_concrete"},
{pos: [0, 2, 0], state: "minecraft:white_concrete"},
{pos: [0, 2, 1], state: "minecraft:white_concrete"},
{pos: [0, 2, 2], state: "minecraft:white_concrete"},
{pos: [0, 2, 3], state: "minecraft:white_concrete"},
{pos: [0, 2, 4], state: "minecraft:white_concrete"},
{pos: [1, 2, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [2, 2, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [3, 2, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [4, 2, 0], state: "minecraft:white_concrete"},
{pos: [4, 2, 1], state: "minecraft:white_concrete"},
{pos: [4, 2, 2], state: "minecraft:white_concrete"},
{pos: [4, 2, 3], state: "minecraft:white_concrete"},
{pos: [4, 2, 4], state: "minecraft:white_concrete"},
{pos: [0, 3, 0], state: "minecraft:white_concrete"},
{pos: [0, 3, 1], state: "minecraft:white_concrete"},
{pos: [0, 3, 2], state: "minecraft:white_concrete"},
{pos: [0, 3, 3], state: "minecraft:white_concrete"},
{pos: [0, 3, 4], state: "minecraft:white_concrete"},
{pos: [1, 3, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [2, 3, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [3, 3, 0], state: "minecraft:white_concrete"},
{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:white_concrete"},
{pos: [4, 3, 0], state: "minecraft:white_concrete"},
{pos: [4, 3, 1], state: "minecraft:white_concrete"},
{pos: [4, 3, 2], state: "minecraft:white_concrete"},
{pos: [4, 3, 3], state: "minecraft:white_concrete"},
{pos: [4, 3, 4], state: "minecraft:white_concrete"},
{pos: [0, 4, 0], state: "minecraft:white_concrete"},
{pos: [0, 4, 1], state: "minecraft:white_concrete"},
{pos: [0, 4, 2], state: "minecraft:white_concrete"},
{pos: [0, 4, 3], state: "minecraft:white_concrete"},
{pos: [0, 4, 4], state: "minecraft:white_concrete"},
{pos: [1, 4, 0], state: "minecraft:white_concrete"},
{pos: [1, 4, 1], state: "minecraft:white_concrete"},
{pos: [1, 4, 2], state: "minecraft:white_concrete"},
{pos: [1, 4, 3], state: "minecraft:white_concrete"},
{pos: [1, 4, 4], state: "minecraft:white_concrete"},
{pos: [2, 4, 0], state: "minecraft:white_concrete"},
{pos: [2, 4, 1], state: "minecraft:white_concrete"},
{pos: [2, 4, 2], state: "minecraft:white_concrete"},
{pos: [2, 4, 3], state: "minecraft:white_concrete"},
{pos: [2, 4, 4], state: "minecraft:white_concrete"},
{pos: [3, 4, 0], state: "minecraft:white_concrete"},
{pos: [3, 4, 1], state: "minecraft:white_concrete"},
{pos: [3, 4, 2], state: "minecraft:white_concrete"},
{pos: [3, 4, 3], state: "minecraft:white_concrete"},
{pos: [3, 4, 4], state: "minecraft:white_concrete"},
{pos: [4, 4, 0], state: "minecraft:white_concrete"},
{pos: [4, 4, 1], state: "minecraft:white_concrete"},
{pos: [4, 4, 2], state: "minecraft:white_concrete"},
{pos: [4, 4, 3], state: "minecraft:white_concrete"},
{pos: [4, 4, 4], state: "minecraft:white_concrete"}
],
entities: [
{blockPos: [2, 2, 3], pos: [2.5d, 2.5d, 3.96875d], nbt: {Air: 300s, CanUpdate: 1b, Facing: 2b, FallDistance: 0.0f, Fire: -1s, Fixed: 0b, Invisible: 0b, Invulnerable: 0b, Item: {Count: 1b, id: "computercraft:printed_page", tag: {Color0: "eeeeeeeeeeeeeeeeeeeeeeeee", Color1: "eeeeeeeeeeeeeeeeeeeeeeeee", Color10: "eeeeeeeeeeeeeeeeeeeeeeeee", Color11: "eeeeeeeeeeeeeeeeeeeeeeeee", Color12: "eeeeeeeeeeeeeeeeeeeeeeeee", Color13: "eeeeeeeeeeeeeeeeeeeeeeeee", Color14: "eeeeeeeeeeeeeeeeeeeeeeeee", Color15: "eeeeeeeeeeeeeeeeeeeeeeeee", Color16: "eeeeeeeeeeeeeeeeeeeeeeeee", Color17: "eeeeeeeeeeeeeeeeeeeeeeeee", Color18: "eeeeeeeeeeeeeeeeeeeeeeeee", Color19: "eeeeeeeeeeeeeeeeeeeeeeeee", Color2: "eeeeeeeeeeeeeeeeeeeeeeeee", Color20: "eeeeeeeeeeeeeeeeeeeeeeeee", Color3: "eeeeeeeeeeeeeeeeeeeeeeeee", Color4: "eeeeeeeeeeeeeeeeeeeeeeeee", Color5: "eeeeeeeeeeeeeeeeeeeeeeeee", Color6: "eeeeeeeeeeeeeeeeeeeeeeeee", Color7: "eeeeeeeeeeeeeeeeeeeeeeeee", Color8: "eeeeeeeeeeeeeeeeeeeeeeeee", Color9: "eeeeeeeeeeeeeeeeeeeeeeeee", Pages: 1, Text0: "If you're reading this, ", Text1: "the test failed. ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: "a.lua"}}, ItemDropChance: 1.0f, ItemRotation: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [10.5d, 7.5d, 44.96875d], Rotation: [180.0f, 0.0f], TileX: 10, TileY: 7, TileZ: 44, UUID: [I; 1043973837, -2076424529, -1762893135, -165665834], id: "minecraft:item_frame"}},
{blockPos: [2, 1, 2], pos: [2.583196949396914d, 1.0d, 2.6089749199596d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CanUpdate: 1b, CustomName: '{"text":"printouttest.in_frame_at_night"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [10.583196949396914d, 6.0d, 43.6089749199596d], Pose: {}, Rotation: [1.3504658f, 6.7031174f], ShowArms: 0b, Small: 0b, UUID: [I; -1917933016, 1390888530, -2109873447, -2136052677], id: "minecraft:armor_stand"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:white_concrete",
"minecraft:air"
]
}

View File

@@ -217,9 +217,10 @@ end
channel. The message will be received by every device listening to rednet.
@param message The message to send. This should not contain coroutines or
functions, as they will be converted to @{nil}. @tparam[opt] string protocol
The "protocol" to send this message under. When using @{rednet.receive} one can
filter to only receive messages sent under a particular protocol.
functions, as they will be converted to @{nil}.
@tparam[opt] string protocol The "protocol" to send this message under. When
using @{rednet.receive} one can filter to only receive messages sent under a
particular protocol.
@see rednet.receive
@changed 1.6 Added protocol parameter.
@usage Broadcast the words "Hello, world!" to every computer using rednet.

View File

@@ -1,3 +1,9 @@
# New features in CC: Tweaked 1.102.2
Several bug fixes:
* Fix printouts crashing in item frames
* Fix disks not being assigned an ID when placed in a disk drive.
# New features in CC: Tweaked 1.102.1
Several bug fixes:

View File

@@ -1,18 +1,7 @@
New features in CC: Tweaked 1.102.1
New features in CC: Tweaked 1.102.2
Several bug fixes:
* Fix crash on Fabric when refuelling with a non-fuel item (emmachase).
* Fix crash when calling `pocket.equipBack()` with a wireless modem.
* Fix turtles dropping their inventory when moving (emmachase).
* Fix crash when inserting items into a full inventory (emmachase).
* Simplify wired cable breaking code, fixing items sometimes not dropping.
* Correctly handle double chests being treated as single threads under Fabric.
* Fix `mouse_up` not being fired under Fabric.
* Fix full-block Wired modems not connecting to adjacent cables when placed.
* Hide the search tab from the `itemGroups` item details.
* Fix speakers playing too loudly.
* Change where turtles drop items from, reducing the chance that items clip through blocks.
* Fix the `computer_threads` config option not applying under Fabric.
* Fix stack overflow in logging code.
* Fix printouts crashing in item frames
* Fix disks not being assigned an ID when placed in a disk drive.
Type "help changelog" to see the full version history.

View File

@@ -12,20 +12,32 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.WritableFileMount;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.MachineEnvironment;
import dan200.computercraft.core.lua.MachineResult;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.test.core.computer.BasicEnvironment;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.function.Executable;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.TestAbortedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.debug.DebugFrame;
import org.squiddev.cobalt.debug.DebugHook;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.OneArgFunction;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
@@ -36,6 +48,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -78,7 +91,7 @@ public class ComputerTestDelegate {
private final Condition hasFinished = lock.newCondition();
private boolean finished = false;
private Map<String, Map<Double, Double>> finishedWith;
private final Map<LuaString, Int2IntArrayMap> coverage = new HashMap<>();
@BeforeEach
public void before() throws IOException {
@@ -99,7 +112,7 @@ public class ComputerTestDelegate {
}
var environment = new BasicEnvironment(mount);
context = new ComputerContext(environment, 1, new NoWorkMainThreadScheduler());
context = new ComputerContext(environment, new ComputerThread(1), new NoWorkMainThreadScheduler(), CoverageLuaMachine::new);
computer = new Computer(context, environment, term, 0);
computer.getEnvironment().setPeripheral(ComputerSide.TOP, new FakeModem());
computer.getEnvironment().setPeripheral(ComputerSide.BOTTOM, new FakePeripheralHub());
@@ -137,10 +150,12 @@ public class ComputerTestDelegate {
computer.shutdown();
}
if (finishedWith != null) {
if (!coverage.isEmpty()) {
Files.createDirectories(REPORT_PATH.getParent());
try (var writer = Files.newBufferedWriter(REPORT_PATH)) {
new LuaCoverage(finishedWith).write(writer);
new LuaCoverage(coverage.entrySet().stream().collect(Collectors.toMap(
x -> x.getKey().substring(1).toString(), Map.Entry::getValue
))).write(writer);
}
}
}
@@ -414,7 +429,9 @@ public class ComputerTestDelegate {
switch (status) {
case "ok":
break;
case "pending":
runResult = new TestAbortedException("Test is pending");
break;
case "fail":
runResult = new AssertionFailedError(wholeMessage.toString());
@@ -432,9 +449,7 @@ public class ComputerTestDelegate {
}
@LuaFunction
public final void finish(Optional<Map<?, ?>> result) {
@SuppressWarnings("unchecked")
var finishedResult = (Map<String, Map<Double, Double>>) result.orElse(null);
public final void finish() {
LOG.info("Finished");
// Signal to after that execution has finished
@@ -445,7 +460,6 @@ public class ComputerTestDelegate {
}
try {
finished = true;
if (finishedResult != null) finishedWith = finishedResult;
hasFinished.signal();
} finally {
@@ -453,4 +467,73 @@ public class ComputerTestDelegate {
}
}
}
/**
* A subclass of {@link CobaltLuaMachine} which tracks coverage for executed files.
* <p>
* This is a super nasty hack, but is also an order of magnitude faster than tracking this in Lua.
*/
private class CoverageLuaMachine extends CobaltLuaMachine {
CoverageLuaMachine(MachineEnvironment environment) {
super(environment);
}
@Override
public MachineResult loadBios(InputStream bios) {
var result = super.loadBios(bios);
if (result != MachineResult.OK) return result;
LuaTable globals;
LuaThread mainRoutine;
try {
var globalField = CobaltLuaMachine.class.getDeclaredField("globals");
globalField.setAccessible(true);
globals = (LuaTable) globalField.get(this);
var threadField = CobaltLuaMachine.class.getDeclaredField("mainRoutine");
threadField.setAccessible(true);
mainRoutine = (LuaThread) threadField.get(this);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Cannot get internal Cobalt state", e);
}
var coverage = ComputerTestDelegate.this.coverage;
var hook = new DebugHook() {
@Override
public void onCall(LuaState state, DebugState ds, DebugFrame frame) {
}
@Override
public void onReturn(LuaState state, DebugState ds, DebugFrame frame) {
}
@Override
public void onCount(LuaState state, DebugState ds, DebugFrame frame) {
}
@Override
public void onLine(LuaState state, DebugState ds, DebugFrame frame, int newLine) {
if (frame.closure == null) return;
var proto = frame.closure.getPrototype();
if (!proto.source.startsWith('@')) return;
var map = coverage.computeIfAbsent(proto.source, x -> new Int2IntArrayMap());
map.put(newLine, map.get(newLine) + 1);
}
};
((LuaTable) globals.rawget("coroutine")).rawset("create", new OneArgFunction() {
@Override
public LuaValue call(LuaState state, LuaValue arg) throws LuaError {
var thread = new LuaThread(state, arg.checkFunction(), state.getCurrentThread().getfenv());
thread.getDebugState().setHook(hook, false, true, false, 0);
return thread;
}
});
mainRoutine.getDebugState().setHook(hook, false, true, false, 0);
return MachineResult.OK;
}
}
}

View File

@@ -5,7 +5,7 @@
*/
package dan200.computercraft.core;
import com.google.common.base.Strings;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.slf4j.Logger;
@@ -26,24 +26,25 @@ class LuaCoverage {
private static final Path ROOT = new File("src/main/resources/data/computercraft/lua").toPath();
private static final Path BIOS = ROOT.resolve("bios.lua");
private static final Path APIS = ROOT.resolve("rom/apis");
private static final Path STARTUP = ROOT.resolve("rom/startup.lua");
private static final Path SHELL = ROOT.resolve("rom/programs/shell.lua");
private static final Path MULTISHELL = ROOT.resolve("rom/programs/advanced/multishell.lua");
private static final Path TREASURE = ROOT.resolve("treasure");
private final Map<String, Map<Double, Double>> coverage;
private final Map<String, Int2IntMap> coverage;
private final String blank;
private final String zero;
private final String countFormat;
LuaCoverage(Map<String, Map<Double, Double>> coverage) {
LuaCoverage(Map<String, Int2IntMap> coverage) {
this.coverage = coverage;
var max = (int) coverage.values().stream()
.flatMapToDouble(x -> x.values().stream().mapToDouble(y -> y))
var max = coverage.values().stream()
.flatMapToInt(x -> x.values().intStream())
.max().orElse(0);
var maxLen = Math.max(1, (int) Math.ceil(Math.log10(max)));
blank = Strings.repeat(" ", maxLen + 1);
zero = Strings.repeat("*", maxLen) + "0";
blank = " ".repeat(maxLen + 1);
zero = "*".repeat(maxLen) + "0";
countFormat = "%" + (maxLen + 1) + "d";
}
@@ -56,14 +57,15 @@ class LuaCoverage {
var possiblePaths = Stream.of(
coverage.remove("/" + full),
path.equals(BIOS) ? coverage.remove("bios.lua") : null,
path.equals(STARTUP) ? coverage.remove("startup.lua") : null,
path.equals(SHELL) ? coverage.remove("shell.lua") : null,
path.equals(MULTISHELL) ? coverage.remove("multishell.lua") : null,
path.startsWith(APIS) ? coverage.remove(path.getFileName().toString()) : null
);
var files = possiblePaths
.filter(Objects::nonNull)
.flatMap(x -> x.entrySet().stream())
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue, Double::sum));
.flatMap(x -> x.int2IntEntrySet().stream())
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
try {
writeCoverageFor(out, path, files);
@@ -78,7 +80,7 @@ class LuaCoverage {
}
}
private void writeCoverageFor(Writer out, Path fullName, Map<Double, Double> visitedLines) throws IOException {
private void writeCoverageFor(Writer out, Path fullName, Map<Integer, Integer> visitedLines) throws IOException {
if (!Files.exists(fullName)) {
LOG.error("Cannot locate file {}", fullName);
return;
@@ -96,9 +98,9 @@ class LuaCoverage {
var lineNo = 0;
while ((line = reader.readLine()) != null) {
lineNo++;
var count = visitedLines.get((double) lineNo);
var count = visitedLines.get(lineNo);
if (count != null) {
out.write(String.format(countFormat, count.intValue()));
out.write(String.format(countFormat, count));
} else if (activeLines.contains(lineNo)) {
out.write(zero);
} else {

View File

@@ -518,26 +518,9 @@ local function before_each(body)
before_each_fns[n], before_each_fns.n = body, n
end
local native_co_create, native_loadfile = coroutine.create, loadfile
local native_loadfile = loadfile
local line_counts = {}
if cct_test then
local string_sub, debug_getinfo = string.sub, debug.getinfo
local function debug_hook(_, line_nr)
local name = debug_getinfo(2, "S").source
if string_sub(name, 1, 1) ~= "@" then return end
name = string_sub(name, 2)
local file = line_counts[name]
if not file then file = {} line_counts[name] = file end
file[line_nr] = (file[line_nr] or 0) + 1
end
coroutine.create = function(...)
local co = native_co_create(...)
debug.sethook(co, debug_hook, "l")
return co
end
local expect = require "cc.expect".expect
_G.native_loadfile = native_loadfile
_G.loadfile = function(filename, mode, env)
@@ -557,8 +540,6 @@ if cct_test then
file.close()
return func, err
end
debug.sethook(debug_hook, "l")
end
local arg = ...
@@ -736,8 +717,6 @@ end
term.setTextColour(colours.white) io.write(info .. "\n")
-- Restore hook stubs
debug.sethook(nil, "l")
coroutine.create = native_co_create
_G.loadfile = native_loadfile
if cct_test then cct_test.finish(line_counts) end

View File

@@ -25,6 +25,9 @@ class ItemFrameRendererMixin {
)
@SuppressWarnings("UnusedMethod")
private void render(ItemFrame entity, float yaw, float partialTicks, PoseStack pose, MultiBufferSource buffers, int light, CallbackInfo ci) {
if (ClientHooks.onRenderItemFrame(pose, buffers, entity, entity.getItem(), light)) ci.cancel();
if (ClientHooks.onRenderItemFrame(pose, buffers, entity, entity.getItem(), light)) {
ci.cancel();
pose.popPose();
}
}
}