mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Merge branch 'master' into mc-1.14.x
This commit is contained in:
		| @@ -17,7 +17,8 @@ ignore = { | |||||||
| -- are largely unsupported. | -- are largely unsupported. | ||||||
| include_files = { | include_files = { | ||||||
|     'src/main/resources/assets/computercraft/lua/rom', |     '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'] = { | files['src/main/resources/assets/computercraft/lua/bios.lua'] = { | ||||||
|   | |||||||
| @@ -11,4 +11,4 @@ cache: | |||||||
|     - $HOME/.gradle/wrapper/s |     - $HOME/.gradle/wrapper/s | ||||||
|  |  | ||||||
| jdk: | 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, | CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers, | ||||||
| turtles and more to Minecraft. | turtles and more to Minecraft. | ||||||
|   | |||||||
							
								
								
									
										58
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -4,7 +4,7 @@ buildscript { | |||||||
|         mavenCentral() |         mavenCentral() | ||||||
|         maven { |         maven { | ||||||
|             name = "forge" |             name = "forge" | ||||||
|             url = "http://files.minecraftforge.net/maven" |             url = "https://files.minecraftforge.net/maven" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     dependencies { |     dependencies { | ||||||
| @@ -67,7 +67,7 @@ minecraft { | |||||||
| repositories { | repositories { | ||||||
|     maven { |     maven { | ||||||
|         name "JEI" |         name "JEI" | ||||||
|         url  "http://dvs1.progwml6.com/files/maven" |         url "https://dvs1.progwml6.com/files/maven" | ||||||
|     } |     } | ||||||
|     maven { |     maven { | ||||||
|         name "SquidDev" |         name "SquidDev" | ||||||
| @@ -79,7 +79,7 @@ repositories { | |||||||
|     } |     } | ||||||
|     maven { |     maven { | ||||||
|         name "Amadornes" |         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' |     shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT' | ||||||
|  |  | ||||||
|     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0' |     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2' | ||||||
|     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0' |     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2' | ||||||
|  |  | ||||||
|     deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0" |     deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0" | ||||||
| } | } | ||||||
| @@ -117,6 +117,8 @@ sourceSets { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Compile tasks | ||||||
|  |  | ||||||
| javadoc { | javadoc { | ||||||
|     include "dan200/computercraft/api/**/*.java" |     include "dan200/computercraft/api/**/*.java" | ||||||
| } | } | ||||||
| @@ -141,6 +143,14 @@ jar { | |||||||
|     from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) } |     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.charset.StandardCharsets | ||||||
| import java.nio.file.* | import java.nio.file.* | ||||||
| import java.util.zip.* | import java.util.zip.* | ||||||
| @@ -276,7 +286,14 @@ task compressJson(dependsOn: jar) { | |||||||
|  |  | ||||||
| assemble.dependsOn compressJson | assemble.dependsOn compressJson | ||||||
|  |  | ||||||
| /* Check tasks */ | // Check tasks | ||||||
|  |  | ||||||
|  | test { | ||||||
|  |     useJUnitPlatform() | ||||||
|  |     testLogging { | ||||||
|  |         events "skipped", "failed" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| license { | license { | ||||||
|     mapping("java", "SLASHSTAR_STYLE") |     mapping("java", "SLASHSTAR_STYLE") | ||||||
| @@ -300,6 +317,13 @@ license { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | gradle.projectsEvaluated { | ||||||
|  |     tasks.withType(LicenseFormat) { | ||||||
|  |         outputs.upToDateWhen { false } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| task licenseAPI(type: LicenseCheck); | task licenseAPI(type: LicenseCheck); | ||||||
| task licenseFormatAPI(type: LicenseFormat); | task licenseFormatAPI(type: LicenseFormat); | ||||||
| [licenseAPI, licenseFormatAPI].forEach { | [licenseAPI, licenseFormatAPI].forEach { | ||||||
| @@ -310,7 +334,7 @@ task licenseFormatAPI(type: LicenseFormat); | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Upload tasks */ | // Upload tasks | ||||||
|  |  | ||||||
| task checkRelease { | task checkRelease { | ||||||
|     group "upload" |     group "upload" | ||||||
| @@ -441,23 +465,3 @@ task uploadAll(dependsOn: uploadTasks) { | |||||||
|     group "upload" |     group "upload" | ||||||
|     description "Uploads to all repositories (Maven, Curse, GitHub release)" |     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 properties | ||||||
| mod_version=1.83.1 | mod_version=1.84.0 | ||||||
|  |  | ||||||
| # Minecraft properties | # Minecraft properties | ||||||
| mc_version=1.14.4 | mc_version=1.14.4 | ||||||
|   | |||||||
| @@ -144,7 +144,9 @@ public interface ITurtleAccess | |||||||
|     GameProfile getOwningPlayer(); |     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 |      * @return This turtle's inventory | ||||||
|      * @see #getItemHandler() |      * @see #getItemHandler() | ||||||
| @@ -155,6 +157,8 @@ public interface ITurtleAccess | |||||||
|     /** |     /** | ||||||
|      * Get the inventory of this turtle as an {@link IItemHandlerModifiable}. |      * 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 |      * @return This turtle's inventory | ||||||
|      * @see #getInventory() |      * @see #getInventory() | ||||||
|      * @see IItemHandlerModifiable |      * @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 |      * 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. |      * 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, |      * Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig} for | ||||||
|      * {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking. |      * digging, {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking. | ||||||
|      * |      * | ||||||
|      * @param turtle    Access to the turtle that the tool resides on. |      * @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. |      * @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.media.IMedia; | ||||||
| import dan200.computercraft.api.peripheral.IComputerAccess; | import dan200.computercraft.api.peripheral.IComputerAccess; | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
|  | import dan200.computercraft.shared.MediaProviders; | ||||||
| import dan200.computercraft.shared.media.items.ItemDisk; | import dan200.computercraft.shared.media.items.ItemDisk; | ||||||
| import dan200.computercraft.shared.util.StringUtil; | import dan200.computercraft.shared.util.StringUtil; | ||||||
| import net.minecraft.item.ItemStack; | import net.minecraft.item.ItemStack; | ||||||
| @@ -19,11 +20,11 @@ import javax.annotation.Nonnull; | |||||||
|  |  | ||||||
| import static dan200.computercraft.core.apis.ArgumentHelper.optString; | import static dan200.computercraft.core.apis.ArgumentHelper.optString; | ||||||
|  |  | ||||||
| public class DiskDrivePeripheral implements IPeripheral | class DiskDrivePeripheral implements IPeripheral | ||||||
| { | { | ||||||
|     private final TileDiskDrive m_diskDrive; |     private final TileDiskDrive m_diskDrive; | ||||||
|  |  | ||||||
|     public DiskDrivePeripheral( TileDiskDrive diskDrive ) |     DiskDrivePeripheral( TileDiskDrive diskDrive ) | ||||||
|     { |     { | ||||||
|         m_diskDrive = diskDrive; |         m_diskDrive = diskDrive; | ||||||
|     } |     } | ||||||
| @@ -55,7 +56,7 @@ public class DiskDrivePeripheral implements IPeripheral | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @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 ) |         switch( method ) | ||||||
|         { |         { | ||||||
| @@ -63,21 +64,26 @@ public class DiskDrivePeripheral implements IPeripheral | |||||||
|                 return new Object[] { !m_diskDrive.getDiskStack().isEmpty() }; |                 return new Object[] { !m_diskDrive.getDiskStack().isEmpty() }; | ||||||
|             case 1: // getDiskLabel |             case 1: // getDiskLabel | ||||||
|             { |             { | ||||||
|                 IMedia media = m_diskDrive.getDiskMedia(); |                 ItemStack stack = m_diskDrive.getDiskStack(); | ||||||
|                 return media == null ? null : new Object[] { media.getLabel( m_diskDrive.getDiskStack() ) }; |                 IMedia media = MediaProviders.get( stack ); | ||||||
|  |                 return media == null ? null : new Object[] { media.getLabel( stack ) }; | ||||||
|             } |             } | ||||||
|             case 2: // setDiskLabel |             case 2: // setDiskLabel | ||||||
|             { |             { | ||||||
|                 String label = optString( arguments, 0, null ); |                 String label = optString( arguments, 0, null ); | ||||||
|  |  | ||||||
|                 IMedia media = m_diskDrive.getDiskMedia(); |                 return context.executeMainThreadTask( () -> { | ||||||
|                 if( media == null ) return null; |                     ItemStack stack = m_diskDrive.getDiskStack(); | ||||||
|  |                     IMedia media = MediaProviders.get( stack ); | ||||||
|  |                     if( media == null ) return null; | ||||||
|  |  | ||||||
|                 ItemStack disk = m_diskDrive.getDiskStack(); |                     if( !media.setLabel( stack, StringUtil.normaliseLabel( label ) ) ) | ||||||
|                 label = StringUtil.normaliseLabel( label ); |                     { | ||||||
|                 if( !media.setLabel( disk, label ) ) throw new LuaException( "Disk label cannot be changed" ); |                         throw new LuaException( "Disk label cannot be changed" ); | ||||||
|                 m_diskDrive.setDiskStack( disk ); |                     } | ||||||
|                 return null; |                     m_diskDrive.setDiskStack( stack ); | ||||||
|  |                     return null; | ||||||
|  |                 } ); | ||||||
|             } |             } | ||||||
|             case 3: // hasData |             case 3: // hasData | ||||||
|                 return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null }; |                 return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null }; | ||||||
| @@ -86,14 +92,16 @@ public class DiskDrivePeripheral implements IPeripheral | |||||||
|             case 5: |             case 5: | ||||||
|             { |             { | ||||||
|                 // hasAudio |                 // hasAudio | ||||||
|                 IMedia media = m_diskDrive.getDiskMedia(); |                 ItemStack stack = m_diskDrive.getDiskStack(); | ||||||
|                 return new Object[] { media != null && media.getAudio( m_diskDrive.getDiskStack() ) != null }; |                 IMedia media = MediaProviders.get( stack ); | ||||||
|  |                 return new Object[] { media != null && media.getAudio( stack ) != null }; | ||||||
|             } |             } | ||||||
|             case 6: |             case 6: | ||||||
|             { |             { | ||||||
|                 // getAudioTitle |                 // getAudioTitle | ||||||
|                 IMedia media = m_diskDrive.getDiskMedia(); |                 ItemStack stack = m_diskDrive.getDiskStack(); | ||||||
|                 return new Object[] { media != null ? media.getAudioTitle( m_diskDrive.getDiskStack() ) : false }; |                 IMedia media = MediaProviders.get( stack ); | ||||||
|  |                 return new Object[] { media != null ? media.getAudioTitle( stack ) : false }; | ||||||
|             } |             } | ||||||
|             case 7: // playAudio |             case 7: // playAudio | ||||||
|                 m_diskDrive.playDiskAudio(); |                 m_diskDrive.playDiskAudio(); | ||||||
| @@ -129,8 +137,7 @@ public class DiskDrivePeripheral implements IPeripheral | |||||||
|     @Override |     @Override | ||||||
|     public boolean equals( IPeripheral other ) |     public boolean equals( IPeripheral other ) | ||||||
|     { |     { | ||||||
|         if( this == other ) return true; |         return this == other || other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive; | ||||||
|         return other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|   | |||||||
| @@ -320,35 +320,31 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|     public ItemStack getDiskStack() |     ItemStack getDiskStack() | ||||||
|     { |     { | ||||||
|         return getStackInSlot( 0 ); |         return getStackInSlot( 0 ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setDiskStack( @Nonnull ItemStack stack ) |     void setDiskStack( @Nonnull ItemStack stack ) | ||||||
|     { |     { | ||||||
|         setInventorySlotContents( 0, stack ); |         setInventorySlotContents( 0, stack ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public IMedia getDiskMedia() |     private IMedia getDiskMedia() | ||||||
|     { |     { | ||||||
|         return MediaProviders.get( getDiskStack() ); |         return MediaProviders.get( getDiskStack() ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public String getDiskMountPath( IComputerAccess computer ) |     String getDiskMountPath( IComputerAccess computer ) | ||||||
|     { |     { | ||||||
|         synchronized( this ) |         synchronized( this ) | ||||||
|         { |         { | ||||||
|             if( m_computers.containsKey( computer ) ) |             MountInfo info = m_computers.get( computer ); | ||||||
|             { |             return info != null ? info.mountPath : null; | ||||||
|                 MountInfo info = m_computers.get( computer ); |  | ||||||
|                 return info.mountPath; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         return null; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void mount( IComputerAccess computer ) |     void mount( IComputerAccess computer ) | ||||||
|     { |     { | ||||||
|         synchronized( this ) |         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 ) |         synchronized( this ) | ||||||
|         { |         { | ||||||
| @@ -366,7 +362,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void playDiskAudio() |     void playDiskAudio() | ||||||
|     { |     { | ||||||
|         synchronized( this ) |         synchronized( this ) | ||||||
|         { |         { | ||||||
| @@ -379,7 +375,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void stopDiskAudio() |     void stopDiskAudio() | ||||||
|     { |     { | ||||||
|         synchronized( this ) |         synchronized( this ) | ||||||
|         { |         { | ||||||
| @@ -388,7 +384,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void ejectDisk() |     void ejectDisk() | ||||||
|     { |     { | ||||||
|         synchronized( this ) |         synchronized( this ) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException; | |||||||
| import dan200.computercraft.api.peripheral.IComputerAccess; | import dan200.computercraft.api.peripheral.IComputerAccess; | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.core.terminal.Terminal; | import dan200.computercraft.core.terminal.Terminal; | ||||||
|  | import dan200.computercraft.shared.util.StringUtil; | ||||||
|  |  | ||||||
| import javax.annotation.Nonnull; | import javax.annotation.Nonnull; | ||||||
|  |  | ||||||
| @@ -51,8 +52,13 @@ public class PrinterPeripheral implements IPeripheral | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @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 ) |         switch( method ) | ||||||
|         { |         { | ||||||
|             case 0: // write |             case 0: // write | ||||||
| @@ -89,10 +95,13 @@ public class PrinterPeripheral implements IPeripheral | |||||||
|                 return new Object[] { width, height }; |                 return new Object[] { width, height }; | ||||||
|             } |             } | ||||||
|             case 4: // newPage |             case 4: // newPage | ||||||
|                 return new Object[] { m_printer.startNewPage() }; |                 return context.executeMainThreadTask( () -> new Object[] { m_printer.startNewPage() } ); | ||||||
|             case 5: // endPage |             case 5: // endPage | ||||||
|                 getCurrentPage(); |                 getCurrentPage(); | ||||||
|                 return new Object[] { m_printer.endCurrentPage() }; |                 return context.executeMainThreadTask( () -> { | ||||||
|  |                     getCurrentPage(); | ||||||
|  |                     return new Object[] { m_printer.endCurrentPage() }; | ||||||
|  |                 } ); | ||||||
|             case 6: // getInkLevel |             case 6: // getInkLevel | ||||||
|                 return new Object[] { m_printer.getInkLevel() }; |                 return new Object[] { m_printer.getInkLevel() }; | ||||||
|             case 7: |             case 7: | ||||||
| @@ -100,7 +109,7 @@ public class PrinterPeripheral implements IPeripheral | |||||||
|                 // setPageTitle |                 // setPageTitle | ||||||
|                 String title = optString( args, 0, "" ); |                 String title = optString( args, 0, "" ); | ||||||
|                 getCurrentPage(); |                 getCurrentPage(); | ||||||
|                 m_printer.setPageTitle( title ); |                 m_printer.setPageTitle( StringUtil.normaliseLabel( title ) ); | ||||||
|                 return null; |                 return null; | ||||||
|             } |             } | ||||||
|             case 8: // getPaperLevel |             case 8: // getPaperLevel | ||||||
| @@ -123,13 +132,11 @@ public class PrinterPeripheral implements IPeripheral | |||||||
|         return m_printer; |         return m_printer; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Nonnull | ||||||
|     private Terminal getCurrentPage() throws LuaException |     private Terminal getCurrentPage() throws LuaException | ||||||
|     { |     { | ||||||
|         Terminal currentPage = m_printer.getCurrentPage(); |         Terminal currentPage = m_printer.getCurrentPage(); | ||||||
|         if( currentPage == null ) |         if( currentPage == null ) throw new LuaException( "Page not started" ); | ||||||
|         { |  | ||||||
|             throw new LuaException( "Page not started" ); |  | ||||||
|         } |  | ||||||
|         return currentPage; |         return currentPage; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -120,10 +120,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Read inventory |         // Read inventory | ||||||
|         synchronized( m_inventory ) |         ItemStackHelper.loadAllItems( nbt, m_inventory ); | ||||||
|         { |  | ||||||
|             ItemStackHelper.loadAllItems( nbt, m_inventory ); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
| @@ -141,15 +138,12 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Write inventory |         // Write inventory | ||||||
|         synchronized( m_inventory ) |         ItemStackHelper.saveAllItems( nbt, m_inventory ); | ||||||
|         { |  | ||||||
|             ItemStackHelper.saveAllItems( nbt, m_inventory ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return super.write( nbt ); |         return super.write( nbt ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public boolean isPrinting() |     boolean isPrinting() | ||||||
|     { |     { | ||||||
|         return m_printing; |         return m_printing; | ||||||
|     } |     } | ||||||
| @@ -173,73 +167,59 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent | |||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|     @Override |     @Override | ||||||
|     public ItemStack getStackInSlot( int i ) |     public ItemStack getStackInSlot( int slot ) | ||||||
|     { |     { | ||||||
|         return m_inventory.get( i ); |         return m_inventory.get( slot ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|     @Override |     @Override | ||||||
|     public ItemStack removeStackFromSlot( int i ) |     public ItemStack removeStackFromSlot( int slot ) | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         ItemStack result = m_inventory.get( slot ); | ||||||
|         { |         m_inventory.set( slot, ItemStack.EMPTY ); | ||||||
|             ItemStack result = m_inventory.get( i ); |         markDirty(); | ||||||
|             m_inventory.set( i, ItemStack.EMPTY ); |         updateBlockState(); | ||||||
|             markDirty(); |         return result; | ||||||
|             updateBlockState(); |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|     @Override |     @Override | ||||||
|     public ItemStack decrStackSize( int i, int j ) |     public ItemStack decrStackSize( int slot, int count ) | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         ItemStack stack = m_inventory.get( slot ); | ||||||
|  |         if( stack.isEmpty() ) return ItemStack.EMPTY; | ||||||
|  |  | ||||||
|  |         if( stack.getCount() <= count ) | ||||||
|         { |         { | ||||||
|             if( m_inventory.get( i ).isEmpty() ) return ItemStack.EMPTY; |             setInventorySlotContents( slot, ItemStack.EMPTY ); | ||||||
|  |             return stack; | ||||||
|             if( m_inventory.get( i ).getCount() <= j ) |  | ||||||
|             { |  | ||||||
|                 ItemStack itemstack = m_inventory.get( i ); |  | ||||||
|                 m_inventory.set( i, ItemStack.EMPTY ); |  | ||||||
|                 markDirty(); |  | ||||||
|                 updateBlockState(); |  | ||||||
|                 return itemstack; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             ItemStack part = m_inventory.get( i ).split( j ); |  | ||||||
|             if( m_inventory.get( i ).isEmpty() ) |  | ||||||
|             { |  | ||||||
|                 m_inventory.set( i, ItemStack.EMPTY ); |  | ||||||
|                 updateBlockState(); |  | ||||||
|             } |  | ||||||
|             markDirty(); |  | ||||||
|             return part; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         ItemStack part = stack.split( count ); | ||||||
|  |         if( m_inventory.get( slot ).isEmpty() ) | ||||||
|  |         { | ||||||
|  |             m_inventory.set( slot, ItemStack.EMPTY ); | ||||||
|  |             updateBlockState(); | ||||||
|  |         } | ||||||
|  |         markDirty(); | ||||||
|  |         return part; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void setInventorySlotContents( int i, @Nonnull ItemStack stack ) |     public void setInventorySlotContents( int slot, @Nonnull ItemStack stack ) | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         m_inventory.set( slot, stack ); | ||||||
|         { |         markDirty(); | ||||||
|             m_inventory.set( i, stack ); |         updateBlockState(); | ||||||
|             markDirty(); |  | ||||||
|             updateBlockState(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void clear() |     public void clear() | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY ); | ||||||
|         { |         markDirty(); | ||||||
|             for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY ); |         updateBlockState(); | ||||||
|             markDirty(); |  | ||||||
|             updateBlockState(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -290,14 +270,18 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent | |||||||
|         return new PrinterPeripheral( this ); |         return new PrinterPeripheral( this ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Terminal getCurrentPage() |     @Nullable | ||||||
|  |     Terminal getCurrentPage() | ||||||
|     { |     { | ||||||
|         return m_printing ? m_page : null; |         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( !canInputPage() ) return false; | ||||||
|             if( m_printing && !outputPage() ) 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 m_printing && outputPage(); | ||||||
|             { |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public int getInkLevel() |  | ||||||
|     { |  | ||||||
|         synchronized( m_inventory ) |  | ||||||
|         { |  | ||||||
|             ItemStack inkStack = m_inventory.get( 0 ); |  | ||||||
|             return isInk( inkStack ) ? inkStack.getCount() : 0; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public int getPaperLevel() |     int getInkLevel() | ||||||
|  |     { | ||||||
|  |         ItemStack inkStack = m_inventory.get( 0 ); | ||||||
|  |         return isInk( inkStack ) ? inkStack.getCount() : 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     int getPaperLevel() | ||||||
|     { |     { | ||||||
|         int count = 0; |         int count = 0; | ||||||
|         synchronized( m_inventory ) |         for( int i = 1; i < 7; i++ ) | ||||||
|         { |         { | ||||||
|             for( int i = 1; i < 7; i++ ) |             ItemStack paperStack = m_inventory.get( i ); | ||||||
|             { |             if( isPaper( paperStack ) ) count += paperStack.getCount(); | ||||||
|                 ItemStack paperStack = m_inventory.get( i ); |  | ||||||
|                 if( !paperStack.isEmpty() && isPaper( paperStack ) ) |  | ||||||
|                 { |  | ||||||
|                     count += paperStack.getCount(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         return count; |         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; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -365,116 +336,100 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent | |||||||
|  |  | ||||||
|     private boolean canInputPage() |     private boolean canInputPage() | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         ItemStack inkStack = m_inventory.get( 0 ); | ||||||
|         { |         return !inkStack.isEmpty() && isInk( inkStack ) && getPaperLevel() > 0; | ||||||
|             ItemStack inkStack = m_inventory.get( 0 ); |  | ||||||
|             return !inkStack.isEmpty() && isInk( inkStack ) && getPaperLevel() > 0; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private boolean inputPage() |     private boolean inputPage() | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         ItemStack inkStack = m_inventory.get( 0 ); | ||||||
|  |         if( !isInk( inkStack ) ) return false; | ||||||
|  |  | ||||||
|  |         for( int i = 1; i < 7; i++ ) | ||||||
|         { |         { | ||||||
|             ItemStack inkStack = m_inventory.get( 0 ); |             ItemStack paperStack = m_inventory.get( i ); | ||||||
|             if( !isInk( inkStack ) ) return false; |             if( paperStack.isEmpty() || !isPaper( paperStack ) ) continue; | ||||||
|  |  | ||||||
|             for( int i = 1; i < 7; i++ ) |             // Setup the new page | ||||||
|  |             DyeColor dye = ColourUtils.getStackColour( inkStack ); | ||||||
|  |             m_page.setTextColour( dye != null ? dye.getId() : 15 ); | ||||||
|  |  | ||||||
|  |             m_page.clear(); | ||||||
|  |             if( paperStack.getItem() instanceof ItemPrintout ) | ||||||
|             { |             { | ||||||
|                 ItemStack paperStack = m_inventory.get( i ); |                 m_pageTitle = ItemPrintout.getTitle( paperStack ); | ||||||
|                 if( !paperStack.isEmpty() && isPaper( paperStack ) ) |                 String[] text = ItemPrintout.getText( paperStack ); | ||||||
|  |                 String[] textColour = ItemPrintout.getColours( paperStack ); | ||||||
|  |                 for( int y = 0; y < m_page.getHeight(); y++ ) | ||||||
|                 { |                 { | ||||||
|                     // Setup the new page |                     m_page.setLine( y, text[y], textColour[y], "" ); | ||||||
|                     DyeColor dye = ColourUtils.getStackColour( inkStack ); |  | ||||||
|                     m_page.setTextColour( dye != null ? dye.getId() : 15 ); |  | ||||||
|  |  | ||||||
|                     m_page.clear(); |  | ||||||
|                     if( paperStack.getItem() instanceof ItemPrintout ) |  | ||||||
|                     { |  | ||||||
|                         m_pageTitle = ItemPrintout.getTitle( paperStack ); |  | ||||||
|                         String[] text = ItemPrintout.getText( paperStack ); |  | ||||||
|                         String[] textColour = ItemPrintout.getColours( paperStack ); |  | ||||||
|                         for( int y = 0; y < m_page.getHeight(); y++ ) |  | ||||||
|                         { |  | ||||||
|                             m_page.setLine( y, text[y], textColour[y], "" ); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                     { |  | ||||||
|                         m_pageTitle = ""; |  | ||||||
|                     } |  | ||||||
|                     m_page.setCursorPos( 0, 0 ); |  | ||||||
|  |  | ||||||
|                     // Decrement ink |  | ||||||
|                     inkStack.shrink( 1 ); |  | ||||||
|                     if( inkStack.isEmpty() ) m_inventory.set( 0, ItemStack.EMPTY ); |  | ||||||
|  |  | ||||||
|                     // Decrement paper |  | ||||||
|                     paperStack.shrink( 1 ); |  | ||||||
|                     if( paperStack.isEmpty() ) |  | ||||||
|                     { |  | ||||||
|                         m_inventory.set( i, ItemStack.EMPTY ); |  | ||||||
|                         updateBlockState(); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     markDirty(); |  | ||||||
|                     m_printing = true; |  | ||||||
|                     return true; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             return false; |             else | ||||||
|  |             { | ||||||
|  |                 m_pageTitle = ""; | ||||||
|  |             } | ||||||
|  |             m_page.setCursorPos( 0, 0 ); | ||||||
|  |  | ||||||
|  |             // Decrement ink | ||||||
|  |             inkStack.shrink( 1 ); | ||||||
|  |             if( inkStack.isEmpty() ) m_inventory.set( 0, ItemStack.EMPTY ); | ||||||
|  |  | ||||||
|  |             // Decrement paper | ||||||
|  |             paperStack.shrink( 1 ); | ||||||
|  |             if( paperStack.isEmpty() ) | ||||||
|  |             { | ||||||
|  |                 m_inventory.set( i, ItemStack.EMPTY ); | ||||||
|  |                 updateBlockState(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             markDirty(); | ||||||
|  |             m_printing = true; | ||||||
|  |             return true; | ||||||
|         } |         } | ||||||
|  |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private boolean outputPage() |     private boolean outputPage() | ||||||
|     { |     { | ||||||
|         synchronized( m_page ) |         int height = m_page.getHeight(); | ||||||
|  |         String[] lines = new String[height]; | ||||||
|  |         String[] colours = new String[height]; | ||||||
|  |         for( int i = 0; i < height; i++ ) | ||||||
|         { |         { | ||||||
|             int height = m_page.getHeight(); |             lines[i] = m_page.getLine( i ).toString(); | ||||||
|             String[] lines = new String[height]; |             colours[i] = m_page.getTextColourLine( i ).toString(); | ||||||
|             String[] colours = new String[height]; |  | ||||||
|             for( int i = 0; i < height; i++ ) |  | ||||||
|             { |  | ||||||
|                 lines[i] = m_page.getLine( i ).toString(); |  | ||||||
|                 colours[i] = m_page.getTextColourLine( i ).toString(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             ItemStack stack = ItemPrintout.createSingleFromTitleAndText( m_pageTitle, lines, colours ); |  | ||||||
|             synchronized( m_inventory ) |  | ||||||
|             { |  | ||||||
|                 for( int slot : BOTTOM_SLOTS ) |  | ||||||
|                 { |  | ||||||
|                     if( m_inventory.get( slot ).isEmpty() ) |  | ||||||
|                     { |  | ||||||
|                         setInventorySlotContents( slot, stack ); |  | ||||||
|                         m_printing = false; |  | ||||||
|                         return true; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         ItemStack stack = ItemPrintout.createSingleFromTitleAndText( m_pageTitle, lines, colours ); | ||||||
|  |         for( int slot : BOTTOM_SLOTS ) | ||||||
|  |         { | ||||||
|  |             if( m_inventory.get( slot ).isEmpty() ) | ||||||
|  |             { | ||||||
|  |                 setInventorySlotContents( slot, stack ); | ||||||
|  |                 m_printing = false; | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void ejectContents() |     private void ejectContents() | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         for( int i = 0; i < 13; i++ ) | ||||||
|         { |         { | ||||||
|             for( int i = 0; i < 13; i++ ) |             ItemStack stack = m_inventory.get( i ); | ||||||
|  |             if( !stack.isEmpty() ) | ||||||
|             { |             { | ||||||
|                 ItemStack stack = m_inventory.get( i ); |                 // Remove the stack from the inventory | ||||||
|                 if( !stack.isEmpty() ) |                 setInventorySlotContents( i, ItemStack.EMPTY ); | ||||||
|                 { |  | ||||||
|                     // Remove the stack from the inventory |  | ||||||
|                     setInventorySlotContents( i, ItemStack.EMPTY ); |  | ||||||
|  |  | ||||||
|                     // Spawn the item in the world |                 // Spawn the item in the world | ||||||
|                     BlockPos pos = getPos(); |                 BlockPos pos = getPos(); | ||||||
|                     double x = pos.getX() + 0.5; |                 double x = pos.getX() + 0.5; | ||||||
|                     double y = pos.getY() + 0.75; |                 double y = pos.getY() + 0.75; | ||||||
|                     double z = pos.getZ() + 0.5; |                 double z = pos.getZ() + 0.5; | ||||||
|                     WorldUtil.dropItemStack( stack, getWorld(), x, y, z ); |                 WorldUtil.dropItemStack( stack, getWorld(), x, y, z ); | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -482,25 +437,22 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent | |||||||
|     private void updateBlockState() |     private void updateBlockState() | ||||||
|     { |     { | ||||||
|         boolean top = false, bottom = false; |         boolean top = false, bottom = false; | ||||||
|         synchronized( m_inventory ) |         for( int i = 1; i < 7; i++ ) | ||||||
|         { |         { | ||||||
|             for( int i = 1; i < 7; i++ ) |             ItemStack stack = m_inventory.get( i ); | ||||||
|  |             if( !stack.isEmpty() && isPaper( stack ) ) | ||||||
|             { |             { | ||||||
|                 ItemStack stack = m_inventory.get( i ); |                 top = true; | ||||||
|                 if( !stack.isEmpty() && isPaper( stack ) ) |                 break; | ||||||
|                 { |  | ||||||
|                     top = true; |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|             for( int i = 7; i < 13; i++ ) |         } | ||||||
|  |         for( int i = 7; i < 13; i++ ) | ||||||
|  |         { | ||||||
|  |             ItemStack stack = m_inventory.get( i ); | ||||||
|  |             if( !stack.isEmpty() && isPaper( stack ) ) | ||||||
|             { |             { | ||||||
|                 ItemStack stack = m_inventory.get( i ); |                 bottom = true; | ||||||
|                 if( !stack.isEmpty() && isPaper( stack ) ) |                 break; | ||||||
|                 { |  | ||||||
|                     bottom = true; |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,9 +31,13 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler | |||||||
|     @Override |     @Override | ||||||
|     public int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack currentStack, int slot, int limit ) |     public int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack currentStack, int slot, int limit ) | ||||||
|     { |     { | ||||||
|         ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false ); |         int fuelSpaceLeft = turtle.getFuelLimit() - turtle.getFuelLevel(); | ||||||
|         int fuelToGive = getFuelPerItem( stack ) * stack.getCount(); |         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 |         // Store the replacement item in the inventory | ||||||
|         ItemStack replacementStack = stack.getItem().getContainerItem( stack ); |         ItemStack replacementStack = stack.getItem().getContainerItem( stack ); | ||||||
|         if( !replacementStack.isEmpty() ) |         if( !replacementStack.isEmpty() ) | ||||||
|   | |||||||
| @@ -335,9 +335,11 @@ public class TurtleAPI implements ILuaAPI | |||||||
|                 return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) ); |                 return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) ); | ||||||
|             case 40: // inspectDown |             case 40: // inspectDown | ||||||
|                 return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) ); |                 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() ); |                 int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); | ||||||
|                 ItemStack stack = m_turtle.getInventory().getStackInSlot( slot ); |                 ItemStack stack = m_turtle.getInventory().getStackInSlot( slot ); | ||||||
|                 if( stack.isEmpty() ) return new Object[] { null }; |                 if( stack.isEmpty() ) return new Object[] { null }; | ||||||
|   | |||||||
| @@ -153,7 +153,7 @@ public class BlockTurtle extends BlockComputerBase<TileTurtle> implements IWater | |||||||
|     @Override |     @Override | ||||||
|     public float getExplosionResistance( BlockState state, IWorldReader world, BlockPos pos, @Nullable Entity exploder, Explosion explosion ) |     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; |             return 2000; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -49,13 +49,12 @@ import net.minecraftforge.items.wrapper.InvWrapper; | |||||||
|  |  | ||||||
| import javax.annotation.Nonnull; | import javax.annotation.Nonnull; | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
|  | import java.util.Collections; | ||||||
|  |  | ||||||
| import static net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY; | import static net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY; | ||||||
|  |  | ||||||
| public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory | public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory | ||||||
| { | { | ||||||
|     // Statics |  | ||||||
|  |  | ||||||
|     public static final int INVENTORY_SIZE = 16; |     public static final int INVENTORY_SIZE = 16; | ||||||
|     public static final int INVENTORY_WIDTH = 4; |     public static final int INVENTORY_WIDTH = 4; | ||||||
|     public static final int INVENTORY_HEIGHT = 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 ) |         type -> new TileTurtle( type, ComputerFamily.Advanced ) | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // Members |  | ||||||
|  |  | ||||||
|     enum MoveState |     enum MoveState | ||||||
|     { |     { | ||||||
|         NOT_MOVED, |         NOT_MOVED, | ||||||
| @@ -79,25 +76,20 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default | |||||||
|         MOVED |         MOVED | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private NonNullList<ItemStack> m_inventory; |     private final NonNullList<ItemStack> m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY ); | ||||||
|     private NonNullList<ItemStack> m_previousInventory; |     private final NonNullList<ItemStack> m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY ); | ||||||
|     private final IItemHandlerModifiable m_itemHandler = new InvWrapper( this ); |     private final IItemHandlerModifiable m_itemHandler = new InvWrapper( this ); | ||||||
|     private LazyOptional<IItemHandlerModifiable> itemHandlerCap; |     private LazyOptional<IItemHandlerModifiable> itemHandlerCap; | ||||||
|     private boolean m_inventoryChanged; |     private boolean m_inventoryChanged = false; | ||||||
|     private TurtleBrain m_brain; |     private TurtleBrain m_brain = new TurtleBrain( this ); | ||||||
|     private MoveState m_moveState; |     private MoveState m_moveState = MoveState.NOT_MOVED; | ||||||
|  |  | ||||||
|     public TileTurtle( TileEntityType<? extends TileGeneric> type, ComputerFamily family ) |     public TileTurtle( TileEntityType<? extends TileGeneric> type, ComputerFamily family ) | ||||||
|     { |     { | ||||||
|         super( type, 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; |         return m_moveState == MoveState.MOVED; | ||||||
|     } |     } | ||||||
| @@ -237,18 +229,15 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default | |||||||
|     { |     { | ||||||
|         super.tick(); |         super.tick(); | ||||||
|         m_brain.update(); |         m_brain.update(); | ||||||
|         synchronized( m_inventory ) |         if( !getWorld().isRemote && m_inventoryChanged ) | ||||||
|         { |         { | ||||||
|             if( !getWorld().isRemote && m_inventoryChanged ) |             ServerComputer computer = getServerComputer(); | ||||||
|             { |             if( computer != null ) computer.queueEvent( "turtle_inventory" ); | ||||||
|                 ServerComputer computer = getServerComputer(); |  | ||||||
|                 if( computer != null ) computer.queueEvent( "turtle_inventory" ); |  | ||||||
|  |  | ||||||
|                 m_inventoryChanged = false; |             m_inventoryChanged = false; | ||||||
|                 for( int n = 0; n < getSizeInventory(); n++ ) |             for( int n = 0; n < getSizeInventory(); n++ ) | ||||||
|                 { |             { | ||||||
|                     m_previousInventory.set( n, InventoryUtil.copyItem( getStackInSlot( n ) ) ); |                 m_previousInventory.set( n, InventoryUtil.copyItem( getStackInSlot( n ) ) ); | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -288,8 +277,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default | |||||||
|  |  | ||||||
|         // Read inventory |         // Read inventory | ||||||
|         ListNBT nbttaglist = nbt.getList( "Items", Constants.NBT.TAG_COMPOUND ); |         ListNBT nbttaglist = nbt.getList( "Items", Constants.NBT.TAG_COMPOUND ); | ||||||
|         m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY ); |         m_inventory.clear(); | ||||||
|         m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY ); |         m_previousInventory.clear(); | ||||||
|         for( int i = 0; i < nbttaglist.size(); i++ ) |         for( int i = 0; i < nbttaglist.size(); i++ ) | ||||||
|         { |         { | ||||||
|             CompoundNBT tag = nbttaglist.getCompound( i ); |             CompoundNBT tag = nbttaglist.getCompound( i ); | ||||||
| @@ -396,7 +385,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default | |||||||
|         return m_brain.getToolRenderAngle( side, f ); |         return m_brain.getToolRenderAngle( side, f ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setOwningPlayer( GameProfile player ) |     void setOwningPlayer( GameProfile player ) | ||||||
|     { |     { | ||||||
|         m_brain.setOwningPlayer( player ); |         m_brain.setOwningPlayer( player ); | ||||||
|         markDirty(); |         markDirty(); | ||||||
| @@ -424,109 +413,76 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default | |||||||
|     @Override |     @Override | ||||||
|     public ItemStack getStackInSlot( int slot ) |     public ItemStack getStackInSlot( int slot ) | ||||||
|     { |     { | ||||||
|         if( slot >= 0 && slot < INVENTORY_SIZE ) |         return slot >= 0 && slot < INVENTORY_SIZE ? m_inventory.get( slot ) : ItemStack.EMPTY; | ||||||
|         { |  | ||||||
|             synchronized( m_inventory ) |  | ||||||
|             { |  | ||||||
|                 return m_inventory.get( slot ); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return ItemStack.EMPTY; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|     @Override |     @Override | ||||||
|     public ItemStack removeStackFromSlot( int slot ) |     public ItemStack removeStackFromSlot( int slot ) | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         ItemStack result = getStackInSlot( slot ); | ||||||
|         { |         setInventorySlotContents( slot, ItemStack.EMPTY ); | ||||||
|             ItemStack result = getStackInSlot( slot ); |         return result; | ||||||
|             setInventorySlotContents( slot, ItemStack.EMPTY ); |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nonnull |     @Nonnull | ||||||
|     @Override |     @Override | ||||||
|     public ItemStack decrStackSize( int slot, int count ) |     public ItemStack decrStackSize( int slot, int count ) | ||||||
|     { |     { | ||||||
|         if( count == 0 ) |         if( count == 0 ) return ItemStack.EMPTY; | ||||||
|  |  | ||||||
|  |         ItemStack stack = getStackInSlot( slot ); | ||||||
|  |         if( stack.isEmpty() ) return ItemStack.EMPTY; | ||||||
|  |  | ||||||
|  |         if( stack.getCount() <= count ) | ||||||
|         { |         { | ||||||
|             return ItemStack.EMPTY; |             setInventorySlotContents( slot, ItemStack.EMPTY ); | ||||||
|  |             return stack; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         synchronized( m_inventory ) |         ItemStack part = stack.split( count ); | ||||||
|         { |         onInventoryDefinitelyChanged(); | ||||||
|             ItemStack stack = getStackInSlot( slot ); |         return part; | ||||||
|             if( stack.isEmpty() ) |  | ||||||
|             { |  | ||||||
|                 return ItemStack.EMPTY; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if( stack.getCount() <= count ) |  | ||||||
|             { |  | ||||||
|                 setInventorySlotContents( slot, ItemStack.EMPTY ); |  | ||||||
|                 return stack; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             ItemStack part = stack.split( count ); |  | ||||||
|             onInventoryDefinitelyChanged(); |  | ||||||
|             return part; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void setInventorySlotContents( int i, @Nonnull ItemStack stack ) |     public void setInventorySlotContents( int i, @Nonnull ItemStack stack ) | ||||||
|     { |     { | ||||||
|         if( i >= 0 && i < INVENTORY_SIZE ) |         if( i >= 0 && i < INVENTORY_SIZE && !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) ) | ||||||
|         { |         { | ||||||
|             synchronized( m_inventory ) |             m_inventory.set( i, stack ); | ||||||
|             { |             onInventoryDefinitelyChanged(); | ||||||
|                 if( !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) ) |  | ||||||
|                 { |  | ||||||
|                     m_inventory.set( i, stack ); |  | ||||||
|                     onInventoryDefinitelyChanged(); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void clear() |     public void clear() | ||||||
|     { |     { | ||||||
|         synchronized( m_inventory ) |         boolean changed = false; | ||||||
|  |         for( int i = 0; i < INVENTORY_SIZE; i++ ) | ||||||
|         { |         { | ||||||
|             boolean changed = false; |             if( !m_inventory.get( i ).isEmpty() ) | ||||||
|             for( int i = 0; i < INVENTORY_SIZE; i++ ) |  | ||||||
|             { |             { | ||||||
|                 if( !m_inventory.get( i ).isEmpty() ) |                 m_inventory.set( i, ItemStack.EMPTY ); | ||||||
|                 { |                 changed = true; | ||||||
|                     m_inventory.set( i, ItemStack.EMPTY ); |  | ||||||
|                     changed = true; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if( changed ) |  | ||||||
|             { |  | ||||||
|                 onInventoryDefinitelyChanged(); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if( changed ) onInventoryDefinitelyChanged(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void markDirty() |     public void markDirty() | ||||||
|     { |     { | ||||||
|         super.markDirty(); |         super.markDirty(); | ||||||
|         synchronized( m_inventory ) |         if( !m_inventoryChanged ) | ||||||
|         { |         { | ||||||
|             if( !m_inventoryChanged ) |             for( int n = 0; n < getSizeInventory(); n++ ) | ||||||
|             { |             { | ||||||
|                 for( int n = 0; n < getSizeInventory(); n++ ) |                 if( !ItemStack.areItemStacksEqual( getStackInSlot( n ), m_previousInventory.get( n ) ) ) | ||||||
|                 { |                 { | ||||||
|                     if( !ItemStack.areItemStacksEqual( getStackInSlot( n ), m_previousInventory.get( n ) ) ) |                     m_inventoryChanged = true; | ||||||
|                     { |                     break; | ||||||
|                         m_inventoryChanged = true; |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -587,8 +543,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default | |||||||
|     public void transferStateFrom( TileTurtle copy ) |     public void transferStateFrom( TileTurtle copy ) | ||||||
|     { |     { | ||||||
|         super.transferStateFrom( copy ); |         super.transferStateFrom( copy ); | ||||||
|         m_inventory = copy.m_inventory; |         Collections.copy( m_inventory, copy.m_inventory ); | ||||||
|         m_previousInventory = copy.m_previousInventory; |         Collections.copy( m_previousInventory, copy.m_previousInventory ); | ||||||
|         m_inventoryChanged = copy.m_inventoryChanged; |         m_inventoryChanged = copy.m_inventoryChanged; | ||||||
|         m_brain = copy.m_brain; |         m_brain = copy.m_brain; | ||||||
|         m_brain.setOwner( this ); |         m_brain.setOwner( this ); | ||||||
|   | |||||||
| @@ -248,7 +248,7 @@ public class TurtleTool extends AbstractTurtleUpgrade | |||||||
|         boolean canHarvest = state.canHarvestBlock( world, blockPosition, turtlePlayer ); |         boolean canHarvest = state.canHarvestBlock( world, blockPosition, turtlePlayer ); | ||||||
|         boolean canBreak = state.removedByPlayer( world, blockPosition, turtlePlayer, canHarvest, fluidState ); |         boolean canBreak = state.removedByPlayer( world, blockPosition, turtlePlayer, canHarvest, fluidState ); | ||||||
|         if( canBreak ) state.getBlock().onPlayerDestroy( world, blockPosition, state ); |         if( canBreak ) state.getBlock().onPlayerDestroy( world, blockPosition, state ); | ||||||
|         if( canHarvest ) |         if( canHarvest && canBreak ) | ||||||
|         { |         { | ||||||
|             state.getBlock().harvestBlock( world, turtlePlayer, blockPosition, state, tile, turtlePlayer.getHeldItemMainhand() ); |             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 | -- Load in expect from the module path. | ||||||
|  |  | ||||||
| --- Expect an argument to have a specific type. |  | ||||||
| -- | -- | ||||||
| -- @tparam int index The 1-based argument index. | -- Ideally we'd use require, but that is part of the shell, and so is not | ||||||
| -- @param value The argument's value. | -- available to the BIOS or any APIs. All APIs load this using dofile, but that | ||||||
| -- @tparam string ... The allowed types of the argument. | -- has not been defined at this point. | ||||||
| -- @throws If the value is not one of the allowed types. | local expect | ||||||
| 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(...) | do | ||||||
|     for i = types.n, 1, -1 do |     local h = fs.open("rom/modules/main/cc/expect.lua", "r") | ||||||
|         if types[i] == "nil" then table.remove(types, i) end |     local f, err = loadstring(h.readAll(), "@expect.lua") | ||||||
|     end |     h.close() | ||||||
|  |  | ||||||
|     local type_names |     if not f then error(err) end | ||||||
|     if #types <= 1 then |     expect = f().expect | ||||||
|         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 | 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 _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 |     -- 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 |     local type = type | ||||||
| @@ -568,23 +539,28 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault ) | |||||||
|     return sLine |     return sLine | ||||||
| end | end | ||||||
|  |  | ||||||
| function loadfile( _sFile, _tEnv ) | function loadfile( filename, mode, env ) | ||||||
|     expect(1, _sFile, "string") |     -- Support the previous `loadfile(filename, env)` form instead. | ||||||
|     expect(2, _tEnv, "table", "nil") |     if type(mode) == "table" and env == nil then | ||||||
|  |         mode, env = nil, mode | ||||||
|     local file = fs.open( _sFile, "r" ) |  | ||||||
|     if file then |  | ||||||
|         local func, err = load( file.readAll(), "@" .. fs.getName( _sFile ), "t", _tEnv ) |  | ||||||
|         file.close() |  | ||||||
|         return func, err |  | ||||||
|     end |     end | ||||||
|     return nil, "File not found" |  | ||||||
|  |     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 | end | ||||||
|  |  | ||||||
| function dofile( _sFile ) | function dofile( _sFile ) | ||||||
|     expect(1, _sFile, "string") |     expect(1, _sFile, "string") | ||||||
|  |  | ||||||
|     local fnFile, e = loadfile( _sFile, _G ) |     local fnFile, e = loadfile( _sFile, nil, _G ) | ||||||
|     if fnFile then |     if fnFile then | ||||||
|         return fnFile() |         return fnFile() | ||||||
|     else |     else | ||||||
| @@ -600,7 +576,7 @@ function os.run( _tEnv, _sPath, ... ) | |||||||
|     local tArgs = table.pack( ... ) |     local tArgs = table.pack( ... ) | ||||||
|     local tEnv = _tEnv |     local tEnv = _tEnv | ||||||
|     setmetatable( tEnv, { __index = _G } ) |     setmetatable( tEnv, { __index = _G } ) | ||||||
|     local fnFile, err = loadfile( _sPath, tEnv ) |     local fnFile, err = loadfile( _sPath, nil, tEnv ) | ||||||
|     if fnFile then |     if fnFile then | ||||||
|         local ok, err = pcall( function() |         local ok, err = pcall( function() | ||||||
|             fnFile( table.unpack( tArgs, 1, tArgs.n ) ) |             fnFile( table.unpack( tArgs, 1, tArgs.n ) ) | ||||||
| @@ -634,7 +610,7 @@ function os.loadAPI( _sPath ) | |||||||
|  |  | ||||||
|     local tEnv = {} |     local tEnv = {} | ||||||
|     setmetatable( tEnv, { __index = _G } ) |     setmetatable( tEnv, { __index = _G } ) | ||||||
|     local fnAPI, err = loadfile( _sPath, tEnv ) |     local fnAPI, err = loadfile( _sPath, nil, tEnv ) | ||||||
|     if fnAPI then |     if fnAPI then | ||||||
|         local ok, err = pcall( fnAPI ) |         local ok, err = pcall( fnAPI ) | ||||||
|         if not ok then |         if not ok then | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| local expect = _G["~expect"] | local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||||
|  |  | ||||||
| -- Colors | -- Colors | ||||||
| white = 1 | white = 1 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| local expect = _G["~expect"] | local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||||
|  |  | ||||||
| CHANNEL_GPS = 65534 | 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" | local sPath = "/rom/help" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| -- Definition for the IO API | -- 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. | --- 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 | -- 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 | -- taught me anything, it's that emulating LWJGL's weird key handling is nigh-on | ||||||
| -- impossible. | -- impossible. | ||||||
|  |  | ||||||
| local expect = _G["~expect"] | local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||||
|  |  | ||||||
| local tKeys = {} | local tKeys = {} | ||||||
| tKeys[32] = 'space' | 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 ) | local function drawPixelInternal( xPos, yPos ) | ||||||
|     term.setCursorPos( 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 | 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_BROADCAST = 65535 | ||||||
| CHANNEL_REPEAT = 65533 | CHANNEL_REPEAT = 65533 | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| local expect = _G["~expect"] | local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||||
|  |  | ||||||
| local tSettings = {} | 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 native = (term.native and term.native()) or term | ||||||
| local redirectTarget = native | 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 ) | function slowWrite( sText, nRate ) | ||||||
|     expect(2, nRate, "number", "nil") |     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 = { | local tHex = { | ||||||
|     [ colors.white ] = "0", |     [ colors.white ] = "0", | ||||||
| @@ -388,6 +388,16 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible ) | |||||||
|         return nBackgroundColor |         return nBackgroundColor | ||||||
|     end |     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 |     -- Other functions | ||||||
|     function window.setVisible( bVis ) |     function window.setVisible( bVis ) | ||||||
|         if type(bVis) ~= "boolean" then expect(1, bVis, "boolean") end |         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 | # New features in CC: Tweaked 1.83.1 | ||||||
|  |  | ||||||
| * Add several new MOTD messages (JakobDev) | * 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: | And several bug fixes: | ||||||
| * Fix type check in `rednet.lookup` | * Ensure file error messages use the absolute correct path | ||||||
| * Error if turtle and pocket computer programs are run on the wrong system (JakobDev) | * Fix NPE when closing a file multiple times. | ||||||
| * Do not discard varargs after a nil. | * 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. | Type "help changelog" to see the full version history. | ||||||
|   | |||||||
| @@ -23,3 +23,4 @@ getPosition() | |||||||
| reposition( x, y, width, height ) | reposition( x, y, width, height ) | ||||||
| getPaletteColor( color ) | getPaletteColor( color ) | ||||||
| setPaletteColor( color, r, g, b ) | 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 | -- Setup process switching | ||||||
| local parentTerm = term.current() | local parentTerm = term.current() | ||||||
|   | |||||||
| @@ -329,7 +329,7 @@ local tMenuFuncs = { | |||||||
|                 printer.setPageTitle( sName.." (page "..nPage..")" ) |                 printer.setPageTitle( sName.." (page "..nPage..")" ) | ||||||
|             end |             end | ||||||
|  |  | ||||||
|             while not printer.newPage()    do |             while not printer.newPage() do | ||||||
|                 if printer.getInkLevel() < 1 then |                 if printer.getInkLevel() < 1 then | ||||||
|                     sStatus = "Printer out of ink, please refill" |                     sStatus = "Printer out of ink, please refill" | ||||||
|                 elseif printer.getPaperLevel() < 1 then |                 elseif printer.getPaperLevel() < 1 then | ||||||
| @@ -342,7 +342,6 @@ local tMenuFuncs = { | |||||||
|                 redrawMenu() |                 redrawMenu() | ||||||
|                 term.redirect( printerTerminal ) |                 term.redirect( printerTerminal ) | ||||||
|  |  | ||||||
|                 local timer = os.startTimer(0.5) |  | ||||||
|                 sleep(0.5) |                 sleep(0.5) | ||||||
|             end |             end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| local expect = _G["~expect"] | local expect = dofile("rom/modules/main/cc/expect.lua").expect | ||||||
|  |  | ||||||
| local multishell = multishell | local multishell = multishell | ||||||
| local parentShell = shell | local parentShell = shell | ||||||
| @@ -56,7 +56,7 @@ local function createShellEnv( sDir ) | |||||||
|                     sPath = fs.combine(sDir, sPath) |                     sPath = fs.combine(sDir, sPath) | ||||||
|                 end |                 end | ||||||
|                 if fs.exists(sPath) and not fs.isDir(sPath) then |                 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 |                     if fnFile then | ||||||
|                         return fnFile, sPath |                         return fnFile, sPath | ||||||
|                     else |                     else | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ public class ComputerTestDelegate | |||||||
|         try( WritableByteChannel channel = mount.openChannelForWrite( "startup.lua" ); |         try( WritableByteChannel channel = mount.openChannelForWrite( "startup.lua" ); | ||||||
|              Writer writer = Channels.newWriter( channel, StandardCharsets.UTF_8.newEncoder(), -1 ) ) |              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 ); |         computer = new Computer( new BasicEnvironment( mount ), term, 0 ); | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ public class ComputerBootstrap | |||||||
|     { |     { | ||||||
|         MemoryMount mount = new MemoryMount() |         MemoryMount mount = new MemoryMount() | ||||||
|             .addFile( "test.lua", program ) |             .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 -> { } ); |         run( mount, x -> { } ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -27,18 +27,58 @@ local function check(func, arg, ty, val) | |||||||
|     end |     end | ||||||
| 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 = {} | local active_stubs = {} | ||||||
|  |  | ||||||
| --- Stub a global variable with a specific value | local function default_stub() end | ||||||
| -- |  | ||||||
| -- @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) |  | ||||||
|  |  | ||||||
|     table.insert(active_stubs, { tbl = tbl, var = var, value = tbl[var] }) | --- Stub a table entry with a new value. | ||||||
|     rawset(tbl, var, 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 | end | ||||||
|  |  | ||||||
| --- Capture the current global state of the computer | --- Capture the current global state of the computer | ||||||
| @@ -51,16 +91,14 @@ local function push_state() | |||||||
|         output = io.output(), |         output = io.output(), | ||||||
|         dir = shell.dir(), |         dir = shell.dir(), | ||||||
|         path = shell.path(), |         path = shell.path(), | ||||||
|  |         aliases = shell.aliases(), | ||||||
|         stubs = stubs, |         stubs = stubs, | ||||||
|     } |     } | ||||||
| end | end | ||||||
|  |  | ||||||
| --- Restore the global state of the computer to a previous version | --- Restore the global state of the computer to a previous version | ||||||
| local function pop_state(state) | local function pop_state(state) | ||||||
|     for i = #active_stubs, 1, -1 do |     for i = #active_stubs, 1, -1 do active_stubs[i]:revert() end | ||||||
|         local stub = active_stubs[i] |  | ||||||
|         rawset(stub.tbl, stub.var, stub.value) |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     active_stubs = state.stubs |     active_stubs = state.stubs | ||||||
|  |  | ||||||
| @@ -69,6 +107,14 @@ local function pop_state(state) | |||||||
|     io.output(state.output) |     io.output(state.output) | ||||||
|     shell.setDir(state.dir) |     shell.setDir(state.dir) | ||||||
|     shell.setPath(state.path) |     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 | end | ||||||
|  |  | ||||||
| local error_mt = { __tostring = function(self) return self.message end } | local error_mt = { __tostring = function(self) return self.message end } | ||||||
| @@ -210,6 +256,16 @@ local function matches(eq, exact, left, right) | |||||||
|     return true |     return true | ||||||
| end | 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 | --- Assert that this expectation is structurally equivalent to | ||||||
| -- the provided object. | -- the provided object. | ||||||
| -- | -- | ||||||
| @@ -236,6 +292,70 @@ function expect_mt:matches(value) | |||||||
|     return self |     return self | ||||||
| end | 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( { | local expect = setmetatable( { | ||||||
|     --- Construct an expectation on the error message calling this function |     --- Construct an expectation on the error message calling this function | ||||||
|     -- produces |     -- produces | ||||||
| @@ -381,7 +501,7 @@ do | |||||||
|             if fs.isDir(file) then |             if fs.isDir(file) then | ||||||
|                 run_in(file) |                 run_in(file) | ||||||
|             elseif file:sub(-#suffix) == suffix then |             elseif file:sub(-#suffix) == suffix then | ||||||
|                 local fun, err = loadfile(file, env) |                 local fun, err = loadfile(file, nil, env) | ||||||
|                 if not fun then |                 if not fun then | ||||||
|                     do_test { name = file:sub(#root_dir + 2), error = { message = err } } |                     do_test { name = file:sub(#root_dir + 2), error = { message = err } } | ||||||
|                 else |                 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)") |             expect.error(w.reposition, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)") | ||||||
|         end) |         end) | ||||||
|     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) | end) | ||||||
|   | |||||||
| @@ -1,36 +1,4 @@ | |||||||
| describe("The Lua base library", function() | 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() |     describe("sleep", function() | ||||||
|         it("validates arguments", function() |         it("validates arguments", function() | ||||||
|             sleep(0) |             sleep(0) | ||||||
| @@ -48,18 +16,43 @@ describe("The Lua base library", function() | |||||||
|     end) |     end) | ||||||
|  |  | ||||||
|     describe("loadfile", function() |     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() |         it("validates arguments", function() | ||||||
|             loadfile("") |             loadfile("") | ||||||
|             loadfile("", {}) |             loadfile("", "") | ||||||
|  |             loadfile("", "", {}) | ||||||
|  |  | ||||||
|             expect.error(loadfile, nil):eq("bad argument #1 (expected string, got nil)") |             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) |         end) | ||||||
|  |  | ||||||
|         it("prefixes the filename with @", function() |         it("prefixes the filename with @", function() | ||||||
|             local info = debug.getinfo(loadfile("/rom/startup.lua"), "S") |             local info = debug.getinfo(loadfile("/rom/startup.lua"), "S") | ||||||
|             expect(info):matches { short_src = "startup.lua", source = "@startup.lua" } |             expect(info):matches { short_src = "startup.lua", source = "@startup.lua" } | ||||||
|         end) |         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) |     end) | ||||||
|  |  | ||||||
|     describe("dofile", function() |     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 | local capture = require "test_helpers".capture_program | ||||||
|  |  | ||||||
| describe("The cd program", function() | describe("The cd program", function() | ||||||
|  |     it("changes into a directory", function() | ||||||
|     it("cd into a directory", function() |         local setDir = stub(shell, "setDir") | ||||||
|         shell.run("cd /rom/programs") |         capture(stub, "cd /rom/programs") | ||||||
|          |         expect(setDir):called_with("rom/programs") | ||||||
|         expect(shell.dir()):eq("rom/programs") |  | ||||||
|     end) |     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")) |         expect(capture(stub, "cd /rom/nothing")) | ||||||
|             :matches { ok = true, output = "Not a directory\n", error = "" } |             :matches { ok = true, output = "Not a directory\n", error = "" } | ||||||
|     end) |     end) | ||||||
|      |  | ||||||
|     it("displays the usage with no arguments", function() |     it("displays the usage when given no arguments", function() | ||||||
|         expect(capture(stub, "cd")) |         expect(capture(stub, "cd")) | ||||||
|             :matches { ok = true, output = "Usage: cd <path>\n", error = "" } |             :matches { ok = true, output = "Usage: cd <path>\n", error = "" } | ||||||
|     end) |     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 capture = require "test_helpers".capture_program | ||||||
|  | local testFile = require "test_helpers".testFile | ||||||
|  |  | ||||||
| describe("The edit program", function() | describe("The edit program", function() | ||||||
|  |  | ||||||
|     it("displays its usage when given no argument", function() |     it("displays its usage when given no argument", function()       | ||||||
|         multishell = nil |  | ||||||
|          |  | ||||||
|         expect(capture(stub, "edit")) |         expect(capture(stub, "edit")) | ||||||
|             :matches { ok = true, output = "Usage: edit <path>\n", error = "" } |             :matches { ok = true, output = "Usage: edit <path>\n", error = "" } | ||||||
|     end) |     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
	 SquidDev
					SquidDev