mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-03 23:22:59 +00:00 
			
		
		
		
	Merge branch 'master' into mc-1.14.x
This commit is contained in:
		@@ -17,7 +17,8 @@ ignore = {
 | 
			
		||||
-- are largely unsupported.
 | 
			
		||||
include_files = {
 | 
			
		||||
    'src/main/resources/assets/computercraft/lua/rom',
 | 
			
		||||
    'src/main/resources/assets/computercraft/lua/bios.lua'
 | 
			
		||||
    'src/main/resources/assets/computercraft/lua/bios.lua',
 | 
			
		||||
    'src/test/resources/test-rom',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
files['src/main/resources/assets/computercraft/lua/bios.lua'] = {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,4 +11,4 @@ cache:
 | 
			
		||||
    - $HOME/.gradle/wrapper/s
 | 
			
		||||
 | 
			
		||||
jdk:
 | 
			
		||||
    - oraclejdk8
 | 
			
		||||
    - openjdk8
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# 
 | 
			
		||||
[](https://travis-ci.org/SquidDev-CC/CC-Tweaked "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
 | 
			
		||||
[](https://travis-ci.org/SquidDev-CC/CC-Tweaked "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
 | 
			
		||||
 | 
			
		||||
CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers,
 | 
			
		||||
turtles and more to Minecraft.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								build.gradle
									
									
									
									
									
								
							@@ -4,7 +4,7 @@ buildscript {
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        maven {
 | 
			
		||||
            name = "forge"
 | 
			
		||||
            url = "http://files.minecraftforge.net/maven"
 | 
			
		||||
            url = "https://files.minecraftforge.net/maven"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    dependencies {
 | 
			
		||||
@@ -67,7 +67,7 @@ minecraft {
 | 
			
		||||
repositories {
 | 
			
		||||
    maven {
 | 
			
		||||
        name "JEI"
 | 
			
		||||
        url  "http://dvs1.progwml6.com/files/maven"
 | 
			
		||||
        url "https://dvs1.progwml6.com/files/maven"
 | 
			
		||||
    }
 | 
			
		||||
    maven {
 | 
			
		||||
        name "SquidDev"
 | 
			
		||||
@@ -79,7 +79,7 @@ repositories {
 | 
			
		||||
    }
 | 
			
		||||
    maven {
 | 
			
		||||
        name "Amadornes"
 | 
			
		||||
        url "http://maven.amadornes.com/"
 | 
			
		||||
        url "https://maven.amadornes.com/"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -102,8 +102,8 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'
 | 
			
		||||
 | 
			
		||||
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
 | 
			
		||||
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
 | 
			
		||||
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
 | 
			
		||||
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
 | 
			
		||||
 | 
			
		||||
    deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
 | 
			
		||||
}
 | 
			
		||||
@@ -117,6 +117,8 @@ sourceSets {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compile tasks
 | 
			
		||||
 | 
			
		||||
javadoc {
 | 
			
		||||
    include "dan200/computercraft/api/**/*.java"
 | 
			
		||||
}
 | 
			
		||||
@@ -141,6 +143,14 @@ jar {
 | 
			
		||||
    from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[compileJava, compileTestJava].forEach {
 | 
			
		||||
    it.configure {
 | 
			
		||||
        options.compilerArgs << "-Xlint" << "-Xlint:-processing" << "-Werror"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets
 | 
			
		||||
import java.nio.file.*
 | 
			
		||||
import java.util.zip.*
 | 
			
		||||
@@ -276,7 +286,14 @@ task compressJson(dependsOn: jar) {
 | 
			
		||||
 | 
			
		||||
assemble.dependsOn compressJson
 | 
			
		||||
 | 
			
		||||
/* Check tasks */
 | 
			
		||||
// Check tasks
 | 
			
		||||
 | 
			
		||||
test {
 | 
			
		||||
    useJUnitPlatform()
 | 
			
		||||
    testLogging {
 | 
			
		||||
        events "skipped", "failed"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
license {
 | 
			
		||||
    mapping("java", "SLASHSTAR_STYLE")
 | 
			
		||||
@@ -300,6 +317,13 @@ license {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gradle.projectsEvaluated {
 | 
			
		||||
    tasks.withType(LicenseFormat) {
 | 
			
		||||
        outputs.upToDateWhen { false }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
task licenseAPI(type: LicenseCheck);
 | 
			
		||||
task licenseFormatAPI(type: LicenseFormat);
 | 
			
		||||
[licenseAPI, licenseFormatAPI].forEach {
 | 
			
		||||
@@ -310,7 +334,7 @@ task licenseFormatAPI(type: LicenseFormat);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Upload tasks */
 | 
			
		||||
// Upload tasks
 | 
			
		||||
 | 
			
		||||
task checkRelease {
 | 
			
		||||
    group "upload"
 | 
			
		||||
@@ -441,23 +465,3 @@ task uploadAll(dependsOn: uploadTasks) {
 | 
			
		||||
    group "upload"
 | 
			
		||||
    description "Uploads to all repositories (Maven, Curse, GitHub release)"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test {
 | 
			
		||||
    useJUnitPlatform()
 | 
			
		||||
    testLogging {
 | 
			
		||||
        events "passed", "skipped", "failed"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gradle.projectsEvaluated {
 | 
			
		||||
    reobfJar.dependsOn proguardMove
 | 
			
		||||
 | 
			
		||||
    tasks.withType(JavaCompile) {
 | 
			
		||||
        options.compilerArgs << "-Xlint" << "-Xlint:-processing" // Causes Forge build to fail << "-Werror"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tasks.withType(LicenseFormat) {
 | 
			
		||||
        outputs.upToDateWhen { false }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# Mod properties
 | 
			
		||||
mod_version=1.83.1
 | 
			
		||||
mod_version=1.84.0
 | 
			
		||||
 | 
			
		||||
# Minecraft properties
 | 
			
		||||
mc_version=1.14.4
 | 
			
		||||
 
 | 
			
		||||
@@ -144,7 +144,9 @@ public interface ITurtleAccess
 | 
			
		||||
    GameProfile getOwningPlayer();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the inventory of this turtle
 | 
			
		||||
     * Get the inventory of this turtle.
 | 
			
		||||
     *
 | 
			
		||||
     * Note: this inventory should only be accessed and modified on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This turtle's inventory
 | 
			
		||||
     * @see #getItemHandler()
 | 
			
		||||
@@ -155,6 +157,8 @@ public interface ITurtleAccess
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
 | 
			
		||||
     *
 | 
			
		||||
     * Note: this inventory should only be accessed and modified on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This turtle's inventory
 | 
			
		||||
     * @see #getInventory()
 | 
			
		||||
     * @see IItemHandlerModifiable
 | 
			
		||||
 
 | 
			
		||||
@@ -98,8 +98,8 @@ public interface ITurtleUpgrade
 | 
			
		||||
     * Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
 | 
			
		||||
     * by the turtle, and the tool is required to do some work.
 | 
			
		||||
     *
 | 
			
		||||
     * Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging,
 | 
			
		||||
     * {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
 | 
			
		||||
     * Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig} for
 | 
			
		||||
     * digging, {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle    Access to the turtle that the tool resides on.
 | 
			
		||||
     * @param side      Which side of the turtle (left or right) the tool resides on.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.api.media.IMedia;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IComputerAccess;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.shared.MediaProviders;
 | 
			
		||||
import dan200.computercraft.shared.media.items.ItemDisk;
 | 
			
		||||
import dan200.computercraft.shared.util.StringUtil;
 | 
			
		||||
import net.minecraft.item.ItemStack;
 | 
			
		||||
@@ -19,11 +20,11 @@ import javax.annotation.Nonnull;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
 | 
			
		||||
 | 
			
		||||
public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
{
 | 
			
		||||
    private final TileDiskDrive m_diskDrive;
 | 
			
		||||
 | 
			
		||||
    public DiskDrivePeripheral( TileDiskDrive diskDrive )
 | 
			
		||||
    DiskDrivePeripheral( TileDiskDrive diskDrive )
 | 
			
		||||
    {
 | 
			
		||||
        m_diskDrive = diskDrive;
 | 
			
		||||
    }
 | 
			
		||||
@@ -55,7 +56,7 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException
 | 
			
		||||
    public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
 | 
			
		||||
    {
 | 
			
		||||
        switch( method )
 | 
			
		||||
        {
 | 
			
		||||
@@ -63,21 +64,26 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
                return new Object[] { !m_diskDrive.getDiskStack().isEmpty() };
 | 
			
		||||
            case 1: // getDiskLabel
 | 
			
		||||
            {
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return media == null ? null : new Object[] { media.getLabel( m_diskDrive.getDiskStack() ) };
 | 
			
		||||
                ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                return media == null ? null : new Object[] { media.getLabel( stack ) };
 | 
			
		||||
            }
 | 
			
		||||
            case 2: // setDiskLabel
 | 
			
		||||
            {
 | 
			
		||||
                String label = optString( arguments, 0, null );
 | 
			
		||||
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return context.executeMainThreadTask( () -> {
 | 
			
		||||
                    ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                    IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                    if( media == null ) return null;
 | 
			
		||||
 | 
			
		||||
                ItemStack disk = m_diskDrive.getDiskStack();
 | 
			
		||||
                label = StringUtil.normaliseLabel( label );
 | 
			
		||||
                if( !media.setLabel( disk, label ) ) throw new LuaException( "Disk label cannot be changed" );
 | 
			
		||||
                m_diskDrive.setDiskStack( disk );
 | 
			
		||||
                    if( !media.setLabel( stack, StringUtil.normaliseLabel( label ) ) )
 | 
			
		||||
                    {
 | 
			
		||||
                        throw new LuaException( "Disk label cannot be changed" );
 | 
			
		||||
                    }
 | 
			
		||||
                    m_diskDrive.setDiskStack( stack );
 | 
			
		||||
                    return null;
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
            case 3: // hasData
 | 
			
		||||
                return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null };
 | 
			
		||||
@@ -86,14 +92,16 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
            case 5:
 | 
			
		||||
            {
 | 
			
		||||
                // hasAudio
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return new Object[] { media != null && media.getAudio( m_diskDrive.getDiskStack() ) != null };
 | 
			
		||||
                ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                return new Object[] { media != null && media.getAudio( stack ) != null };
 | 
			
		||||
            }
 | 
			
		||||
            case 6:
 | 
			
		||||
            {
 | 
			
		||||
                // getAudioTitle
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return new Object[] { media != null ? media.getAudioTitle( m_diskDrive.getDiskStack() ) : false };
 | 
			
		||||
                ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                return new Object[] { media != null ? media.getAudioTitle( stack ) : false };
 | 
			
		||||
            }
 | 
			
		||||
            case 7: // playAudio
 | 
			
		||||
                m_diskDrive.playDiskAudio();
 | 
			
		||||
@@ -129,8 +137,7 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals( IPeripheral other )
 | 
			
		||||
    {
 | 
			
		||||
        if( this == other ) return true;
 | 
			
		||||
        return other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
 | 
			
		||||
        return this == other || other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
 
 | 
			
		||||
@@ -320,35 +320,31 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public ItemStack getDiskStack()
 | 
			
		||||
    ItemStack getDiskStack()
 | 
			
		||||
    {
 | 
			
		||||
        return getStackInSlot( 0 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDiskStack( @Nonnull ItemStack stack )
 | 
			
		||||
    void setDiskStack( @Nonnull ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        setInventorySlotContents( 0, stack );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IMedia getDiskMedia()
 | 
			
		||||
    private IMedia getDiskMedia()
 | 
			
		||||
    {
 | 
			
		||||
        return MediaProviders.get( getDiskStack() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDiskMountPath( IComputerAccess computer )
 | 
			
		||||
    String getDiskMountPath( IComputerAccess computer )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
            if( m_computers.containsKey( computer ) )
 | 
			
		||||
        {
 | 
			
		||||
            MountInfo info = m_computers.get( computer );
 | 
			
		||||
                return info.mountPath;
 | 
			
		||||
            return info != null ? info.mountPath : null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void mount( IComputerAccess computer )
 | 
			
		||||
    void mount( IComputerAccess computer )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -357,7 +353,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void unmount( IComputerAccess computer )
 | 
			
		||||
    void unmount( IComputerAccess computer )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -366,7 +362,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void playDiskAudio()
 | 
			
		||||
    void playDiskAudio()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -379,7 +375,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void stopDiskAudio()
 | 
			
		||||
    void stopDiskAudio()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -388,7 +384,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void ejectDisk()
 | 
			
		||||
    void ejectDisk()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IComputerAccess;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.shared.util.StringUtil;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
 | 
			
		||||
@@ -51,8 +52,13 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
 | 
			
		||||
    public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
 | 
			
		||||
    {
 | 
			
		||||
        // FIXME: There's a theoretical race condition here between getCurrentPage and then using the page. Ideally
 | 
			
		||||
        //  we'd lock on the page, consume it, and unlock.
 | 
			
		||||
 | 
			
		||||
        // FIXME: None of our page modification functions actually mark the tile as dirty, so the page may not be
 | 
			
		||||
        //  persisted correctly.
 | 
			
		||||
        switch( method )
 | 
			
		||||
        {
 | 
			
		||||
            case 0: // write
 | 
			
		||||
@@ -89,10 +95,13 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
                return new Object[] { width, height };
 | 
			
		||||
            }
 | 
			
		||||
            case 4: // newPage
 | 
			
		||||
                return new Object[] { m_printer.startNewPage() };
 | 
			
		||||
                return context.executeMainThreadTask( () -> new Object[] { m_printer.startNewPage() } );
 | 
			
		||||
            case 5: // endPage
 | 
			
		||||
                getCurrentPage();
 | 
			
		||||
                return context.executeMainThreadTask( () -> {
 | 
			
		||||
                    getCurrentPage();
 | 
			
		||||
                    return new Object[] { m_printer.endCurrentPage() };
 | 
			
		||||
                } );
 | 
			
		||||
            case 6: // getInkLevel
 | 
			
		||||
                return new Object[] { m_printer.getInkLevel() };
 | 
			
		||||
            case 7:
 | 
			
		||||
@@ -100,7 +109,7 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
                // setPageTitle
 | 
			
		||||
                String title = optString( args, 0, "" );
 | 
			
		||||
                getCurrentPage();
 | 
			
		||||
                m_printer.setPageTitle( title );
 | 
			
		||||
                m_printer.setPageTitle( StringUtil.normaliseLabel( title ) );
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            case 8: // getPaperLevel
 | 
			
		||||
@@ -123,13 +132,11 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
        return m_printer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    private Terminal getCurrentPage() throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        Terminal currentPage = m_printer.getCurrentPage();
 | 
			
		||||
        if( currentPage == null )
 | 
			
		||||
        {
 | 
			
		||||
            throw new LuaException( "Page not started" );
 | 
			
		||||
        }
 | 
			
		||||
        if( currentPage == null ) throw new LuaException( "Page not started" );
 | 
			
		||||
        return currentPage;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -120,11 +120,8 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Read inventory
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        ItemStackHelper.loadAllItems( nbt, m_inventory );
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -141,15 +138,12 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Write inventory
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        ItemStackHelper.saveAllItems( nbt, m_inventory );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return super.write( nbt );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isPrinting()
 | 
			
		||||
    boolean isPrinting()
 | 
			
		||||
    {
 | 
			
		||||
        return m_printing;
 | 
			
		||||
    }
 | 
			
		||||
@@ -173,74 +167,60 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack getStackInSlot( int i )
 | 
			
		||||
    public ItemStack getStackInSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        return m_inventory.get( i );
 | 
			
		||||
        return m_inventory.get( slot );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack removeStackFromSlot( int i )
 | 
			
		||||
    public ItemStack removeStackFromSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack result = m_inventory.get( i );
 | 
			
		||||
            m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
        ItemStack result = m_inventory.get( slot );
 | 
			
		||||
        m_inventory.set( slot, ItemStack.EMPTY );
 | 
			
		||||
        markDirty();
 | 
			
		||||
        updateBlockState();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack decrStackSize( int i, int j )
 | 
			
		||||
    public ItemStack decrStackSize( int slot, int count )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            if( m_inventory.get( i ).isEmpty() ) return ItemStack.EMPTY;
 | 
			
		||||
        ItemStack stack = m_inventory.get( slot );
 | 
			
		||||
        if( stack.isEmpty() ) return ItemStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
            if( m_inventory.get( i ).getCount() <= j )
 | 
			
		||||
        if( stack.getCount() <= count )
 | 
			
		||||
        {
 | 
			
		||||
                ItemStack itemstack = m_inventory.get( i );
 | 
			
		||||
                m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
                markDirty();
 | 
			
		||||
                updateBlockState();
 | 
			
		||||
                return itemstack;
 | 
			
		||||
            setInventorySlotContents( slot, ItemStack.EMPTY );
 | 
			
		||||
            return stack;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            ItemStack part = m_inventory.get( i ).split( j );
 | 
			
		||||
            if( m_inventory.get( i ).isEmpty() )
 | 
			
		||||
        ItemStack part = stack.split( count );
 | 
			
		||||
        if( m_inventory.get( slot ).isEmpty() )
 | 
			
		||||
        {
 | 
			
		||||
                m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
            m_inventory.set( slot, ItemStack.EMPTY );
 | 
			
		||||
            updateBlockState();
 | 
			
		||||
        }
 | 
			
		||||
        markDirty();
 | 
			
		||||
        return part;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setInventorySlotContents( int i, @Nonnull ItemStack stack )
 | 
			
		||||
    public void setInventorySlotContents( int slot, @Nonnull ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            m_inventory.set( i, stack );
 | 
			
		||||
        m_inventory.set( slot, stack );
 | 
			
		||||
        markDirty();
 | 
			
		||||
        updateBlockState();
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clear()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    {
 | 
			
		||||
        for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
        markDirty();
 | 
			
		||||
        updateBlockState();
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isItemValidForSlot( int slot, @Nonnull ItemStack stack )
 | 
			
		||||
@@ -290,14 +270,18 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
        return new PrinterPeripheral( this );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Terminal getCurrentPage()
 | 
			
		||||
    @Nullable
 | 
			
		||||
    Terminal getCurrentPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
        {
 | 
			
		||||
            return m_printing ? m_page : null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean startNewPage()
 | 
			
		||||
    boolean startNewPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
        {
 | 
			
		||||
            if( !canInputPage() ) return false;
 | 
			
		||||
            if( m_printing && !outputPage() ) return false;
 | 
			
		||||
@@ -305,49 +289,36 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean endCurrentPage()
 | 
			
		||||
    boolean endCurrentPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
        {
 | 
			
		||||
            if( m_printing && outputPage() )
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            return m_printing && outputPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getInkLevel()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    int getInkLevel()
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack inkStack = m_inventory.get( 0 );
 | 
			
		||||
        return isInk( inkStack ) ? inkStack.getCount() : 0;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getPaperLevel()
 | 
			
		||||
    int getPaperLevel()
 | 
			
		||||
    {
 | 
			
		||||
        int count = 0;
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        for( int i = 1; i < 7; i++ )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack paperStack = m_inventory.get( i );
 | 
			
		||||
                if( !paperStack.isEmpty() && isPaper( paperStack ) )
 | 
			
		||||
                {
 | 
			
		||||
                    count += paperStack.getCount();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if( isPaper( paperStack ) ) count += paperStack.getCount();
 | 
			
		||||
        }
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPageTitle( String title )
 | 
			
		||||
    void setPageTitle( String title )
 | 
			
		||||
    {
 | 
			
		||||
        if( m_printing )
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
        {
 | 
			
		||||
            m_pageTitle = title;
 | 
			
		||||
            if( m_printing ) m_pageTitle = title;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -364,17 +335,12 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean canInputPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack inkStack = m_inventory.get( 0 );
 | 
			
		||||
        return !inkStack.isEmpty() && isInk( inkStack ) && getPaperLevel() > 0;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean inputPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack inkStack = m_inventory.get( 0 );
 | 
			
		||||
        if( !isInk( inkStack ) ) return false;
 | 
			
		||||
@@ -382,8 +348,8 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
        for( int i = 1; i < 7; i++ )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack paperStack = m_inventory.get( i );
 | 
			
		||||
                if( !paperStack.isEmpty() && isPaper( paperStack ) )
 | 
			
		||||
                {
 | 
			
		||||
            if( paperStack.isEmpty() || !isPaper( paperStack ) ) continue;
 | 
			
		||||
 | 
			
		||||
            // Setup the new page
 | 
			
		||||
            DyeColor dye = ColourUtils.getStackColour( inkStack );
 | 
			
		||||
            m_page.setTextColour( dye != null ? dye.getId() : 15 );
 | 
			
		||||
@@ -421,14 +387,10 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
            m_printing = true;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
            }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean outputPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
    {
 | 
			
		||||
        int height = m_page.getHeight();
 | 
			
		||||
        String[] lines = new String[height];
 | 
			
		||||
@@ -440,8 +402,6 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ItemStack stack = ItemPrintout.createSingleFromTitleAndText( m_pageTitle, lines, colours );
 | 
			
		||||
            synchronized( m_inventory )
 | 
			
		||||
            {
 | 
			
		||||
        for( int slot : BOTTOM_SLOTS )
 | 
			
		||||
        {
 | 
			
		||||
            if( m_inventory.get( slot ).isEmpty() )
 | 
			
		||||
@@ -451,14 +411,10 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void ejectContents()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    {
 | 
			
		||||
        for( int i = 0; i < 13; i++ )
 | 
			
		||||
        {
 | 
			
		||||
@@ -477,13 +433,10 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateBlockState()
 | 
			
		||||
    {
 | 
			
		||||
        boolean top = false, bottom = false;
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        for( int i = 1; i < 7; i++ )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack stack = m_inventory.get( i );
 | 
			
		||||
@@ -502,7 +455,6 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateBlockState( top, bottom );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,9 +31,13 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler
 | 
			
		||||
    @Override
 | 
			
		||||
    public int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack currentStack, int slot, int limit )
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false );
 | 
			
		||||
        int fuelToGive = getFuelPerItem( stack ) * stack.getCount();
 | 
			
		||||
        int fuelSpaceLeft = turtle.getFuelLimit() - turtle.getFuelLevel();
 | 
			
		||||
        int fuelPerItem = getFuelPerItem( turtle.getItemHandler().getStackInSlot( slot ) );
 | 
			
		||||
        int fuelItemLimit = (int) Math.ceil( fuelSpaceLeft / (double) fuelPerItem );
 | 
			
		||||
        if( limit > fuelItemLimit ) limit = fuelItemLimit;
 | 
			
		||||
 | 
			
		||||
        ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false );
 | 
			
		||||
        int fuelToGive = fuelPerItem * stack.getCount();
 | 
			
		||||
        // Store the replacement item in the inventory
 | 
			
		||||
        ItemStack replacementStack = stack.getItem().getContainerItem( stack );
 | 
			
		||||
        if( !replacementStack.isEmpty() )
 | 
			
		||||
 
 | 
			
		||||
@@ -335,9 +335,11 @@ public class TurtleAPI implements ILuaAPI
 | 
			
		||||
                return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) );
 | 
			
		||||
            case 40: // inspectDown
 | 
			
		||||
                return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) );
 | 
			
		||||
            case 41:
 | 
			
		||||
            case 41: // getItemDetail
 | 
			
		||||
            {
 | 
			
		||||
                // getItemDetail
 | 
			
		||||
                // FIXME: There's a race condition here if the stack is being modified (mutating NBT, etc...)
 | 
			
		||||
                //  on another thread. The obvious solution is to move this into a command, but some programs rely
 | 
			
		||||
                //  on this having a 0-tick delay.
 | 
			
		||||
                int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() );
 | 
			
		||||
                ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
 | 
			
		||||
                if( stack.isEmpty() ) return new Object[] { null };
 | 
			
		||||
 
 | 
			
		||||
@@ -153,7 +153,7 @@ public class BlockTurtle extends BlockComputerBase<TileTurtle> implements IWater
 | 
			
		||||
    @Override
 | 
			
		||||
    public float getExplosionResistance( BlockState state, IWorldReader world, BlockPos pos, @Nullable Entity exploder, Explosion explosion )
 | 
			
		||||
    {
 | 
			
		||||
        if( getFamily() == ComputerFamily.Advanced && (exploder instanceof LivingEntity || exploder instanceof DamagingProjectileEntity) )
 | 
			
		||||
        if( getFamily() == ComputerFamily.Advanced || exploder instanceof LivingEntity || exploder instanceof DamagingProjectileEntity )
 | 
			
		||||
        {
 | 
			
		||||
            return 2000;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -49,13 +49,12 @@ import net.minecraftforge.items.wrapper.InvWrapper;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
import static net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY;
 | 
			
		||||
 | 
			
		||||
public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory
 | 
			
		||||
{
 | 
			
		||||
    // Statics
 | 
			
		||||
 | 
			
		||||
    public static final int INVENTORY_SIZE = 16;
 | 
			
		||||
    public static final int INVENTORY_WIDTH = 4;
 | 
			
		||||
    public static final int INVENTORY_HEIGHT = 4;
 | 
			
		||||
@@ -70,8 +69,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        type -> new TileTurtle( type, ComputerFamily.Advanced )
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Members
 | 
			
		||||
 | 
			
		||||
    enum MoveState
 | 
			
		||||
    {
 | 
			
		||||
        NOT_MOVED,
 | 
			
		||||
@@ -79,25 +76,20 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        MOVED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NonNullList<ItemStack> m_inventory;
 | 
			
		||||
    private NonNullList<ItemStack> m_previousInventory;
 | 
			
		||||
    private final NonNullList<ItemStack> m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
    private final NonNullList<ItemStack> m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
    private final IItemHandlerModifiable m_itemHandler = new InvWrapper( this );
 | 
			
		||||
    private LazyOptional<IItemHandlerModifiable> itemHandlerCap;
 | 
			
		||||
    private boolean m_inventoryChanged;
 | 
			
		||||
    private TurtleBrain m_brain;
 | 
			
		||||
    private MoveState m_moveState;
 | 
			
		||||
    private boolean m_inventoryChanged = false;
 | 
			
		||||
    private TurtleBrain m_brain = new TurtleBrain( this );
 | 
			
		||||
    private MoveState m_moveState = MoveState.NOT_MOVED;
 | 
			
		||||
 | 
			
		||||
    public TileTurtle( TileEntityType<? extends TileGeneric> type, ComputerFamily family )
 | 
			
		||||
    {
 | 
			
		||||
        super( type, family );
 | 
			
		||||
        m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
        m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
        m_inventoryChanged = false;
 | 
			
		||||
        m_brain = new TurtleBrain( this );
 | 
			
		||||
        m_moveState = MoveState.NOT_MOVED;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean hasMoved()
 | 
			
		||||
    private boolean hasMoved()
 | 
			
		||||
    {
 | 
			
		||||
        return m_moveState == MoveState.MOVED;
 | 
			
		||||
    }
 | 
			
		||||
@@ -237,8 +229,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    {
 | 
			
		||||
        super.tick();
 | 
			
		||||
        m_brain.update();
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        if( !getWorld().isRemote && m_inventoryChanged )
 | 
			
		||||
        {
 | 
			
		||||
            ServerComputer computer = getServerComputer();
 | 
			
		||||
@@ -251,7 +241,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void updateBlockState( ComputerState newState )
 | 
			
		||||
@@ -288,8 +277,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
 | 
			
		||||
        // Read inventory
 | 
			
		||||
        ListNBT nbttaglist = nbt.getList( "Items", Constants.NBT.TAG_COMPOUND );
 | 
			
		||||
        m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
        m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
        m_inventory.clear();
 | 
			
		||||
        m_previousInventory.clear();
 | 
			
		||||
        for( int i = 0; i < nbttaglist.size(); i++ )
 | 
			
		||||
        {
 | 
			
		||||
            CompoundNBT tag = nbttaglist.getCompound( i );
 | 
			
		||||
@@ -396,7 +385,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        return m_brain.getToolRenderAngle( side, f );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOwningPlayer( GameProfile player )
 | 
			
		||||
    void setOwningPlayer( GameProfile player )
 | 
			
		||||
    {
 | 
			
		||||
        m_brain.setOwningPlayer( player );
 | 
			
		||||
        markDirty();
 | 
			
		||||
@@ -424,44 +413,26 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack getStackInSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        if( slot >= 0 && slot < INVENTORY_SIZE )
 | 
			
		||||
        {
 | 
			
		||||
            synchronized( m_inventory )
 | 
			
		||||
            {
 | 
			
		||||
                return m_inventory.get( slot );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ItemStack.EMPTY;
 | 
			
		||||
        return slot >= 0 && slot < INVENTORY_SIZE ? m_inventory.get( slot ) : ItemStack.EMPTY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack removeStackFromSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack result = getStackInSlot( slot );
 | 
			
		||||
        setInventorySlotContents( slot, ItemStack.EMPTY );
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack decrStackSize( int slot, int count )
 | 
			
		||||
    {
 | 
			
		||||
        if( count == 0 )
 | 
			
		||||
        {
 | 
			
		||||
            return ItemStack.EMPTY;
 | 
			
		||||
        }
 | 
			
		||||
        if( count == 0 ) return ItemStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        ItemStack stack = getStackInSlot( slot );
 | 
			
		||||
            if( stack.isEmpty() )
 | 
			
		||||
            {
 | 
			
		||||
                return ItemStack.EMPTY;
 | 
			
		||||
            }
 | 
			
		||||
        if( stack.isEmpty() ) return ItemStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
        if( stack.getCount() <= count )
 | 
			
		||||
        {
 | 
			
		||||
@@ -473,28 +444,19 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        onInventoryDefinitelyChanged();
 | 
			
		||||
        return part;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setInventorySlotContents( int i, @Nonnull ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        if( i >= 0 && i < INVENTORY_SIZE )
 | 
			
		||||
        {
 | 
			
		||||
            synchronized( m_inventory )
 | 
			
		||||
            {
 | 
			
		||||
                if( !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) )
 | 
			
		||||
        if( i >= 0 && i < INVENTORY_SIZE && !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) )
 | 
			
		||||
        {
 | 
			
		||||
            m_inventory.set( i, stack );
 | 
			
		||||
            onInventoryDefinitelyChanged();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clear()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
    {
 | 
			
		||||
        boolean changed = false;
 | 
			
		||||
        for( int i = 0; i < INVENTORY_SIZE; i++ )
 | 
			
		||||
@@ -505,19 +467,14 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
                changed = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
            if( changed )
 | 
			
		||||
            {
 | 
			
		||||
                onInventoryDefinitelyChanged();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if( changed ) onInventoryDefinitelyChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void markDirty()
 | 
			
		||||
    {
 | 
			
		||||
        super.markDirty();
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
        if( !m_inventoryChanged )
 | 
			
		||||
        {
 | 
			
		||||
            for( int n = 0; n < getSizeInventory(); n++ )
 | 
			
		||||
@@ -530,7 +487,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isUsableByPlayer( @Nonnull PlayerEntity player )
 | 
			
		||||
@@ -587,8 +543,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    public void transferStateFrom( TileTurtle copy )
 | 
			
		||||
    {
 | 
			
		||||
        super.transferStateFrom( copy );
 | 
			
		||||
        m_inventory = copy.m_inventory;
 | 
			
		||||
        m_previousInventory = copy.m_previousInventory;
 | 
			
		||||
        Collections.copy( m_inventory, copy.m_inventory );
 | 
			
		||||
        Collections.copy( m_previousInventory, copy.m_previousInventory );
 | 
			
		||||
        m_inventoryChanged = copy.m_inventoryChanged;
 | 
			
		||||
        m_brain = copy.m_brain;
 | 
			
		||||
        m_brain.setOwner( this );
 | 
			
		||||
 
 | 
			
		||||
@@ -248,7 +248,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
 | 
			
		||||
        boolean canHarvest = state.canHarvestBlock( world, blockPosition, turtlePlayer );
 | 
			
		||||
        boolean canBreak = state.removedByPlayer( world, blockPosition, turtlePlayer, canHarvest, fluidState );
 | 
			
		||||
        if( canBreak ) state.getBlock().onPlayerDestroy( world, blockPosition, state );
 | 
			
		||||
        if( canHarvest )
 | 
			
		||||
        if( canHarvest && canBreak )
 | 
			
		||||
        {
 | 
			
		||||
            state.getBlock().harvestBlock( world, turtlePlayer, blockPosition, state, tile, turtlePlayer.getHeldItemMainhand() );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
--- The @{craftos.expect} library provides helper functions for verifying that
 | 
			
		||||
-- function arguments are well-formed and of the correct type.
 | 
			
		||||
--
 | 
			
		||||
-- @module craftos.expect
 | 
			
		||||
 | 
			
		||||
local native_select, native_type = select, type
 | 
			
		||||
 | 
			
		||||
--- Expect an argument to have a specific type.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam int index The 1-based argument index.
 | 
			
		||||
-- @param value The argument's value.
 | 
			
		||||
-- @tparam string ... The allowed types of the argument.
 | 
			
		||||
-- @throws If the value is not one of the allowed types.
 | 
			
		||||
local function expect(index, value, ...)
 | 
			
		||||
    local t = native_type(value)
 | 
			
		||||
    for i = 1, native_select("#", ...) do
 | 
			
		||||
        if t == native_select(i, ...) then return true end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    local types = table.pack(...)
 | 
			
		||||
    for i = types.n, 1, -1 do
 | 
			
		||||
        if types[i] == "nil" then table.remove(types, i) end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    local type_names
 | 
			
		||||
    if #types <= 1 then
 | 
			
		||||
        type_names = tostring(...)
 | 
			
		||||
    else
 | 
			
		||||
        type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    -- If we can determine the function name with a high level of confidence, try to include it.
 | 
			
		||||
    local name
 | 
			
		||||
    if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
 | 
			
		||||
        local ok, info = pcall(debug.getinfo, 3, "nS")
 | 
			
		||||
        if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if name then
 | 
			
		||||
        error( ("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3 )
 | 
			
		||||
    else
 | 
			
		||||
        error( ("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3 )
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return { expect = expect }
 | 
			
		||||
@@ -1,48 +1,19 @@
 | 
			
		||||
local native_select, native_type = select, type
 | 
			
		||||
 | 
			
		||||
--- Expect an argument to have a specific type.
 | 
			
		||||
-- Load in expect from the module path.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam int index The 1-based argument index.
 | 
			
		||||
-- @param value The argument's value.
 | 
			
		||||
-- @tparam string ... The allowed types of the argument.
 | 
			
		||||
-- @throws If the value is not one of the allowed types.
 | 
			
		||||
local function expect(index, value, ...)
 | 
			
		||||
    local t = native_type(value)
 | 
			
		||||
    for i = 1, native_select("#", ...) do
 | 
			
		||||
        if t == native_select(i, ...) then return true end
 | 
			
		||||
    end
 | 
			
		||||
-- Ideally we'd use require, but that is part of the shell, and so is not
 | 
			
		||||
-- available to the BIOS or any APIs. All APIs load this using dofile, but that
 | 
			
		||||
-- has not been defined at this point.
 | 
			
		||||
local expect
 | 
			
		||||
 | 
			
		||||
    local types = table.pack(...)
 | 
			
		||||
    for i = types.n, 1, -1 do
 | 
			
		||||
        if types[i] == "nil" then table.remove(types, i) end
 | 
			
		||||
    end
 | 
			
		||||
do
 | 
			
		||||
    local h = fs.open("rom/modules/main/cc/expect.lua", "r")
 | 
			
		||||
    local f, err = loadstring(h.readAll(), "@expect.lua")
 | 
			
		||||
    h.close()
 | 
			
		||||
 | 
			
		||||
    local type_names
 | 
			
		||||
    if #types <= 1 then
 | 
			
		||||
        type_names = tostring(...)
 | 
			
		||||
    else
 | 
			
		||||
        type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    -- If we can determine the function name with a high level of confidence, try to include it.
 | 
			
		||||
    local name
 | 
			
		||||
    if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
 | 
			
		||||
        local ok, info = pcall(debug.getinfo, 3, "nS")
 | 
			
		||||
        if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    if name then
 | 
			
		||||
        error( ("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3 )
 | 
			
		||||
    else
 | 
			
		||||
        error( ("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3 )
 | 
			
		||||
    end
 | 
			
		||||
    if not f then error(err) end
 | 
			
		||||
    expect = f().expect
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- We expose expect in the global table as APIs need to access it, but give it
 | 
			
		||||
-- a non-identifier name - meaning it does not show up in auto-completion.
 | 
			
		||||
-- expect is an internal function, and should not be used by users.
 | 
			
		||||
_G["~expect"] = expect
 | 
			
		||||
 | 
			
		||||
if _VERSION == "Lua 5.1" then
 | 
			
		||||
    -- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
 | 
			
		||||
    local type = type
 | 
			
		||||
@@ -568,23 +539,28 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
 | 
			
		||||
    return sLine
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function loadfile( _sFile, _tEnv )
 | 
			
		||||
    expect(1, _sFile, "string")
 | 
			
		||||
    expect(2, _tEnv, "table", "nil")
 | 
			
		||||
function loadfile( filename, mode, env )
 | 
			
		||||
    -- Support the previous `loadfile(filename, env)` form instead.
 | 
			
		||||
    if type(mode) == "table" and env == nil then
 | 
			
		||||
        mode, env = nil, mode
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    local file = fs.open( _sFile, "r" )
 | 
			
		||||
    if file then
 | 
			
		||||
        local func, err = load( file.readAll(), "@" .. fs.getName( _sFile ), "t", _tEnv )
 | 
			
		||||
    expect(1, filename, "string")
 | 
			
		||||
    expect(2, mode, "string", "nil")
 | 
			
		||||
    expect(3, env, "table", "nil")
 | 
			
		||||
 | 
			
		||||
    local file = fs.open( filename, "r" )
 | 
			
		||||
    if not file then return nil, "File not found" end
 | 
			
		||||
 | 
			
		||||
    local func, err = load( file.readAll(), "@" .. fs.getName( filename ), mode, env )
 | 
			
		||||
    file.close()
 | 
			
		||||
    return func, err
 | 
			
		||||
    end
 | 
			
		||||
    return nil, "File not found"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function dofile( _sFile )
 | 
			
		||||
    expect(1, _sFile, "string")
 | 
			
		||||
 | 
			
		||||
    local fnFile, e = loadfile( _sFile, _G )
 | 
			
		||||
    local fnFile, e = loadfile( _sFile, nil, _G )
 | 
			
		||||
    if fnFile then
 | 
			
		||||
        return fnFile()
 | 
			
		||||
    else
 | 
			
		||||
@@ -600,7 +576,7 @@ function os.run( _tEnv, _sPath, ... )
 | 
			
		||||
    local tArgs = table.pack( ... )
 | 
			
		||||
    local tEnv = _tEnv
 | 
			
		||||
    setmetatable( tEnv, { __index = _G } )
 | 
			
		||||
    local fnFile, err = loadfile( _sPath, tEnv )
 | 
			
		||||
    local fnFile, err = loadfile( _sPath, nil, tEnv )
 | 
			
		||||
    if fnFile then
 | 
			
		||||
        local ok, err = pcall( function()
 | 
			
		||||
            fnFile( table.unpack( tArgs, 1, tArgs.n ) )
 | 
			
		||||
@@ -634,7 +610,7 @@ function os.loadAPI( _sPath )
 | 
			
		||||
 | 
			
		||||
    local tEnv = {}
 | 
			
		||||
    setmetatable( tEnv, { __index = _G } )
 | 
			
		||||
    local fnAPI, err = loadfile( _sPath, tEnv )
 | 
			
		||||
    local fnAPI, err = loadfile( _sPath, nil, tEnv )
 | 
			
		||||
    if fnAPI then
 | 
			
		||||
        local ok, err = pcall( fnAPI )
 | 
			
		||||
        if not ok then
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
-- Colors
 | 
			
		||||
white = 1
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
CHANNEL_GPS = 65534
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local sPath = "/rom/help"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
-- Definition for the IO API
 | 
			
		||||
 | 
			
		||||
local expect, typeOf = _G["~expect"], _G.type
 | 
			
		||||
local expect, typeOf = dofile("rom/modules/main/cc/expect.lua").expect, _G.type
 | 
			
		||||
 | 
			
		||||
--- If we return nil then close the file, as we've reached the end.
 | 
			
		||||
-- We use this weird wrapper function as we wish to preserve the varargs
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
-- taught me anything, it's that emulating LWJGL's weird key handling is nigh-on
 | 
			
		||||
-- impossible.
 | 
			
		||||
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local tKeys = {}
 | 
			
		||||
tKeys[32] = 'space'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local function drawPixelInternal( xPos, yPos )
 | 
			
		||||
    term.setCursorPos( xPos, yPos )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local native = peripheral
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
CHANNEL_BROADCAST = 65535
 | 
			
		||||
CHANNEL_REPEAT = 65533
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local tSettings = {}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local native = (term.native and term.native()) or term
 | 
			
		||||
local redirectTarget = native
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
function slowWrite( sText, nRate )
 | 
			
		||||
    expect(2, nRate, "number", "nil")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local tHex = {
 | 
			
		||||
    [ colors.white ] = "0",
 | 
			
		||||
@@ -388,6 +388,16 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
 | 
			
		||||
        return nBackgroundColor
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function window.getLine(y)
 | 
			
		||||
        if type(y) ~= "number" then expect(1, y, "number") end
 | 
			
		||||
 | 
			
		||||
        if y < 1 or y > nHeight then
 | 
			
		||||
            error("Line is out of range.", 2)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    -- Other functions
 | 
			
		||||
    function window.setVisible( bVis )
 | 
			
		||||
        if type(bVis) ~= "boolean" then expect(1, bVis, "boolean") end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,20 @@
 | 
			
		||||
New features in CC: Tweaked 1.84.0
 | 
			
		||||
 | 
			
		||||
* Improve validation in rename, copy and delete programs
 | 
			
		||||
* Add window.getLine - the inverse of blit
 | 
			
		||||
* turtle.refuel no longer consumes more fuel than needed
 | 
			
		||||
* Add "cc.expect" module, for improved argument type checks
 | 
			
		||||
* Mount the ROM from all mod jars, not just CC's
 | 
			
		||||
 | 
			
		||||
And several bug fixes:
 | 
			
		||||
* Ensure file error messages use the absolute correct path
 | 
			
		||||
* Fix NPE when closing a file multiple times.
 | 
			
		||||
* Do not load chunks when calling writeDescription.
 | 
			
		||||
* Fix the signature of loadfile
 | 
			
		||||
* Fix turtles harvesting blocks multiple times
 | 
			
		||||
* Improve thread-safety of various peripherals
 | 
			
		||||
* Prevent printed pages having massive/malformed titles
 | 
			
		||||
 | 
			
		||||
# New features in CC: Tweaked 1.83.1
 | 
			
		||||
 | 
			
		||||
* Add several new MOTD messages (JakobDev)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,18 @@
 | 
			
		||||
New features in CC: Tweaked 1.83.1
 | 
			
		||||
New features in CC: Tweaked 1.84.0
 | 
			
		||||
 | 
			
		||||
* Add several new MOTD messages (JakobDev)
 | 
			
		||||
* Improve validation in rename, copy and delete programs
 | 
			
		||||
* Add window.getLine - the inverse of blit
 | 
			
		||||
* turtle.refuel no longer consumes more fuel than needed
 | 
			
		||||
* Add "cc.expect" module, for improved argument type checks
 | 
			
		||||
* Mount the ROM from all mod jars, not just CC's
 | 
			
		||||
 | 
			
		||||
And several bug fixes:
 | 
			
		||||
* Fix type check in `rednet.lookup`
 | 
			
		||||
* Error if turtle and pocket computer programs are run on the wrong system (JakobDev)
 | 
			
		||||
* Do not discard varargs after a nil.
 | 
			
		||||
* Ensure file error messages use the absolute correct path
 | 
			
		||||
* Fix NPE when closing a file multiple times.
 | 
			
		||||
* Do not load chunks when calling writeDescription.
 | 
			
		||||
* Fix the signature of loadfile
 | 
			
		||||
* Fix turtles harvesting blocks multiple times
 | 
			
		||||
* Improve thread-safety of various peripherals
 | 
			
		||||
* Prevent printed pages having massive/malformed titles
 | 
			
		||||
 | 
			
		||||
Type "help changelog" to see the full version history.
 | 
			
		||||
 
 | 
			
		||||
@@ -23,3 +23,4 @@ getPosition()
 | 
			
		||||
reposition( x, y, width, height )
 | 
			
		||||
getPaletteColor( color )
 | 
			
		||||
setPaletteColor( color, r, g, b )
 | 
			
		||||
getLine()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
-- Setup process switching
 | 
			
		||||
local parentTerm = term.current()
 | 
			
		||||
 
 | 
			
		||||
@@ -342,7 +342,6 @@ local tMenuFuncs = {
 | 
			
		||||
                redrawMenu()
 | 
			
		||||
                term.redirect( printerTerminal )
 | 
			
		||||
 | 
			
		||||
                local timer = os.startTimer(0.5)
 | 
			
		||||
                sleep(0.5)
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
local expect = _G["~expect"]
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
local multishell = multishell
 | 
			
		||||
local parentShell = shell
 | 
			
		||||
@@ -56,7 +56,7 @@ local function createShellEnv( sDir )
 | 
			
		||||
                    sPath = fs.combine(sDir, sPath)
 | 
			
		||||
                end
 | 
			
		||||
                if fs.exists(sPath) and not fs.isDir(sPath) then
 | 
			
		||||
                    local fnFile, sError = loadfile( sPath, tEnv )
 | 
			
		||||
                    local fnFile, sError = loadfile( sPath, nil, tEnv )
 | 
			
		||||
                    if fnFile then
 | 
			
		||||
                        return fnFile, sPath
 | 
			
		||||
                    else
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ public class ComputerTestDelegate
 | 
			
		||||
        try( WritableByteChannel channel = mount.openChannelForWrite( "startup.lua" );
 | 
			
		||||
             Writer writer = Channels.newWriter( channel, StandardCharsets.UTF_8.newEncoder(), -1 ) )
 | 
			
		||||
        {
 | 
			
		||||
            writer.write( "loadfile('test/mcfly.lua', _ENV)('test/spec') cct_test.finish()" );
 | 
			
		||||
            writer.write( "loadfile('test/mcfly.lua', nil, _ENV)('test/spec') cct_test.finish()" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        computer = new Computer( new BasicEnvironment( mount ), term, 0 );
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ public class ComputerBootstrap
 | 
			
		||||
    {
 | 
			
		||||
        MemoryMount mount = new MemoryMount()
 | 
			
		||||
            .addFile( "test.lua", program )
 | 
			
		||||
            .addFile( "startup", "assertion.assert(pcall(loadfile('test.lua', _ENV))) os.shutdown()" );
 | 
			
		||||
            .addFile( "startup", "assertion.assert(pcall(loadfile('test.lua', nil, _ENV))) os.shutdown()" );
 | 
			
		||||
 | 
			
		||||
        run( mount, x -> { } );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,18 +27,58 @@ local function check(func, arg, ty, val)
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- A stub - wraps a value within a a table,
 | 
			
		||||
local stub_mt = {}
 | 
			
		||||
stub_mt.__index = stub_mt
 | 
			
		||||
 | 
			
		||||
--- Revert this stub, restoring the previous value.
 | 
			
		||||
--
 | 
			
		||||
-- Note, a stub can only be reverted once.
 | 
			
		||||
function stub_mt:revert()
 | 
			
		||||
    if not self.active then return end
 | 
			
		||||
 | 
			
		||||
    self.active = false
 | 
			
		||||
    rawset(self.stubbed_in, self.key, self.original)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local active_stubs = {}
 | 
			
		||||
 | 
			
		||||
--- Stub a global variable with a specific value
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string var The variable to stub
 | 
			
		||||
-- @param value The value to stub it with
 | 
			
		||||
local function stub(tbl, var, value)
 | 
			
		||||
    check('stub', 1, 'table', tbl)
 | 
			
		||||
    check('stub', 2, 'string', var)
 | 
			
		||||
local function default_stub() end
 | 
			
		||||
 | 
			
		||||
    table.insert(active_stubs, { tbl = tbl, var = var, value = tbl[var] })
 | 
			
		||||
    rawset(tbl, var, value)
 | 
			
		||||
--- Stub a table entry with a new value.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam table
 | 
			
		||||
-- @tparam string key The variable to stub
 | 
			
		||||
-- @param[opt] value The value to stub it with. If this is a function, one can
 | 
			
		||||
-- use the various stub expectation methods to determine what it was called
 | 
			
		||||
-- with. Defaults to an empty function - pass @{nil} in explicitly to set the
 | 
			
		||||
-- value to nil.
 | 
			
		||||
-- @treturn Stub The resulting stub
 | 
			
		||||
local function stub(tbl, key, ...)
 | 
			
		||||
    check('stub', 1, 'table', tbl)
 | 
			
		||||
    check('stub', 2, 'string', key)
 | 
			
		||||
 | 
			
		||||
    local stub = setmetatable({
 | 
			
		||||
        active = true,
 | 
			
		||||
        stubbed_in = tbl,
 | 
			
		||||
        key = key,
 | 
			
		||||
        original = rawget(tbl, key),
 | 
			
		||||
    }, stub_mt)
 | 
			
		||||
 | 
			
		||||
    local value = ...
 | 
			
		||||
    if select('#', ...) == 0 then value = default_stub end
 | 
			
		||||
    if type(value) == "function" then
 | 
			
		||||
        local arguments, delegate = {}, value
 | 
			
		||||
        stub.arguments = arguments
 | 
			
		||||
        value = function(...)
 | 
			
		||||
            arguments[#arguments + 1] = table.pack(...)
 | 
			
		||||
            return delegate(...)
 | 
			
		||||
        end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    table.insert(active_stubs, stub)
 | 
			
		||||
    rawset(tbl, key, value)
 | 
			
		||||
    return stub
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Capture the current global state of the computer
 | 
			
		||||
@@ -51,16 +91,14 @@ local function push_state()
 | 
			
		||||
        output = io.output(),
 | 
			
		||||
        dir = shell.dir(),
 | 
			
		||||
        path = shell.path(),
 | 
			
		||||
        aliases = shell.aliases(),
 | 
			
		||||
        stubs = stubs,
 | 
			
		||||
    }
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Restore the global state of the computer to a previous version
 | 
			
		||||
local function pop_state(state)
 | 
			
		||||
    for i = #active_stubs, 1, -1 do
 | 
			
		||||
        local stub = active_stubs[i]
 | 
			
		||||
        rawset(stub.tbl, stub.var, stub.value)
 | 
			
		||||
    end
 | 
			
		||||
    for i = #active_stubs, 1, -1 do active_stubs[i]:revert() end
 | 
			
		||||
 | 
			
		||||
    active_stubs = state.stubs
 | 
			
		||||
 | 
			
		||||
@@ -69,6 +107,14 @@ local function pop_state(state)
 | 
			
		||||
    io.output(state.output)
 | 
			
		||||
    shell.setDir(state.dir)
 | 
			
		||||
    shell.setPath(state.path)
 | 
			
		||||
 | 
			
		||||
    local aliases = shell.aliases()
 | 
			
		||||
    for k in pairs(aliases) do
 | 
			
		||||
        if not state.aliases[k] then shell.clearAlias(k) end
 | 
			
		||||
    end
 | 
			
		||||
    for k, v in pairs(state.aliases) do
 | 
			
		||||
        if aliases[k] ~= v then shell.setAlias(k, v) end
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local error_mt = { __tostring = function(self) return self.message end }
 | 
			
		||||
@@ -210,6 +256,16 @@ local function matches(eq, exact, left, right)
 | 
			
		||||
    return true
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function pairwise_equal(left, right)
 | 
			
		||||
    if left.n ~= right.n then return false end
 | 
			
		||||
 | 
			
		||||
    for i = 1, left.n do
 | 
			
		||||
        if left[i] ~= right[i] then return false end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    return true
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Assert that this expectation is structurally equivalent to
 | 
			
		||||
-- the provided object.
 | 
			
		||||
--
 | 
			
		||||
@@ -236,6 +292,70 @@ function expect_mt:matches(value)
 | 
			
		||||
    return self
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Assert that this stub was called a specific number of times.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam[opt] number The exact number of times the function must be called.
 | 
			
		||||
-- If not given just require the function to be called at least once.
 | 
			
		||||
-- @raises If this function was not called the expected number of times.
 | 
			
		||||
function expect_mt:called(times)
 | 
			
		||||
    if getmetatable(self.value) ~= stub_mt or self.value.arguments == nil then
 | 
			
		||||
        fail(("Expected stubbed function, got %s"):format(type(self.value)))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    local called = #self.value.arguments
 | 
			
		||||
 | 
			
		||||
    if times == nil then
 | 
			
		||||
        if called == 0 then
 | 
			
		||||
            fail("Expected stub to be called\nbut it was not.")
 | 
			
		||||
        end
 | 
			
		||||
    else
 | 
			
		||||
        check('stub', 1, 'number', times)
 | 
			
		||||
        if called ~= times then
 | 
			
		||||
            fail(("Expected stub to be called %d times\nbut was called %d times."):format(times, called))
 | 
			
		||||
        end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    return self
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function called_with_check(eq, self, ...)
 | 
			
		||||
    if getmetatable(self.value) ~= stub_mt or self.value.arguments == nil then
 | 
			
		||||
        fail(("Expected stubbed function, got %s"):format(type(self.value)))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    local exp_args = table.pack(...)
 | 
			
		||||
    local actual_args = self.value.arguments
 | 
			
		||||
    for i = 1, #actual_args do
 | 
			
		||||
        if eq(actual_args[i], exp_args) then return self end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    local head = ("Expected stub to be called with %s\nbut was"):format(format(exp_args))
 | 
			
		||||
    if #actual_args == 0 then
 | 
			
		||||
        fail(head .. " not called at all")
 | 
			
		||||
    elseif #actual_args == 1 then
 | 
			
		||||
        fail(("%s called with %s."):format(head, format(actual_args[1])))
 | 
			
		||||
    else
 | 
			
		||||
        local lines = { head .. " called with:" }
 | 
			
		||||
        for i = 1, #actual_args do lines[i + 1] = " - " .. format(actual_args[i]) end
 | 
			
		||||
 | 
			
		||||
        fail(table.concat(lines, "\n"))
 | 
			
		||||
    end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Assert that this stub was called with a set of arguments
 | 
			
		||||
--
 | 
			
		||||
-- Arguments are compared using exact equality.
 | 
			
		||||
function expect_mt:called_with(...)
 | 
			
		||||
    return called_with_check(pairwise_equal, self, ...)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
--- Assert that this stub was called with a set of arguments
 | 
			
		||||
--
 | 
			
		||||
-- Arguments are compared using matching.
 | 
			
		||||
function expect_mt:called_with_matching(...)
 | 
			
		||||
    return called_with_check(matches, self, ...)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local expect = setmetatable( {
 | 
			
		||||
    --- Construct an expectation on the error message calling this function
 | 
			
		||||
    -- produces
 | 
			
		||||
@@ -381,7 +501,7 @@ do
 | 
			
		||||
            if fs.isDir(file) then
 | 
			
		||||
                run_in(file)
 | 
			
		||||
            elseif file:sub(-#suffix) == suffix then
 | 
			
		||||
                local fun, err = loadfile(file, env)
 | 
			
		||||
                local fun, err = loadfile(file, nil, env)
 | 
			
		||||
                if not fun then
 | 
			
		||||
                    do_test { name = file:sub(#root_dir + 2), error = { message = err } }
 | 
			
		||||
                else
 | 
			
		||||
 
 | 
			
		||||
@@ -120,4 +120,21 @@ describe("The window library", function()
 | 
			
		||||
            expect.error(w.reposition, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
 | 
			
		||||
        end)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    describe("Window.getLine", function()
 | 
			
		||||
        it("validates arguments", function()
 | 
			
		||||
            local w = mk()
 | 
			
		||||
            w.getLine(1)
 | 
			
		||||
            local _, y = w.getSize()
 | 
			
		||||
            expect.error(w.getLine, nil):eq("bad argument #1 (expected number, got nil)")
 | 
			
		||||
            expect.error(w.getLine, 0):eq("Line is out of range.")
 | 
			
		||||
            expect.error(w.getLine, y + 1):eq("Line is out of range.")
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("provides a line's contents", function()
 | 
			
		||||
            local w = mk()
 | 
			
		||||
            w.blit("test", "aaaa", "4444")
 | 
			
		||||
            expect({ w.getLine(1) }):same { "test ", "aaaa0", "4444f" }
 | 
			
		||||
        end)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,36 +1,4 @@
 | 
			
		||||
describe("The Lua base library", function()
 | 
			
		||||
    describe("expect", function()
 | 
			
		||||
        local e = _G["~expect"]
 | 
			
		||||
 | 
			
		||||
        it("checks a single type", function()
 | 
			
		||||
            expect(e(1, "test", "string")):eq(true)
 | 
			
		||||
            expect(e(1, 2, "number")):eq(true)
 | 
			
		||||
 | 
			
		||||
            expect.error(e, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)")
 | 
			
		||||
            expect.error(e, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)")
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("checks multiple types", function()
 | 
			
		||||
            expect(e(1, "test", "string", "number")):eq(true)
 | 
			
		||||
            expect(e(1, 2, "string", "number")):eq(true)
 | 
			
		||||
 | 
			
		||||
            expect.error(e, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)")
 | 
			
		||||
            expect.error(e, 2, false, "string", "table", "number", "nil")
 | 
			
		||||
                  :eq("bad argument #2 (expected string, table or number, got boolean)")
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("includes the function name", function()
 | 
			
		||||
            local function worker()
 | 
			
		||||
                expect(e(1, nil, "string")):eq(true)
 | 
			
		||||
            end
 | 
			
		||||
            local function trampoline()
 | 
			
		||||
                worker()
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            expect.error(trampoline):eq("base_spec.lua:27: bad argument #1 to 'worker' (expected string, got nil)")
 | 
			
		||||
        end)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    describe("sleep", function()
 | 
			
		||||
        it("validates arguments", function()
 | 
			
		||||
            sleep(0)
 | 
			
		||||
@@ -48,18 +16,43 @@ describe("The Lua base library", function()
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    describe("loadfile", function()
 | 
			
		||||
        local function make_file()
 | 
			
		||||
            local tmp = fs.open("test-files/out.lua", "w")
 | 
			
		||||
            tmp.write("return _ENV")
 | 
			
		||||
            tmp.close()
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it("validates arguments", function()
 | 
			
		||||
            loadfile("")
 | 
			
		||||
            loadfile("", {})
 | 
			
		||||
            loadfile("", "")
 | 
			
		||||
            loadfile("", "", {})
 | 
			
		||||
 | 
			
		||||
            expect.error(loadfile, nil):eq("bad argument #1 (expected string, got nil)")
 | 
			
		||||
            expect.error(loadfile, "", false):eq("bad argument #2 (expected table, got boolean)")
 | 
			
		||||
            expect.error(loadfile, "", false):eq("bad argument #2 (expected string, got boolean)")
 | 
			
		||||
            expect.error(loadfile, "", "", false):eq("bad argument #3 (expected table, got boolean)")
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("prefixes the filename with @", function()
 | 
			
		||||
            local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
 | 
			
		||||
            expect(info):matches { short_src = "startup.lua", source = "@startup.lua" }
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("loads a file with the global environment", function()
 | 
			
		||||
            make_file()
 | 
			
		||||
            expect(loadfile("test-files/out.lua")()):eq(_G)
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("loads a file with a specific environment", function()
 | 
			
		||||
            make_file()
 | 
			
		||||
            local env = {}
 | 
			
		||||
            expect(loadfile("test-files/out.lua", nil, env)()):eq(env)
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("supports the old-style argument form", function()
 | 
			
		||||
            make_file()
 | 
			
		||||
            local env = {}
 | 
			
		||||
            expect(loadfile("test-files/out.lua", env)()):eq(env)
 | 
			
		||||
        end)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    describe("dofile", function()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								src/test/resources/test-rom/spec/modules/cc/expect_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/test/resources/test-rom/spec/modules/cc/expect_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
describe("cc.expect", function()
 | 
			
		||||
    local e = require("cc.expect")
 | 
			
		||||
 | 
			
		||||
    it("checks a single type", function()
 | 
			
		||||
        expect(e.expect(1, "test", "string")):eq(true)
 | 
			
		||||
        expect(e.expect(1, 2, "number")):eq(true)
 | 
			
		||||
 | 
			
		||||
        expect.error(e.expect, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)")
 | 
			
		||||
        expect.error(e.expect, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)")
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("checks multiple types", function()
 | 
			
		||||
        expect(e.expect(1, "test", "string", "number")):eq(true)
 | 
			
		||||
        expect(e.expect(1, 2, "string", "number")):eq(true)
 | 
			
		||||
 | 
			
		||||
        expect.error(e.expect, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)")
 | 
			
		||||
        expect.error(e.expect, 2, false, "string", "table", "number", "nil")
 | 
			
		||||
              :eq("bad argument #2 (expected string, table or number, got boolean)")
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("includes the function name", function()
 | 
			
		||||
        local function worker()
 | 
			
		||||
            expect(e.expect(1, nil, "string")):eq(true)
 | 
			
		||||
        end
 | 
			
		||||
        local function trampoline()
 | 
			
		||||
            worker()
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        expect.error(trampoline):eq("expect_spec.lua:26: bad argument #1 to 'worker' (expected string, got nil)")
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The bg program", function()
 | 
			
		||||
    it("opens a tab in the background", function()
 | 
			
		||||
        local openTab = stub(shell, "openTab", function() return 12 end)
 | 
			
		||||
        local switchTab = stub(shell, "switchTab")
 | 
			
		||||
        capture(stub, "bg")
 | 
			
		||||
        expect(openTab):called_with("shell")
 | 
			
		||||
        expect(switchTab):called(0)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The fg program", function()
 | 
			
		||||
    it("opens the shell in the foreground", function()
 | 
			
		||||
        local openTab = stub(shell, "openTab", function() return 12 end)
 | 
			
		||||
        local switchTab = stub(shell, "switchTab")
 | 
			
		||||
        capture(stub, "fg")
 | 
			
		||||
        expect(openTab):called_with("shell")
 | 
			
		||||
        expect(switchTab):called_with(12)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										28
									
								
								src/test/resources/test-rom/spec/programs/alias_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/test/resources/test-rom/spec/programs/alias_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The alias program", function()
 | 
			
		||||
    it("displays its usage when given too many arguments", function()
 | 
			
		||||
        expect(capture(stub, "alias a b c"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: alias <alias> <program>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("lists aliases", function()
 | 
			
		||||
        local pagedTabulate = stub(textutils, "pagedTabulate", function(x) print(table.unpack(x)) end)
 | 
			
		||||
        stub(shell, "aliases", function() return { cp = "copy" } end)
 | 
			
		||||
        expect(capture(stub, "alias"))
 | 
			
		||||
            :matches { ok = true, output = "cp:copy\n", error = "" }
 | 
			
		||||
        expect(pagedTabulate):called_with_matching({ "cp:copy" })
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("sets an alias", function()
 | 
			
		||||
        local setAlias = stub(shell, "setAlias")
 | 
			
		||||
        capture(stub, "alias test Hello")
 | 
			
		||||
        expect(setAlias):called_with("test", "Hello")
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("clears an alias", function()
 | 
			
		||||
        local clearAlias = stub(shell, "clearAlias")
 | 
			
		||||
        capture(stub, "alias test")
 | 
			
		||||
        expect(clearAlias):called_with("test")
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -1,19 +1,18 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The cd program", function()
 | 
			
		||||
 | 
			
		||||
    it("cd into a directory", function()
 | 
			
		||||
        shell.run("cd /rom/programs")
 | 
			
		||||
        
 | 
			
		||||
        expect(shell.dir()):eq("rom/programs")
 | 
			
		||||
    it("changes into a directory", function()
 | 
			
		||||
        local setDir = stub(shell, "setDir")
 | 
			
		||||
        capture(stub, "cd /rom/programs")
 | 
			
		||||
        expect(setDir):called_with("rom/programs")
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("cd into a not existing directory", function()
 | 
			
		||||
    it("does not move into a non-existent directory", function()
 | 
			
		||||
        expect(capture(stub, "cd /rom/nothing"))
 | 
			
		||||
            :matches { ok = true, output = "Not a directory\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("displays the usage with no arguments", function()
 | 
			
		||||
    it("displays the usage when given no arguments", function()
 | 
			
		||||
        expect(capture(stub, "cd"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: cd <path>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/test/resources/test-rom/spec/programs/clear_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/test/resources/test-rom/spec/programs/clear_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The clear program", function()
 | 
			
		||||
    it("clears the screen", function()
 | 
			
		||||
        local clear = stub(term, "clear")
 | 
			
		||||
        local setCursorPos = stub(term, "setCursorPos")
 | 
			
		||||
 | 
			
		||||
        capture(stub, "clear")
 | 
			
		||||
 | 
			
		||||
        expect(clear):called(1)
 | 
			
		||||
        expect(setCursorPos):called_with(1, 1)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The commands program", function()
 | 
			
		||||
    it("displays an error without the commands api", function()
 | 
			
		||||
        stub(_G, "commands", nil)
 | 
			
		||||
        expect(capture(stub, "/rom/programs/command/commands.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Command Computer.\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("lists commands", function()
 | 
			
		||||
        local pagedTabulate = stub(textutils, "pagedTabulate", function(x) print(table.unpack(x)) end)
 | 
			
		||||
        stub(_G, "commands", {
 | 
			
		||||
            list = function() return { "computercraft" } end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/command/commands.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Available commands:\ncomputercraft\n", error = "" }
 | 
			
		||||
        expect(pagedTabulate):called_with_matching({ "computercraft" })
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The exec program", function()
 | 
			
		||||
    it("displays an error without the commands api", function()
 | 
			
		||||
        stub(_G, "commands", nil)
 | 
			
		||||
        expect(capture(stub, "/rom/programs/command/exec.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Command Computer.\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("displays its usage when given no argument", function()
 | 
			
		||||
        stub(_G, "commands", {})
 | 
			
		||||
        expect(capture(stub, "/rom/programs/command/exec.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Usage: exec <command>\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("runs a command", function()
 | 
			
		||||
        stub(_G, "commands", {
 | 
			
		||||
            exec = function() return true, {"Hello World!"} end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
 | 
			
		||||
            :matches { ok = true, output = "Success\nHello World!\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("reports command failures", function()
 | 
			
		||||
        stub(_G,"commands",{
 | 
			
		||||
            exec = function() return false, {"Hello World!"} end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
 | 
			
		||||
            :matches { ok = true, output = "Hello World!\n", error = "Failed\n" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										16
									
								
								src/test/resources/test-rom/spec/programs/drive_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/test/resources/test-rom/spec/programs/drive_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The drive program", function()
 | 
			
		||||
    it("run the program", function()
 | 
			
		||||
        local getFreeSpace = stub(fs, "getFreeSpace", function() return 1234e4 end)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "drive"))
 | 
			
		||||
            :matches { ok = true, output = "hdd (12.3MB remaining)\n", error = "" }
 | 
			
		||||
        expect(getFreeSpace):called(1):called_with("")
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails on a non-existent path", function()
 | 
			
		||||
        expect(capture(stub, "drive /rom/nothing"))
 | 
			
		||||
            :matches { ok = true, output = "No such path\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
local testFile = require "test_helpers".testFile
 | 
			
		||||
 | 
			
		||||
describe("The edit program", function()
 | 
			
		||||
 | 
			
		||||
    it("displays its usage when given no argument", function()      
 | 
			
		||||
        multishell = nil
 | 
			
		||||
        
 | 
			
		||||
        expect(capture(stub, "edit"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: edit <path>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/test/resources/test-rom/spec/programs/eject_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/test/resources/test-rom/spec/programs/eject_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The eject program", function()
 | 
			
		||||
    it("displays its usage when given no argument", function()
 | 
			
		||||
        expect(capture(stub, "eject"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: eject <drive>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails when trying to eject a non-drive", function()
 | 
			
		||||
        expect(capture(stub, "eject /rom"))
 | 
			
		||||
            :matches { ok = true, output = "Nothing in /rom drive\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										9
									
								
								src/test/resources/test-rom/spec/programs/exit_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/test/resources/test-rom/spec/programs/exit_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The exit program", function()
 | 
			
		||||
    it("exits the shell", function()
 | 
			
		||||
        local exit = stub(shell, "exit")
 | 
			
		||||
        expect(capture(stub, "exit")):matches { ok = true, combined = "" }
 | 
			
		||||
        expect(exit):called(1)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The paint program", function()
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        expect(capture(stub, "paint"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: paint <path>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										13
									
								
								src/test/resources/test-rom/spec/programs/fun/dj_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/test/resources/test-rom/spec/programs/fun/dj_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The dj program", function()
 | 
			
		||||
    it("displays its usage when given too many arguments", function()
 | 
			
		||||
        expect(capture(stub, "dj a b c"))
 | 
			
		||||
            :matches { ok = true, output = "Usages:\ndj play\ndj play <drive>\ndj stop\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails when no disks are present", function()
 | 
			
		||||
        expect(capture(stub, "dj"))
 | 
			
		||||
            :matches { ok = true, output = "No Music Discs in attached disk drives\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										10
									
								
								src/test/resources/test-rom/spec/programs/fun/hello_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/test/resources/test-rom/spec/programs/fun/hello_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The hello program", function()
 | 
			
		||||
    it("says hello", function()
 | 
			
		||||
        local slowPrint = stub(textutils, "slowPrint", function(...) return print(...) end)
 | 
			
		||||
        expect(capture(stub, "hello"))
 | 
			
		||||
            :matches { ok = true, output = "Hello World!\n", error = "" }
 | 
			
		||||
        expect(slowPrint):called(1)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										23
									
								
								src/test/resources/test-rom/spec/programs/gps_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/test/resources/test-rom/spec/programs/gps_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The gps program", function()
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        expect(capture(stub, "gps"))
 | 
			
		||||
            :matches { ok = true, output = "Usages:\ngps host\ngps host <x> <y> <z>\ngps locate\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails on a pocket computer", function()
 | 
			
		||||
        stub(_G, "pocket", {})
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "gps host"))
 | 
			
		||||
            :matches { ok = true, output = "GPS Hosts must be stationary\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("can locate the computer", function()
 | 
			
		||||
        local locate = stub(gps, "locate", function() print("Some debugging information.") end)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "gps locate"))
 | 
			
		||||
            :matches { ok = true, output = "Some debugging information.\n", error = "" }
 | 
			
		||||
        expect(locate):called_with(2, true)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										8
									
								
								src/test/resources/test-rom/spec/programs/help_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/test/resources/test-rom/spec/programs/help_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The help program", function()
 | 
			
		||||
    it("errors when there is no such help file", function()
 | 
			
		||||
        expect(capture(stub, "help nothing"))
 | 
			
		||||
            :matches { ok = true, output = "No help available\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										34
									
								
								src/test/resources/test-rom/spec/programs/label_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/test/resources/test-rom/spec/programs/label_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The label program", function()
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        expect(capture(stub, "label"))
 | 
			
		||||
            :matches { ok = true, output = "Usages:\nlabel get\nlabel get <drive>\nlabel set <text>\nlabel set <drive> <text>\nlabel clear\nlabel clear <drive>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    describe("displays the computer's label", function()
 | 
			
		||||
        it("when it is not labelled", function()
 | 
			
		||||
            stub(os, "getComputerLabel", function() return nil end)
 | 
			
		||||
            expect(capture(stub, "label get"))
 | 
			
		||||
                :matches { ok = true, output = "No Computer label\n", error = "" }
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("when it is labelled", function()
 | 
			
		||||
            stub(os, "getComputerLabel", function() return "Test" end)
 | 
			
		||||
            expect(capture(stub, "label get"))
 | 
			
		||||
                :matches { ok = true, output = "Computer label is \"Test\"\n", error = "" }
 | 
			
		||||
        end)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("sets the computer's label", function()
 | 
			
		||||
        local setComputerLabel = stub(os, "setComputerLabel")
 | 
			
		||||
        capture(stub, "label set Test")
 | 
			
		||||
        expect(setComputerLabel):called_with("Test")
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("clears the computer's label", function()
 | 
			
		||||
        local setComputerLabel = stub(os, "setComputerLabel")
 | 
			
		||||
        capture(stub, "label clear")
 | 
			
		||||
        expect(setComputerLabel):called_with(nil)
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										22
									
								
								src/test/resources/test-rom/spec/programs/list_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/test/resources/test-rom/spec/programs/list_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The list program", function()
 | 
			
		||||
    it("lists files", function()
 | 
			
		||||
        local pagedTabulate = stub(textutils, "pagedTabulate")
 | 
			
		||||
        capture(stub, "list /rom")
 | 
			
		||||
        expect(pagedTabulate):called_with_matching(
 | 
			
		||||
            colors.green, { "apis", "autorun", "help", "modules", "programs" },
 | 
			
		||||
            colors.white, { "motd.txt", "startup.lua" }
 | 
			
		||||
        )
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails on a non-existent directory", function()
 | 
			
		||||
        expect(capture(stub, "list /rom/nothing"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Not a directory\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails on a file", function()
 | 
			
		||||
        expect(capture(stub, "list /rom/startup.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Not a directory\n" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The monitor program", function()
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        expect(capture(stub, "monitor"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: monitor <name> <program> <arguments>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The peripherals program", function()
 | 
			
		||||
    it("says when there are no peripherals", function()
 | 
			
		||||
        expect(capture(stub, "peripherals" ))
 | 
			
		||||
            :matches { ok = true, output = "Attached Peripherals:\nNone\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The pocket equip program", function()
 | 
			
		||||
    it("errors when not a pocket computer", function()
 | 
			
		||||
        stub(_G, "pocket", nil)
 | 
			
		||||
        expect(capture(stub, "/rom/programs/pocket/equip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Pocket Computer\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("can equip an upgrade", function()
 | 
			
		||||
        stub(_G, "pocket", {
 | 
			
		||||
            equipBack = function() return true end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/pocket/equip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Item equipped\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("handles when an upgrade cannot be equipped", function()
 | 
			
		||||
        stub(_G, "pocket", {
 | 
			
		||||
            equipBack = function() return false, "Cannot equip this item." end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/pocket/equip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Cannot equip this item.\n" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The pocket unequip program", function()
 | 
			
		||||
    it("errors when not a pocket computer", function()
 | 
			
		||||
        stub(_G, "pocket", nil)
 | 
			
		||||
        expect(capture(stub, "/rom/programs/pocket/unequip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Pocket Computer\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("unequips an upgrade", function()
 | 
			
		||||
        stub(_G, "pocket", {
 | 
			
		||||
            unequipBack = function() return true end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/pocket/unequip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Item unequipped\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("handles when an upgrade cannot be equipped", function()
 | 
			
		||||
        stub(_G, "pocket", {
 | 
			
		||||
            unequipBack = function() return false, "Nothing to remove." end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/pocket/unequip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Nothing to remove.\n" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										14
									
								
								src/test/resources/test-rom/spec/programs/programs_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/test/resources/test-rom/spec/programs/programs_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The programs program", function()
 | 
			
		||||
    it("list programs", function()
 | 
			
		||||
        local programs = stub(shell, "programs", function() return { "some", "programs" } end)
 | 
			
		||||
        local pagedTabulate = stub(textutils, "pagedTabulate", function(x) print(table.unpack(x)) end)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/programs.lua"))
 | 
			
		||||
            :matches { ok = true, output = "some programs\n", error = "" }
 | 
			
		||||
 | 
			
		||||
        expect(programs):called_with(false)
 | 
			
		||||
        expect(pagedTabulate):called_with_matching({ "some", "programs" })
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										14
									
								
								src/test/resources/test-rom/spec/programs/reboot_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/test/resources/test-rom/spec/programs/reboot_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The reboot program", function()
 | 
			
		||||
    it("sleeps and then reboots", function()
 | 
			
		||||
        local sleep = stub(_G, "sleep")
 | 
			
		||||
        local reboot = stub(os, "reboot")
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "reboot"))
 | 
			
		||||
            :matches { ok = true, output = "Goodbye\n", error = "" }
 | 
			
		||||
 | 
			
		||||
        expect(sleep):called_with(1)
 | 
			
		||||
        expect(reboot):called()
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The redstone program", function()
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        expect(capture(stub, "redstone"))
 | 
			
		||||
            :matches { ok = true, output = "Usages:\nredstone probe\nredstone set <side> <value>\nredstone set <side> <color> <value>\nredstone pulse <side> <count> <period>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										15
									
								
								src/test/resources/test-rom/spec/programs/shutdown_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/test/resources/test-rom/spec/programs/shutdown_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The shutdown program", function()
 | 
			
		||||
 | 
			
		||||
    it("run the program", function()
 | 
			
		||||
        local sleep = stub(_G, "sleep")
 | 
			
		||||
        local shutdown = stub(os, "shutdown")
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "shutdown"))
 | 
			
		||||
            :matches { ok = true, output = "Goodbye\n", error = "" }
 | 
			
		||||
 | 
			
		||||
        expect(sleep):called_with(1)
 | 
			
		||||
        expect(shutdown):called()
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The craft program", function()
 | 
			
		||||
    it("errors when not a turtle", function()
 | 
			
		||||
        stub(_G, "turtle", nil)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/craft.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Turtle\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails when turtle.craft() is unavailable", function()
 | 
			
		||||
        stub(_G, "turtle", {})
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/craft.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Requires a Crafty Turtle\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        stub(_G, "turtle", { craft = function() end })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/craft.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: craft [number]\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("crafts multiple items", function()
 | 
			
		||||
        local item_count = 3
 | 
			
		||||
        stub(_G, "turtle", {
 | 
			
		||||
            craft = function()
 | 
			
		||||
                item_count = 1
 | 
			
		||||
                return true
 | 
			
		||||
            end,
 | 
			
		||||
            getItemCount = function() return item_count end,
 | 
			
		||||
            getSelectedSlot = function() return 1 end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/craft.lua 2"))
 | 
			
		||||
            :matches { ok = true, output = "2 items crafted\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("craft a single item", function()
 | 
			
		||||
        local item_count = 2
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            craft = function()
 | 
			
		||||
                item_count = 1
 | 
			
		||||
                return true
 | 
			
		||||
            end,
 | 
			
		||||
            getItemCount = function() return item_count end,
 | 
			
		||||
            getSelectedSlot = function() return 1 end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
 | 
			
		||||
            :matches { ok = true, output = "1 item crafted\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
     it("crafts no items", function()
 | 
			
		||||
        local item_count = 2
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            craft = function()
 | 
			
		||||
                item_count = 1
 | 
			
		||||
                return false
 | 
			
		||||
            end,
 | 
			
		||||
            getItemCount = function() return item_count end,
 | 
			
		||||
            getSelectedSlot = function() return 1 end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
 | 
			
		||||
            :matches { ok = true, output = "No items crafted\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The turtle equip program", function()
 | 
			
		||||
    it("errors when not a turtle", function()
 | 
			
		||||
        stub(_G, "turtle", nil)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Turtle\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        stub(_G, "turtle", {})
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: equip <slot> <side>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("equip nothing", function()
 | 
			
		||||
        stub(_G, "turtle", {
 | 
			
		||||
            select = function() end,
 | 
			
		||||
            getItemCount = function() return 0 end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
 | 
			
		||||
            :matches { ok = true, output = "Nothing to equip\n", error = "" }
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
 | 
			
		||||
            :matches { ok = true, output = "Nothing to equip\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("swaps existing upgrades", function()
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            select = function() end,
 | 
			
		||||
            getItemCount = function() return 1 end,
 | 
			
		||||
            equipLeft = function() return true end,
 | 
			
		||||
            equipRight = function() return true end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
 | 
			
		||||
            :matches { ok = true, output = "Items swapped\n", error = "" }
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
 | 
			
		||||
            :matches { ok = true, output = "Items swapped\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    describe("equips a new upgrade", function()
 | 
			
		||||
        local function setup()
 | 
			
		||||
            local item_count = 1
 | 
			
		||||
            stub(_G,"turtle",{
 | 
			
		||||
                select = function() end,
 | 
			
		||||
                getItemCount = function() return item_count end,
 | 
			
		||||
                equipLeft = function()
 | 
			
		||||
                    item_count  = 0
 | 
			
		||||
                    return true
 | 
			
		||||
                end,
 | 
			
		||||
                equipRight = function()
 | 
			
		||||
                    item_count = 0
 | 
			
		||||
                    return true
 | 
			
		||||
                end,
 | 
			
		||||
            })
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it("on the left", function()
 | 
			
		||||
            setup()
 | 
			
		||||
            expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
 | 
			
		||||
                :matches { ok = true, output = "Item equipped\n", error = "" }
 | 
			
		||||
        end)
 | 
			
		||||
 | 
			
		||||
        it("on the right", function()
 | 
			
		||||
            setup()
 | 
			
		||||
            expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
 | 
			
		||||
                :matches { ok = true, output = "Item equipped\n", error = "" }
 | 
			
		||||
        end)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("handles when an upgrade cannot be equipped", function()
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            select = function() end,
 | 
			
		||||
            getItemCount = function() return 1 end,
 | 
			
		||||
            equipLeft = function() return false end,
 | 
			
		||||
            equipRight = function() return false end,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua 1 left"))
 | 
			
		||||
            :matches { ok = true, output = "Item not equippable\n", error = "" }
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/equip.lua 1 right"))
 | 
			
		||||
            :matches { ok = true, output = "Item not equippable\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The refuel program", function()
 | 
			
		||||
    local function setup_turtle(fuel_level, fuel_limit, item_count)
 | 
			
		||||
        stub(_G, "turtle", {
 | 
			
		||||
            getFuelLevel = function()
 | 
			
		||||
                return fuel_level
 | 
			
		||||
            end,
 | 
			
		||||
            getItemCount = function()
 | 
			
		||||
                return item_count
 | 
			
		||||
            end,
 | 
			
		||||
            refuel = function(nLimit)
 | 
			
		||||
                item_count = item_count - nLimit
 | 
			
		||||
                fuel_level = fuel_level + nLimit
 | 
			
		||||
            end,
 | 
			
		||||
            select = function()
 | 
			
		||||
            end,
 | 
			
		||||
            getFuelLimit = function()
 | 
			
		||||
                return fuel_limit
 | 
			
		||||
            end
 | 
			
		||||
        })
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it("errors when not a turtle", function()
 | 
			
		||||
        stub(_G, "turtle", nil)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/refuel.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Turtle\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    it("displays its usage when given too many argument", function()
 | 
			
		||||
        setup_turtle(0, 5, 0)
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/refuel.lua a b"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: refuel [number]\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("requires a numeric argument", function()
 | 
			
		||||
       setup_turtle(0, 0, 0)
 | 
			
		||||
       expect(capture(stub, "/rom/programs/turtle/refuel.lua nothing"))
 | 
			
		||||
           :matches { ok = true, output = "Invalid limit, expected a number or \"all\"\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("refuels the turtle", function()
 | 
			
		||||
       setup_turtle(0, 10, 5)
 | 
			
		||||
 | 
			
		||||
       expect(capture(stub, "/rom/programs/turtle/refuel.lua 5"))
 | 
			
		||||
           :matches { ok = true, output = "Fuel level is 5\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("reports when the fuel limit is reached", function()
 | 
			
		||||
       setup_turtle(0,5,5)
 | 
			
		||||
       expect(capture(stub, "/rom/programs/turtle/refuel.lua 5"))
 | 
			
		||||
           :matches { ok = true, output = "Fuel level is 5\nFuel limit reached\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("reports when the fuel level is unlimited", function()
 | 
			
		||||
       setup_turtle("unlimited",5,5)
 | 
			
		||||
       expect(capture(stub, "/rom/programs/turtle/refuel.lua 5"))
 | 
			
		||||
           :matches { ok = true, output = "Fuel level is unlimited\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
end)
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
local capture = require "test_helpers".capture_program
 | 
			
		||||
 | 
			
		||||
describe("The turtle unequip program", function()
 | 
			
		||||
    it("errors when not a turtle", function()
 | 
			
		||||
        stub(_G, "turtle", nil)
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "", error = "Requires a Turtle\n" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    it("displays its usage when given no arguments", function()
 | 
			
		||||
        stub(_G, "turtle", {})
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua"))
 | 
			
		||||
            :matches { ok = true, output = "Usage: unequip <side>\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("says when nothing was unequipped", function()
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            select = function() end,
 | 
			
		||||
            getItemCount = function() return 0 end,
 | 
			
		||||
            equipRight = function() return true end,
 | 
			
		||||
            equipLeft = function() return true end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua left"))
 | 
			
		||||
            :matches { ok = true, output = "Nothing to unequip\n", error = "" }
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua right"))
 | 
			
		||||
            :matches { ok = true, output = "Nothing to unequip\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("unequips a upgrade", function()
 | 
			
		||||
        local item_count = 0
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            select = function() end,
 | 
			
		||||
            getItemCount = function() return item_count end,
 | 
			
		||||
            equipRight = function()
 | 
			
		||||
                item_count = 1
 | 
			
		||||
                return true
 | 
			
		||||
            end,
 | 
			
		||||
            equipLeft = function()
 | 
			
		||||
                item_count = 1
 | 
			
		||||
                return true
 | 
			
		||||
            end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua left"))
 | 
			
		||||
            :matches { ok = true, output = "Item unequipped\n", error = "" }
 | 
			
		||||
        item_count = 0
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua right"))
 | 
			
		||||
            :matches { ok = true, output = "Item unequipped\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it("fails when the turtle is full", function()
 | 
			
		||||
        stub(_G,"turtle",{
 | 
			
		||||
            select = function() end,
 | 
			
		||||
            getItemCount = function() return 1 end,
 | 
			
		||||
            equipRight = function() return true end,
 | 
			
		||||
            equipLeft = function() return true end
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua left"))
 | 
			
		||||
            :matches { ok = true, output = "No space to unequip item\n", error = "" }
 | 
			
		||||
        expect(capture(stub, "/rom/programs/turtle/unequip.lua right"))
 | 
			
		||||
            :matches { ok = true, output = "No space to unequip item\n", error = "" }
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
end)
 | 
			
		||||
		Reference in New Issue
	
	Block a user