mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-03 23:22:59 +00:00 
			
		
		
		
	Compare commits
	
		
			84 Commits
		
	
	
		
			v1.12.2-1.
			...
			v1.12.2-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					46d78af068 | ||
| 
						 | 
					eb5cff1045 | ||
| 
						 | 
					35c7792aa2 | ||
| 
						 | 
					521688d630 | ||
| 
						 | 
					75e2845c01 | ||
| 
						 | 
					2f96283286 | ||
| 
						 | 
					cbe6e9b5f5 | ||
| 
						 | 
					2ab79cf474 | ||
| 
						 | 
					6ce34aba79 | ||
| 
						 | 
					5eeb320b60 | ||
| 
						 | 
					93310850d2 | ||
| 
						 | 
					a2880b12ca | ||
| 
						 | 
					303b57779a | ||
| 
						 | 
					6279816ecc | ||
| 
						 | 
					4ae77261fa | ||
| 
						 | 
					4b7d843b78 | ||
| 
						 | 
					1c28df65c3 | ||
| 
						 | 
					85b740f484 | ||
| 
						 | 
					f9929cb27d | ||
| 
						 | 
					bafab1ac07 | ||
| 
						 | 
					e05c262468 | ||
| 
						 | 
					7a3f7d3bba | ||
| 
						 | 
					95aa48c456 | ||
| 
						 | 
					904a168d5c | ||
| 
						 | 
					724441eddc | ||
| 
						 | 
					f68ab3edd1 | ||
| 
						 | 
					29dce26bf6 | ||
| 
						 | 
					717ab69093 | ||
| 
						 | 
					138a2cf08f | ||
| 
						 | 
					81daf82647 | ||
| 
						 | 
					f3798bfb63 | ||
| 
						 | 
					bc07dfad2e | ||
| 
						 | 
					309cbdb8be | ||
| 
						 | 
					a0e7c4a74c | ||
| 
						 | 
					7d428030df | ||
| 
						 | 
					00c395f689 | ||
| 
						 | 
					d8e1c73d26 | ||
| 
						 | 
					ffa4cc241b | ||
| 
						 | 
					6f1b740c8f | ||
| 
						 | 
					3406ba3ebf | ||
| 
						 | 
					6b81bcf334 | ||
| 
						 | 
					acac70675d | ||
| 
						 | 
					56434259c1 | ||
| 
						 | 
					da7e4b9016 | ||
| 
						 | 
					d4b8650d21 | ||
| 
						 | 
					17645a79f0 | ||
| 
						 | 
					ce1f14a010 | ||
| 
						 | 
					43050426de | ||
| 
						 | 
					b05f60c98b | ||
| 
						 | 
					c44c560f96 | ||
| 
						 | 
					e839ef54af | ||
| 
						 | 
					0cb659d78c | ||
| 
						 | 
					9048deeb95 | ||
| 
						 | 
					5592ebae7d | ||
| 
						 | 
					b076c32fd1 | ||
| 
						 | 
					a48f1e310f | ||
| 
						 | 
					19aca001d7 | ||
| 
						 | 
					114f913bf8 | ||
| 
						 | 
					1c9810890a | ||
| 
						 | 
					b11beb508b | ||
| 
						 | 
					af8d4da594 | ||
| 
						 | 
					a81db2cda6 | ||
| 
						 | 
					99bdff0f92 | ||
| 
						 | 
					5b0ce7410d | ||
| 
						 | 
					d5ea22d1a0 | ||
| 
						 | 
					210f3fa9e2 | ||
| 
						 | 
					d661cfa88b | ||
| 
						 | 
					68bf3a71dc | ||
| 
						 | 
					3cdb12d293 | ||
| 
						 | 
					ad33acd7d1 | ||
| 
						 | 
					0ec3884e98 | ||
| 
						 | 
					7f2471d6b2 | ||
| 
						 | 
					8fafec4915 | ||
| 
						 | 
					b9fd690ecb | ||
| 
						 | 
					2f2ada4416 | ||
| 
						 | 
					9c951c58d9 | ||
| 
						 | 
					4b4b47e231 | ||
| 
						 | 
					978c28a686 | ||
| 
						 | 
					b867ada5e5 | ||
| 
						 | 
					7071cc972b | ||
| 
						 | 
					6898f932a0 | ||
| 
						 | 
					2e0ef6385d | ||
| 
						 | 
					f93da7ea51 | ||
| 
						 | 
					1210bb8a4d | 
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
---
 | 
			
		||||
name: Bug report
 | 
			
		||||
about: Report some misbehaviour in the mod
 | 
			
		||||
 | 
			
		||||
labels: bug
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
---
 | 
			
		||||
name: Feature request
 | 
			
		||||
about: Suggest an idea or improvement
 | 
			
		||||
 | 
			
		||||
labels: enhancement
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
@@ -11,4 +11,4 @@ about: Suggest an idea or improvement
 | 
			
		||||
 | 
			
		||||
## Useful information to include:
 | 
			
		||||
 - Explanation of how the feature/change should work.
 | 
			
		||||
 - Some rationale/use case for a feature. I'd like to keep CC:T as minimal as possible, so I like have a solid justification for each feature.
 | 
			
		||||
 - Some rationale/use case for a feature. My general approach to designing new features is to ask yourself "what issue are we trying to solve" and _then_ "is this the best way to solve this issue?".
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
## A quick checklist
 | 
			
		||||
 - If there's a existing issue, please link to it. If not, provide fill out the same information you would in a normal issue - reproduction steps for bugs, rationale for use-case.
 | 
			
		||||
 - If you're working on CraftOS, try to write a few test cases so we can ensure everything continues to work in the future. Tests live in `src/test/resources/test-rom/spec` and can be run with `./gradlew check`.
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -15,3 +15,9 @@
 | 
			
		||||
.idea
 | 
			
		||||
.gradle
 | 
			
		||||
*.DS_Store
 | 
			
		||||
 | 
			
		||||
.classpath
 | 
			
		||||
.project
 | 
			
		||||
.settings/
 | 
			
		||||
bin/
 | 
			
		||||
*.launch
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,8 @@ ignore = {
 | 
			
		||||
-- are largely unsupported.
 | 
			
		||||
include_files = {
 | 
			
		||||
    'src/main/resources/assets/computercraft/lua/rom',
 | 
			
		||||
    'src/main/resources/assets/computercraft/lua/bios.lua'
 | 
			
		||||
    'src/main/resources/assets/computercraft/lua/bios.lua',
 | 
			
		||||
    'src/test/resources/test-rom',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
files['src/main/resources/assets/computercraft/lua/bios.lua'] = {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,4 +11,4 @@ cache:
 | 
			
		||||
    - $HOME/.gradle/wrapper/s
 | 
			
		||||
 | 
			
		||||
jdk:
 | 
			
		||||
    - oraclejdk8
 | 
			
		||||
    - openjdk8
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
# 
 | 
			
		||||
[](https://travis-ci.org/SquidDev-CC/CC-Tweaked)
 | 
			
		||||
[](https://travis-ci.org/SquidDev-CC/CC-Tweaked "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
 | 
			
		||||
 | 
			
		||||
CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers,
 | 
			
		||||
turtles and more to Minecraft.
 | 
			
		||||
@@ -9,7 +9,7 @@ ComputerCraft has always held a fond place in my heart: it's the mod which reall
 | 
			
		||||
mod which has kept me playing it for many years. However, development of the original mod has slowed, as the original
 | 
			
		||||
developers have had less time to work on the mod, and moved onto other projects and commitments.
 | 
			
		||||
 | 
			
		||||
CC:Tweaked (or CC:T for short) is an attempt to continue ComputerCraft's legacy. It's not intended to be a competitor
 | 
			
		||||
CC: Tweaked (or CC:T for short) is an attempt to continue ComputerCraft's legacy. It's not intended to be a competitor
 | 
			
		||||
to CC, nor do I want to take it in a vastly different direction to the original mod. Instead, CC:T focuses on making the
 | 
			
		||||
ComputerCraft experience as _solid_ as possible, ironing out any wrinkles that may have developed over time.
 | 
			
		||||
 | 
			
		||||
@@ -46,8 +46,17 @@ develop CC:T, you'll need to follow these steps:
 | 
			
		||||
 | 
			
		||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
 | 
			
		||||
 | 
			
		||||
## Community
 | 
			
		||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
 | 
			
		||||
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.gg/H2UyJXe)!
 | 
			
		||||
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=#computercraft), if
 | 
			
		||||
that's more your cup of tea.
 | 
			
		||||
 | 
			
		||||
I'd generally recommend you don't contact me directly (email, DM, etc...) unless absolutely necessary (i.e. in order to
 | 
			
		||||
report exploits). You'll get a far quicker response if you ask the whole community!
 | 
			
		||||
 | 
			
		||||
## Using
 | 
			
		||||
If you want to depend on CC:Tweaked, we have a maven repo. However, you should be wary that some functionality is only
 | 
			
		||||
If you want to depend on CC: Tweaked, we have a maven repo. However, you should be wary that some functionality is only
 | 
			
		||||
exposed by CC:T's API and not vanilla ComputerCraft. If you wish to support all variations of ComputerCraft, I recommend
 | 
			
		||||
using [cc.crzd.me's maven](https://cc.crzd.me/maven/) instead.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										152
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								build.gradle
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ buildscript {
 | 
			
		||||
        jcenter()
 | 
			
		||||
        maven {
 | 
			
		||||
            name = "forge"
 | 
			
		||||
            url = "http://files.minecraftforge.net/maven"
 | 
			
		||||
            url = "https://files.minecraftforge.net/maven"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    dependencies {
 | 
			
		||||
@@ -17,7 +17,9 @@ buildscript {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'com.matthewprenger.cursegradle' version '1.0.10'
 | 
			
		||||
    id "checkstyle"
 | 
			
		||||
    id "com.github.hierynomus.license" version "0.15.0"
 | 
			
		||||
    id "com.matthewprenger.cursegradle" version "1.3.0"
 | 
			
		||||
    id "com.github.breadmoirai.github-release" version "2.2.4"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +45,7 @@ minecraft {
 | 
			
		||||
repositories {
 | 
			
		||||
    maven {
 | 
			
		||||
        name "JEI"
 | 
			
		||||
        url "http://dvs1.progwml6.com/files/maven"
 | 
			
		||||
        url "https://dvs1.progwml6.com/files/maven"
 | 
			
		||||
    }
 | 
			
		||||
    maven {
 | 
			
		||||
        name "SquidDev"
 | 
			
		||||
@@ -55,7 +57,7 @@ repositories {
 | 
			
		||||
    }
 | 
			
		||||
    maven {
 | 
			
		||||
        name "Amadornes"
 | 
			
		||||
        url "http://maven.amadornes.com/"
 | 
			
		||||
        url "https://maven.amadornes.com/"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -66,6 +68,8 @@ configurations {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    checkstyle "com.puppycrawl.tools:checkstyle:8.21"
 | 
			
		||||
 | 
			
		||||
    deobfProvided "mezz.jei:jei_1.12.2:4.15.0.269:api"
 | 
			
		||||
    deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
 | 
			
		||||
    deobfProvided "MCMultiPart2:MCMultiPart:2.5.3"
 | 
			
		||||
@@ -74,12 +78,14 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'
 | 
			
		||||
 | 
			
		||||
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
 | 
			
		||||
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
 | 
			
		||||
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
 | 
			
		||||
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
 | 
			
		||||
 | 
			
		||||
    deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compile tasks
 | 
			
		||||
 | 
			
		||||
javadoc {
 | 
			
		||||
    include "dan200/computercraft/api/**/*.java"
 | 
			
		||||
}
 | 
			
		||||
@@ -98,12 +104,22 @@ jar {
 | 
			
		||||
    from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[compileJava, compileTestJava].forEach {
 | 
			
		||||
    it.configure {
 | 
			
		||||
        options.compilerArgs << "-Xlint" << "-Xlint:-processing" << "-Werror"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets
 | 
			
		||||
import java.nio.file.*
 | 
			
		||||
import java.util.zip.*
 | 
			
		||||
 | 
			
		||||
import com.google.gson.GsonBuilder
 | 
			
		||||
import com.google.gson.JsonElement
 | 
			
		||||
import com.hierynomus.gradle.license.tasks.LicenseCheck
 | 
			
		||||
import com.hierynomus.gradle.license.tasks.LicenseFormat
 | 
			
		||||
import org.ajoberstar.grgit.Grgit
 | 
			
		||||
import proguard.gradle.ProGuardTask
 | 
			
		||||
 | 
			
		||||
@@ -199,6 +215,7 @@ task compressJson(dependsOn: extractAnnotationsJar) {
 | 
			
		||||
        // Copy over all files in the current jar to the new one, running json files from GSON. As pretty printing
 | 
			
		||||
        // is turned off, they should be minified.
 | 
			
		||||
        new ZipFile(jarPath).withCloseable { inJar ->
 | 
			
		||||
            tempPath.getParentFile().mkdirs()
 | 
			
		||||
            new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempPath))).withCloseable { outJar ->
 | 
			
		||||
                inJar.entries().each { entry ->
 | 
			
		||||
                    if(entry.directory) {
 | 
			
		||||
@@ -226,12 +243,105 @@ task compressJson(dependsOn: extractAnnotationsJar) {
 | 
			
		||||
 | 
			
		||||
assemble.dependsOn compressJson
 | 
			
		||||
 | 
			
		||||
// Check tasks
 | 
			
		||||
 | 
			
		||||
test {
 | 
			
		||||
    useJUnitPlatform()
 | 
			
		||||
    testLogging {
 | 
			
		||||
        events "skipped", "failed"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
license {
 | 
			
		||||
    mapping("java", "SLASHSTAR_STYLE")
 | 
			
		||||
    strictCheck true
 | 
			
		||||
 | 
			
		||||
    ext.year = Calendar.getInstance().get(Calendar.YEAR)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[licenseMain, licenseFormatMain].forEach {
 | 
			
		||||
    it.configure {
 | 
			
		||||
        include("**/*.java")
 | 
			
		||||
        exclude("dan200/computercraft/api/**")
 | 
			
		||||
        header rootProject.file('config/license/main.txt')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[licenseTest, licenseFormatTest].forEach {
 | 
			
		||||
    it.configure {
 | 
			
		||||
        include("**/*.java")
 | 
			
		||||
        header rootProject.file('config/license/main.txt')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gradle.projectsEvaluated {
 | 
			
		||||
    tasks.withType(LicenseFormat) {
 | 
			
		||||
        outputs.upToDateWhen { false }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
task licenseAPI(type: LicenseCheck);
 | 
			
		||||
task licenseFormatAPI(type: LicenseFormat);
 | 
			
		||||
[licenseAPI, licenseFormatAPI].forEach {
 | 
			
		||||
    it.configure {
 | 
			
		||||
        source = sourceSets.main.java
 | 
			
		||||
        include("dan200/computercraft/api/**")
 | 
			
		||||
        header rootProject.file('config/license/api.txt')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Upload tasks
 | 
			
		||||
 | 
			
		||||
task checkRelease {
 | 
			
		||||
    group "upload"
 | 
			
		||||
    description "Verifies that everything is ready for a release"
 | 
			
		||||
 | 
			
		||||
    inputs.property "version", mod_version
 | 
			
		||||
    inputs.file("src/main/resources/assets/computercraft/lua/rom/help/changelog.txt")
 | 
			
		||||
    inputs.file("src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt")
 | 
			
		||||
 | 
			
		||||
    doLast {
 | 
			
		||||
        def ok = true
 | 
			
		||||
 | 
			
		||||
        // Check we're targetting the current version
 | 
			
		||||
        def whatsnew = new File("src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt").readLines()
 | 
			
		||||
        if (whatsnew[0] != "New features in CC: Tweaked $mod_version") {
 | 
			
		||||
            ok = false
 | 
			
		||||
            project.logger.error("Expected `whatsnew.txt' to target $mod_version.")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check "read more" exists and trim it
 | 
			
		||||
        def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
 | 
			
		||||
        if (idx == -1) {
 | 
			
		||||
            ok = false
 | 
			
		||||
            project.logger.error("Must mention the changelog in whatsnew.txt")
 | 
			
		||||
        } else {
 | 
			
		||||
            whatsnew = whatsnew.getAt(0 ..< idx)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check whatsnew and changelog match.
 | 
			
		||||
        def versionChangelog = "# " + whatsnew.join("\n")
 | 
			
		||||
        def changelog = new File("src/main/resources/assets/computercraft/lua/rom/help/changelog.txt").getText()
 | 
			
		||||
        if (!changelog.startsWith(versionChangelog)) {
 | 
			
		||||
            ok = false
 | 
			
		||||
            project.logger.error("whatsnew and changelog are not in sync")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!ok) throw new IllegalStateException("Could not check release")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
curseforge {
 | 
			
		||||
    apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
 | 
			
		||||
    project {
 | 
			
		||||
        id = '282001'
 | 
			
		||||
        releaseType = 'release'
 | 
			
		||||
        changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
 | 
			
		||||
 | 
			
		||||
        relations {
 | 
			
		||||
            incompatible "computercraft"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -292,33 +402,25 @@ githubRelease {
 | 
			
		||||
    token project.hasProperty('githubApiKey') ? project.githubApiKey : ''
 | 
			
		||||
    owner 'SquidDev-CC'
 | 
			
		||||
    repo 'CC-Tweaked'
 | 
			
		||||
    targetCommitish (mc_version == "1.12.2" ? "master" : mc_version)
 | 
			
		||||
    targetCommitish { Grgit.open(dir: '.').branch.current().name }
 | 
			
		||||
 | 
			
		||||
    tagName "v${mc_version}-${mod_version}"
 | 
			
		||||
    releaseName "[${mc_version}] ${mod_version}"
 | 
			
		||||
    body ''
 | 
			
		||||
    body {
 | 
			
		||||
        "## " + new File("src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt")
 | 
			
		||||
            .readLines()
 | 
			
		||||
            .takeWhile { it != 'Type "help changelog" to see the full version history.' }
 | 
			
		||||
            .join("\n").trim()
 | 
			
		||||
    }
 | 
			
		||||
    prerelease false
 | 
			
		||||
 | 
			
		||||
    releaseAssets.from(jar.archivePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
task uploadAll(dependsOn: proguard) {
 | 
			
		||||
def uploadTasks = ["uploadArchives", "curseforge", "githubRelease"]
 | 
			
		||||
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
 | 
			
		||||
 | 
			
		||||
task uploadAll(dependsOn: uploadTasks) {
 | 
			
		||||
    group "upload"
 | 
			
		||||
    description "Uploads to all repositories (Maven, Curse, GitHub release)"
 | 
			
		||||
    dependsOn uploadArchives, curseforge, githubRelease
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test {
 | 
			
		||||
    useJUnitPlatform()
 | 
			
		||||
    testLogging {
 | 
			
		||||
        events "passed", "skipped", "failed"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gradle.projectsEvaluated {
 | 
			
		||||
    tasks.withType(JavaCompile) {
 | 
			
		||||
        options.compilerArgs << "-Xlint" << "-Xlint:-processing" << "-Werror"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
runClient.outputs.upToDateWhen { false }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										159
									
								
								config/checkstyle/checkstyle.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								config/checkstyle/checkstyle.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!DOCTYPE module PUBLIC
 | 
			
		||||
    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
 | 
			
		||||
    "https://checkstyle.org/dtds/configuration_1_3.dtd">
 | 
			
		||||
<module name="Checker">
 | 
			
		||||
    <property name="tabWidth" value="4"/>
 | 
			
		||||
    <property name="charset" value="UTF-8" />
 | 
			
		||||
 | 
			
		||||
    <module name="SuppressionFilter">
 | 
			
		||||
        <property name="file" value="config/checkstyle/suppressions.xml" />
 | 
			
		||||
    </module>
 | 
			
		||||
 | 
			
		||||
    <module name="TreeWalker">
 | 
			
		||||
        <!-- Annotations -->
 | 
			
		||||
        <module name="AnnotationLocation" />
 | 
			
		||||
        <module name="AnnotationUseStyle" />
 | 
			
		||||
        <module name="MissingDeprecated">
 | 
			
		||||
            <property name="skipNoJavadoc" value="true" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="MissingOverride" />
 | 
			
		||||
 | 
			
		||||
        <!-- Blocks -->
 | 
			
		||||
        <module name="EmptyBlock" />
 | 
			
		||||
        <module name="EmptyCatchBlock">
 | 
			
		||||
            <property name="exceptionVariableName" value="ignored" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="LeftCurly">
 | 
			
		||||
            <property name="option" value="nl" />
 | 
			
		||||
            <!-- The defaults, minus lambdas. -->
 | 
			
		||||
            <property name="tokens" value="ANNOTATION_DEF,CLASS_DEF,CTOR_DEF,ENUM_CONSTANT_DEF,ENUM_DEF,INTERFACE_DEF,LITERAL_CASE,LITERAL_CATCH,LITERAL_DEFAULT,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,METHOD_DEF,OBJBLOCK,STATIC_INIT" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="NeedBraces">
 | 
			
		||||
            <property name="allowSingleLineStatement" value="true"/>
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="RightCurly">
 | 
			
		||||
            <property name="option" value="alone" />
 | 
			
		||||
        </module>
 | 
			
		||||
 | 
			
		||||
        <!-- Class design. As if we've ever followed good practice here. -->
 | 
			
		||||
        <module name="FinalClass" />
 | 
			
		||||
        <module name="InterfaceIsType" />
 | 
			
		||||
        <module name="MutableException" />
 | 
			
		||||
        <module name="OneTopLevelClass" />
 | 
			
		||||
 | 
			
		||||
        <!-- Coding -->
 | 
			
		||||
        <module name="ArrayTrailingComma" />
 | 
			
		||||
        <module name="EqualsHashCode" />
 | 
			
		||||
        <!-- FallThrough does not handle unreachable code well -->
 | 
			
		||||
        <module name="IllegalInstantiation" />
 | 
			
		||||
        <module name="IllegalThrows" />
 | 
			
		||||
        <module name="ModifiedControlVariable" />
 | 
			
		||||
        <module name="NoClone" />
 | 
			
		||||
        <module name="NoFinalizer" />
 | 
			
		||||
        <module name="OneStatementPerLine" />
 | 
			
		||||
        <module name="PackageDeclaration" />
 | 
			
		||||
        <module name="SimplifyBooleanExpression" />
 | 
			
		||||
        <module name="SimplifyBooleanReturn" />
 | 
			
		||||
        <module name="StringLiteralEquality" />
 | 
			
		||||
        <module name="UnnecessaryParentheses" />
 | 
			
		||||
 | 
			
		||||
        <!-- Imports -->
 | 
			
		||||
        <module name="CustomImportOrder" />
 | 
			
		||||
        <module name="IllegalImport" />
 | 
			
		||||
        <module name="RedundantImport" />
 | 
			
		||||
        <module name="UnusedImports" />
 | 
			
		||||
 | 
			
		||||
        <!-- Javadoc -->
 | 
			
		||||
        <module name="AtclauseOrder" />
 | 
			
		||||
        <!-- TODO: Cleanup our documentation before enabling JavadocMethod, JavadocStyle, JavadocType and SummaryJavadoc. -->
 | 
			
		||||
        <module name="NonEmptyAtclauseDescription" />
 | 
			
		||||
        <module name="SingleLineJavadoc" />
 | 
			
		||||
 | 
			
		||||
        <!-- Misc -->
 | 
			
		||||
        <module name="ArrayTypeStyle" />
 | 
			
		||||
        <module name="CommentsIndentation" />
 | 
			
		||||
        <module name="Indentation" />
 | 
			
		||||
        <module name="OuterTypeFilename" />
 | 
			
		||||
 | 
			
		||||
        <!-- Modifiers -->
 | 
			
		||||
        <module name="ModifierOrder" />
 | 
			
		||||
        <module name="RedundantModifier" />
 | 
			
		||||
 | 
			
		||||
        <!-- Naming -->
 | 
			
		||||
        <module name="ClassTypeParameterName" />
 | 
			
		||||
        <module name="InterfaceTypeParameterName" />
 | 
			
		||||
        <module name="LambdaParameterName" />
 | 
			
		||||
        <module name="LocalFinalVariableName" />
 | 
			
		||||
        <module name="LocalVariableName" />
 | 
			
		||||
        <!-- Allow an optional m_ on private members -->
 | 
			
		||||
        <module name="MemberName">
 | 
			
		||||
            <property name="applyToPrivate" value="false" />
 | 
			
		||||
            <property name="applyToPackage" value="false" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="MemberName">
 | 
			
		||||
            <property name="format" value="^(m_)?[a-z][a-zA-Z0-9]*$" />
 | 
			
		||||
            <property name="applyToPrivate" value="true" />
 | 
			
		||||
            <property name="applyToPackage" value="true" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="MethodName" />
 | 
			
		||||
        <module name="MethodTypeParameterName" />
 | 
			
		||||
        <module name="PackageName">
 | 
			
		||||
            <property name="format" value="^dan200\.computercraf(\.[a-z][a-z0-9]*)*" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="ParameterName" />
 | 
			
		||||
        <module name="StaticVariableName">
 | 
			
		||||
            <property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z]+)?$" />
 | 
			
		||||
            <property name="applyToPrivate" value="false" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="StaticVariableName">
 | 
			
		||||
            <property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z]+)?$" />
 | 
			
		||||
            <property name="applyToPrivate" value="true" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="TypeName" />
 | 
			
		||||
 | 
			
		||||
        <!-- Whitespace -->
 | 
			
		||||
        <module name="EmptyForInitializerPad"/>
 | 
			
		||||
        <module name="EmptyForIteratorPad">
 | 
			
		||||
            <property name="option" value="space"/>
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="GenericWhitespace" />
 | 
			
		||||
        <module name="MethodParamPad" />
 | 
			
		||||
        <module name="NoLineWrap" />
 | 
			
		||||
        <module name="NoWhitespaceAfter">
 | 
			
		||||
            <property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="NoWhitespaceBefore" />
 | 
			
		||||
        <!-- TODO: Decide on an OperatorWrap style. -->
 | 
			
		||||
        <module name="ParenPad">
 | 
			
		||||
            <property name="option" value="space" />
 | 
			
		||||
            <property name="tokens" value="ANNOTATION,ANNOTATION_FIELD_DEF,CTOR_CALL,CTOR_DEF,ENUM_CONSTANT_DEF,LITERAL_CATCH,LITERAL_DO,LITERAL_FOR,LITERAL_IF,LITERAL_NEW,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_WHILE,METHOD_CALL,METHOD_DEF,RESOURCE_SPECIFICATION,SUPER_CTOR_CALL,LAMBDA" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="ParenPad">
 | 
			
		||||
            <property name="option" value="nospace" />
 | 
			
		||||
            <property name="tokens" value="DOT,EXPR,QUESTION" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="SeparatorWrap">
 | 
			
		||||
            <property name="option" value="eol" />
 | 
			
		||||
            <property name="tokens" value="COMMA,SEMI,ELLIPSIS,ARRAY_DECLARATOR,RBRACK,METHOD_REF" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="SeparatorWrap">
 | 
			
		||||
            <property name="option" value="nl" />
 | 
			
		||||
            <property name="tokens" value="DOT,AT" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="SingleSpaceSeparator" />
 | 
			
		||||
        <module name="TypecastParenPad" />
 | 
			
		||||
        <module name="WhitespaceAfter">
 | 
			
		||||
            <property name="tokens" value="COMMA" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="WhitespaceAround">
 | 
			
		||||
            <property name="allowEmptyConstructors" value="true" />
 | 
			
		||||
            <property name="ignoreEnhancedForColon" value="false" />
 | 
			
		||||
            <property name="tokens" value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,DO_WHILE,EQUAL,GE,GT,LAMBDA,LAND,LCURLY,LE,LITERAL_RETURN,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS,PLUS_ASSIGN,QUESTION,RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND" />
 | 
			
		||||
        </module>
 | 
			
		||||
    </module>
 | 
			
		||||
 | 
			
		||||
    <module name="FileTabCharacter" />
 | 
			
		||||
    <module name="NewlineAtEndOfFile" />
 | 
			
		||||
</module>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								config/checkstyle/suppressions.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								config/checkstyle/suppressions.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!DOCTYPE suppressions PUBLIC
 | 
			
		||||
    "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
 | 
			
		||||
    "https://checkstyle.org/dtds/suppressions_1_2.dtd">
 | 
			
		||||
<suppressions>
 | 
			
		||||
    <!-- All the config options and method fields. -->
 | 
			
		||||
    <suppress checks="StaticVariableName" files=".*[\\/]ComputerCraft.java" />
 | 
			
		||||
    <suppress checks="StaticVariableName" files=".*[\\/]ComputerCraftAPI.java" />
 | 
			
		||||
</suppressions>
 | 
			
		||||
							
								
								
									
										3
									
								
								config/license/api.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								config/license/api.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
Copyright Daniel Ratcliffe, 2011-${year}. This API may be redistributed unmodified and in full only.
 | 
			
		||||
For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
							
								
								
									
										3
									
								
								config/license/main.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								config/license/main.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
Copyright Daniel Ratcliffe, 2011-${year}. Do not distribute without permission.
 | 
			
		||||
Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# Mod properties
 | 
			
		||||
mod_version=1.82.2
 | 
			
		||||
mod_version=1.84.0
 | 
			
		||||
 | 
			
		||||
# Minecraft properties
 | 
			
		||||
mc_version=1.12.2
 | 
			
		||||
 
 | 
			
		||||
@@ -72,9 +72,7 @@ import net.minecraft.util.math.BlockPos;
 | 
			
		||||
import net.minecraft.world.IBlockAccess;
 | 
			
		||||
import net.minecraft.world.World;
 | 
			
		||||
import net.minecraftforge.common.DimensionManager;
 | 
			
		||||
import net.minecraftforge.fml.common.FMLCommonHandler;
 | 
			
		||||
import net.minecraftforge.fml.common.Mod;
 | 
			
		||||
import net.minecraftforge.fml.common.SidedProxy;
 | 
			
		||||
import net.minecraftforge.fml.common.*;
 | 
			
		||||
import net.minecraftforge.fml.common.event.*;
 | 
			
		||||
import net.minecraftforge.fml.relauncher.Side;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
@@ -83,9 +81,7 @@ import java.io.*;
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URISyntaxException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.EnumSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.zip.ZipEntry;
 | 
			
		||||
import java.util.zip.ZipFile;
 | 
			
		||||
@@ -394,6 +390,27 @@ public class ComputerCraft
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void loadFromFile( List<IMount> mounts, File file, String path, boolean allowMissing )
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if( file.isFile() )
 | 
			
		||||
            {
 | 
			
		||||
                mounts.add( new JarMount( file, path ) );
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                File subResource = new File( file, path );
 | 
			
		||||
                if( subResource.exists() ) mounts.add( new FileMount( subResource, 0 ) );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch( IOException | RuntimeException e )
 | 
			
		||||
        {
 | 
			
		||||
            if( allowMissing && e instanceof FileNotFoundException ) return;
 | 
			
		||||
            ComputerCraft.log.error( "Could not load mount '" + path + " 'from '" + file.getName() + "'", e );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public static IMount createResourceMount( Class<?> modClass, String domain, String subPath )
 | 
			
		||||
    {
 | 
			
		||||
@@ -413,18 +430,26 @@ public class ComputerCraft
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Mount from mod jar
 | 
			
		||||
        // Mount from mod jars, preferring the specified one.
 | 
			
		||||
        File modJar = getContainingJar( modClass );
 | 
			
		||||
        Set<File> otherMods = new HashSet<>();
 | 
			
		||||
        for( ModContainer container : Loader.instance().getActiveModList() )
 | 
			
		||||
        {
 | 
			
		||||
            File modFile = container.getSource();
 | 
			
		||||
            if( modFile != null && !modFile.equals( modJar ) && modFile.exists() )
 | 
			
		||||
            {
 | 
			
		||||
                otherMods.add( container.getSource() );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for( File file : otherMods )
 | 
			
		||||
        {
 | 
			
		||||
            loadFromFile( mounts, file, subPath, true );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if( modJar != null )
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                mounts.add( new JarMount( modJar, subPath ) );
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException | RuntimeException e )
 | 
			
		||||
            {
 | 
			
		||||
                ComputerCraft.log.error( "Could not load mount from mod jar", e );
 | 
			
		||||
            }
 | 
			
		||||
            loadFromFile( mounts, modJar, subPath, false );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Mount from resource packs
 | 
			
		||||
@@ -434,28 +459,8 @@ public class ComputerCraft
 | 
			
		||||
            String[] resourcePacks = resourcePackDir.list();
 | 
			
		||||
            for( String resourcePackName : resourcePacks )
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    File resourcePack = new File( resourcePackDir, resourcePackName );
 | 
			
		||||
                    if( !resourcePack.isDirectory() )
 | 
			
		||||
                    {
 | 
			
		||||
                        // Mount a resource pack from a jar
 | 
			
		||||
                        mounts.add( new JarMount( resourcePack, subPath ) );
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        // Mount a resource pack from a folder
 | 
			
		||||
                        File subResource = new File( resourcePack, subPath );
 | 
			
		||||
                        if( subResource.exists() ) mounts.add( new FileMount( subResource, 0 ) );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                catch( FileNotFoundException ignored )
 | 
			
		||||
                {
 | 
			
		||||
                }
 | 
			
		||||
                catch( IOException | RuntimeException e )
 | 
			
		||||
                {
 | 
			
		||||
                    ComputerCraft.log.error( "Could not load resource pack '" + resourcePackName + "'", e );
 | 
			
		||||
                }
 | 
			
		||||
                File resourcePack = new File( resourcePackDir, resourcePackName );
 | 
			
		||||
                loadFromFile( mounts, resourcePack, subPath, true );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
 
 | 
			
		||||
@@ -438,45 +438,45 @@ public final class ComputerCraftAPI
 | 
			
		||||
                computerCraft_getVersion = findCCMethod( "getVersion", new Class<?>[] {
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_createUniqueNumberedSaveDir = findCCMethod( "createUniqueNumberedSaveDir", new Class<?>[] {
 | 
			
		||||
                    World.class, String.class
 | 
			
		||||
                    World.class, String.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_createSaveDirMount = findCCMethod( "createSaveDirMount", new Class<?>[] {
 | 
			
		||||
                    World.class, String.class, Long.TYPE
 | 
			
		||||
                    World.class, String.class, Long.TYPE,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_createResourceMount = findCCMethod( "createResourceMount", new Class<?>[] {
 | 
			
		||||
                    Class.class, String.class, String.class
 | 
			
		||||
                    Class.class, String.class, String.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerPeripheralProvider = findCCMethod( "registerPeripheralProvider", new Class<?>[] {
 | 
			
		||||
                    IPeripheralProvider.class
 | 
			
		||||
                    IPeripheralProvider.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerTurtleUpgrade = findCCMethod( "registerTurtleUpgrade", new Class<?>[] {
 | 
			
		||||
                    ITurtleUpgrade.class
 | 
			
		||||
                    ITurtleUpgrade.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerBundledRedstoneProvider = findCCMethod( "registerBundledRedstoneProvider", new Class<?>[] {
 | 
			
		||||
                    IBundledRedstoneProvider.class
 | 
			
		||||
                    IBundledRedstoneProvider.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_getDefaultBundledRedstoneOutput = findCCMethod( "getDefaultBundledRedstoneOutput", new Class<?>[] {
 | 
			
		||||
                    World.class, BlockPos.class, EnumFacing.class
 | 
			
		||||
                    World.class, BlockPos.class, EnumFacing.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerMediaProvider = findCCMethod( "registerMediaProvider", new Class<?>[] {
 | 
			
		||||
                    IMediaProvider.class
 | 
			
		||||
                    IMediaProvider.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerPermissionProvider = findCCMethod( "registerPermissionProvider", new Class<?>[] {
 | 
			
		||||
                    ITurtlePermissionProvider.class
 | 
			
		||||
                    ITurtlePermissionProvider.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerPocketUpgrade = findCCMethod( "registerPocketUpgrade", new Class<?>[] {
 | 
			
		||||
                    IPocketUpgrade.class
 | 
			
		||||
                    IPocketUpgrade.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_getWirelessNetwork = findCCMethod( "getWirelessNetwork", new Class<?>[] {
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] {
 | 
			
		||||
                    ILuaAPIFactory.class
 | 
			
		||||
                    ILuaAPIFactory.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class<?>[] {
 | 
			
		||||
                    IWiredElement.class
 | 
			
		||||
                    IWiredElement.class,
 | 
			
		||||
                } );
 | 
			
		||||
                computerCraft_getWiredElementAt = findCCMethod( "getWiredElementAt", new Class<?>[] {
 | 
			
		||||
                    IBlockAccess.class, BlockPos.class, EnumFacing.class
 | 
			
		||||
                    IBlockAccess.class, BlockPos.class, EnumFacing.class,
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
            catch( Exception e )
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.filesystem;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An {@link IOException} which occurred on a specific file.
 | 
			
		||||
 *
 | 
			
		||||
 * This may be thrown from a {@link IMount} or {@link IWritableMount} to give more information about a failure.
 | 
			
		||||
 */
 | 
			
		||||
public class FileOperationException extends IOException
 | 
			
		||||
{
 | 
			
		||||
    private static final long serialVersionUID = -8809108200853029849L;
 | 
			
		||||
 | 
			
		||||
    private final String filename;
 | 
			
		||||
 | 
			
		||||
    public FileOperationException( @Nullable String filename, @Nonnull String message )
 | 
			
		||||
    {
 | 
			
		||||
        super( Objects.requireNonNull( message, "message cannot be null" ) );
 | 
			
		||||
        this.filename = filename;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public FileOperationException( String message )
 | 
			
		||||
    {
 | 
			
		||||
        super( Objects.requireNonNull( message, "message cannot be null" ) );
 | 
			
		||||
        this.filename = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public String getFilename()
 | 
			
		||||
    {
 | 
			
		||||
        return filename;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -90,7 +90,6 @@ public interface IMount
 | 
			
		||||
     * @throws IOException If the file does not exist, or could not be opened.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @SuppressWarnings( "deprecation" )
 | 
			
		||||
    default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        return Channels.newChannel( openForRead( path ) );
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,6 @@ public interface IWritableMount extends IMount
 | 
			
		||||
     * @throws IOException If the file could not be opened for writing.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @SuppressWarnings( "deprecation" )
 | 
			
		||||
    default WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        return Channels.newChannel( openForWrite( path ) );
 | 
			
		||||
@@ -94,7 +93,6 @@ public interface IWritableMount extends IMount
 | 
			
		||||
     * @throws IOException If the file could not be opened for writing.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @SuppressWarnings( "deprecation" )
 | 
			
		||||
    default WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        return Channels.newChannel( openForAppend( path ) );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.peripheral;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.item.ItemStack;
 | 
			
		||||
 
 | 
			
		||||
@@ -145,7 +145,9 @@ public interface ITurtleAccess
 | 
			
		||||
    GameProfile getOwningPlayer();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the inventory of this turtle
 | 
			
		||||
     * Get the inventory of this turtle.
 | 
			
		||||
     *
 | 
			
		||||
     * Note: this inventory should only be accessed and modified on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This turtle's inventory
 | 
			
		||||
     * @see #getItemHandler()
 | 
			
		||||
@@ -156,6 +158,8 @@ public interface ITurtleAccess
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
 | 
			
		||||
     *
 | 
			
		||||
     * Note: this inventory should only be accessed and modified on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This turtle's inventory
 | 
			
		||||
     * @see #getInventory()
 | 
			
		||||
     * @see IItemHandlerModifiable
 | 
			
		||||
 
 | 
			
		||||
@@ -109,8 +109,8 @@ public interface ITurtleUpgrade
 | 
			
		||||
     * Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
 | 
			
		||||
     * by the turtle, and the tool is required to do some work.
 | 
			
		||||
     *
 | 
			
		||||
     * Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging,
 | 
			
		||||
     * {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
 | 
			
		||||
     * Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig} for
 | 
			
		||||
     * digging, {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle    Access to the turtle that the tool resides on.
 | 
			
		||||
     * @param side      Which side of the turtle (left or right) the tool resides on.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle.event;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.lua.ILuaContext;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle.event;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,10 @@ import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class GuiComputer extends GuiContainer
 | 
			
		||||
{
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/corners.png" );
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation( "computercraft", "textures/gui/corners_advanced.png" );
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation( "computercraft", "textures/gui/corners_command.png" );
 | 
			
		||||
    public static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners.png" );
 | 
			
		||||
    public static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_advanced.png" );
 | 
			
		||||
    public static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_command.png" );
 | 
			
		||||
    public static final ResourceLocation BACKGROUND_COLOUR = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_colour.png" );
 | 
			
		||||
 | 
			
		||||
    private final ComputerFamily m_family;
 | 
			
		||||
    private final ClientComputer m_computer;
 | 
			
		||||
@@ -158,7 +159,7 @@ public class GuiComputer extends GuiContainer
 | 
			
		||||
        {
 | 
			
		||||
            case Normal:
 | 
			
		||||
            default:
 | 
			
		||||
                mc.getTextureManager().bindTexture( BACKGROUND );
 | 
			
		||||
                mc.getTextureManager().bindTexture( BACKGROUND_NORMAL );
 | 
			
		||||
                break;
 | 
			
		||||
            case Advanced:
 | 
			
		||||
                mc.getTextureManager().bindTexture( BACKGROUND_ADVANCED );
 | 
			
		||||
@@ -169,12 +170,12 @@ public class GuiComputer extends GuiContainer
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        drawTexturedModalRect( startX - 12, startY - 12, 12, 28, 12, 12 );
 | 
			
		||||
        drawTexturedModalRect( startX - 12, endY, 12, 40, 12, 16 );
 | 
			
		||||
        drawTexturedModalRect( startX - 12, endY, 12, 40, 12, 12 );
 | 
			
		||||
        drawTexturedModalRect( endX, startY - 12, 24, 28, 12, 12 );
 | 
			
		||||
        drawTexturedModalRect( endX, endY, 24, 40, 12, 16 );
 | 
			
		||||
        drawTexturedModalRect( endX, endY, 24, 40, 12, 12 );
 | 
			
		||||
 | 
			
		||||
        drawTexturedModalRect( startX, startY - 12, 0, 0, endX - startX, 12 );
 | 
			
		||||
        drawTexturedModalRect( startX, endY, 0, 12, endX - startX, 16 );
 | 
			
		||||
        drawTexturedModalRect( startX, endY, 0, 12, endX - startX, 12 );
 | 
			
		||||
 | 
			
		||||
        drawTexturedModalRect( startX - 12, startY, 0, 28, 12, endY - startY );
 | 
			
		||||
        drawTexturedModalRect( endX, startY, 36, 28, 12, endY - startY );
 | 
			
		||||
 
 | 
			
		||||
@@ -120,7 +120,7 @@ public class GuiTurtle extends GuiContainer
 | 
			
		||||
            int slotX = slot % 4;
 | 
			
		||||
            int slotY = slot / 4;
 | 
			
		||||
            mc.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
 | 
			
		||||
            drawTexturedModalRect( guiLeft + m_container.m_turtleInvStartX - 2 + slotX * 18, guiTop + m_container.m_playerInvStartY - 2 + slotY * 18, 0, 217, 24, 24 );
 | 
			
		||||
            drawTexturedModalRect( guiLeft + m_container.turtleInvStartX - 2 + slotX * 18, guiTop + m_container.playerInvStartY - 2 + slotY * 18, 0, 217, 24, 24 );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -29,13 +29,13 @@ import net.minecraftforge.fml.relauncher.Side;
 | 
			
		||||
import org.lwjgl.opengl.GL11;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
 | 
			
		||||
public final class RenderOverlayCable
 | 
			
		||||
public final class CableHighlightRenderer
 | 
			
		||||
{
 | 
			
		||||
    private static final float EXPAND = 0.002f;
 | 
			
		||||
    private static final double MIN = CableBounds.MIN - EXPAND;
 | 
			
		||||
    private static final double MAX = CableBounds.MAX + EXPAND;
 | 
			
		||||
 | 
			
		||||
    private RenderOverlayCable()
 | 
			
		||||
    private CableHighlightRenderer()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -12,25 +12,24 @@ import dan200.computercraft.client.gui.FixedWidthFontRenderer;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.core.terminal.TextBuffer;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ClientComputer;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
 | 
			
		||||
import dan200.computercraft.shared.util.Colour;
 | 
			
		||||
import dan200.computercraft.shared.util.Palette;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.renderer.BufferBuilder;
 | 
			
		||||
import net.minecraft.client.renderer.GlStateManager;
 | 
			
		||||
import net.minecraft.client.renderer.RenderItem;
 | 
			
		||||
import net.minecraft.client.renderer.block.model.IBakedModel;
 | 
			
		||||
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureManager;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureMap;
 | 
			
		||||
import net.minecraft.client.renderer.Tessellator;
 | 
			
		||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
 | 
			
		||||
import net.minecraft.item.ItemStack;
 | 
			
		||||
import net.minecraftforge.client.ForgeHooksClient;
 | 
			
		||||
import net.minecraftforge.client.event.RenderSpecificHandEvent;
 | 
			
		||||
import net.minecraftforge.fml.common.Mod;
 | 
			
		||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
 | 
			
		||||
import net.minecraftforge.fml.relauncher.Side;
 | 
			
		||||
import org.lwjgl.opengl.GL11;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
 | 
			
		||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
 | 
			
		||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
 | 
			
		||||
import static dan200.computercraft.client.gui.GuiComputer.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Emulates map rendering for pocket computers
 | 
			
		||||
@@ -38,6 +37,10 @@ import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
 | 
			
		||||
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
 | 
			
		||||
public final class ItemPocketRenderer extends ItemMapLikeRenderer
 | 
			
		||||
{
 | 
			
		||||
    private static final int MARGIN = 2;
 | 
			
		||||
    private static final int FRAME = 12;
 | 
			
		||||
    private static final int LIGHT_HEIGHT = 8;
 | 
			
		||||
 | 
			
		||||
    private static final ItemPocketRenderer INSTANCE = new ItemPocketRenderer();
 | 
			
		||||
 | 
			
		||||
    private ItemPocketRenderer()
 | 
			
		||||
@@ -57,115 +60,194 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderItem( ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        ClientComputer computer = ItemPocketComputer.createClientComputer( stack );
 | 
			
		||||
        Terminal terminal = computer == null ? null : computer.getTerminal();
 | 
			
		||||
 | 
			
		||||
        int termWidth, termHeight;
 | 
			
		||||
        if( terminal == null )
 | 
			
		||||
        {
 | 
			
		||||
            termWidth = ComputerCraft.terminalWidth_pocketComputer;
 | 
			
		||||
            termHeight = ComputerCraft.terminalHeight_pocketComputer;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            termWidth = terminal.getWidth();
 | 
			
		||||
            termHeight = terminal.getHeight();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int width = termWidth * FONT_WIDTH + MARGIN * 2;
 | 
			
		||||
        int height = termHeight * FONT_HEIGHT + MARGIN * 2;
 | 
			
		||||
 | 
			
		||||
        // Setup various transformations. Note that these are partially adapted from the corresponding method
 | 
			
		||||
        // in ItemRenderer
 | 
			
		||||
        GlStateManager.pushMatrix();
 | 
			
		||||
 | 
			
		||||
        GlStateManager.disableLighting();
 | 
			
		||||
        GlStateManager.disableDepth();
 | 
			
		||||
 | 
			
		||||
        GlStateManager.rotate( 180f, 0f, 1f, 0f );
 | 
			
		||||
        GlStateManager.rotate( 180f, 0f, 0f, 1f );
 | 
			
		||||
        GlStateManager.scale( 0.5, 0.5, 0.5 );
 | 
			
		||||
 | 
			
		||||
        ClientComputer computer = ItemPocketComputer.createClientComputer( stack );
 | 
			
		||||
        double scale = 0.75 / Math.max( width + FRAME * 2, height + FRAME * 2 + LIGHT_HEIGHT );
 | 
			
		||||
        GlStateManager.scale( scale, scale, 0 );
 | 
			
		||||
        GlStateManager.translate( -0.5 * width, -0.5 * height, 0 );
 | 
			
		||||
 | 
			
		||||
        // Render the main frame
 | 
			
		||||
        ComputerFamily family = ComputerCraft.Items.pocketComputer.getFamily( stack );
 | 
			
		||||
        int frameColour = ComputerCraft.Items.pocketComputer.getColour( stack );
 | 
			
		||||
        renderFrame( family, frameColour, width, height );
 | 
			
		||||
 | 
			
		||||
        // Render the light
 | 
			
		||||
        int lightColour = ItemPocketComputer.getLightState( stack );
 | 
			
		||||
        if( lightColour == -1 ) lightColour = Colour.Black.getHex();
 | 
			
		||||
        renderLight( lightColour, width, height );
 | 
			
		||||
 | 
			
		||||
        if( computer != null && terminal != null )
 | 
			
		||||
        {
 | 
			
		||||
            // First render the background item. We use the item's model rather than a direct texture as this ensures
 | 
			
		||||
            // we display the pocket light and other such decorations.
 | 
			
		||||
            GlStateManager.pushMatrix();
 | 
			
		||||
            // If we've a computer and terminal then attempt to render it.
 | 
			
		||||
            renderTerminal( terminal, !computer.isColour(), width, height );
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            // Otherwise render a plain background
 | 
			
		||||
            Minecraft.getMinecraft().getTextureManager().bindTexture( BACKGROUND );
 | 
			
		||||
 | 
			
		||||
            GlStateManager.scale( 1.0f, -1.0f, 1.0f );
 | 
			
		||||
            Tessellator tessellator = Tessellator.getInstance();
 | 
			
		||||
            BufferBuilder buffer = tessellator.getBuffer();
 | 
			
		||||
 | 
			
		||||
            Minecraft minecraft = Minecraft.getMinecraft();
 | 
			
		||||
            TextureManager textureManager = minecraft.getTextureManager();
 | 
			
		||||
            RenderItem renderItem = minecraft.getRenderItem();
 | 
			
		||||
 | 
			
		||||
            // Copy of RenderItem#renderItemModelIntoGUI but without the translation or scaling
 | 
			
		||||
            textureManager.bindTexture( TextureMap.LOCATION_BLOCKS_TEXTURE );
 | 
			
		||||
            textureManager.getTexture( TextureMap.LOCATION_BLOCKS_TEXTURE ).setBlurMipmap( false, false );
 | 
			
		||||
 | 
			
		||||
            GlStateManager.enableRescaleNormal();
 | 
			
		||||
            GlStateManager.enableAlpha();
 | 
			
		||||
            GlStateManager.alphaFunc( GL11.GL_GREATER, 0.1F );
 | 
			
		||||
            GlStateManager.enableBlend();
 | 
			
		||||
            GlStateManager.blendFunc( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA );
 | 
			
		||||
            GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
 | 
			
		||||
 | 
			
		||||
            IBakedModel baked = renderItem.getItemModelWithOverrides( stack, null, null );
 | 
			
		||||
            baked = ForgeHooksClient.handleCameraTransforms( baked, ItemCameraTransforms.TransformType.GUI, false );
 | 
			
		||||
            renderItem.renderItem( stack, baked );
 | 
			
		||||
 | 
			
		||||
            GlStateManager.disableAlpha();
 | 
			
		||||
            GlStateManager.disableRescaleNormal();
 | 
			
		||||
 | 
			
		||||
            GlStateManager.popMatrix();
 | 
			
		||||
            Colour black = Colour.Black;
 | 
			
		||||
            buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION );
 | 
			
		||||
            renderTexture( buffer, 0, 0, 0, 0, width, height, black.getR(), black.getG(), black.getB() );
 | 
			
		||||
            tessellator.draw();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we've a computer and terminal then attempt to render it.
 | 
			
		||||
        if( computer != null )
 | 
			
		||||
        GlStateManager.enableDepth();
 | 
			
		||||
        GlStateManager.enableLighting();
 | 
			
		||||
        GlStateManager.popMatrix();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderFrame( ComputerFamily family, int colour, int width, int height )
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        Minecraft.getMinecraft().getTextureManager().bindTexture( colour != -1
 | 
			
		||||
            ? BACKGROUND_COLOUR
 | 
			
		||||
            : family == ComputerFamily.Normal ? BACKGROUND_NORMAL : BACKGROUND_ADVANCED
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        float r = ((colour >>> 16) & 0xFF) / 255.0f;
 | 
			
		||||
        float g = ((colour >>> 8) & 0xFF) / 255.0f;
 | 
			
		||||
        float b = (colour & 0xFF) / 255.0f;
 | 
			
		||||
 | 
			
		||||
        Tessellator tessellator = Tessellator.getInstance();
 | 
			
		||||
        BufferBuilder buffer = tessellator.getBuffer();
 | 
			
		||||
        buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR );
 | 
			
		||||
 | 
			
		||||
        // Top left, middle, right
 | 
			
		||||
        renderTexture( buffer, -FRAME, -FRAME, 12, 28, FRAME, FRAME, r, g, b );
 | 
			
		||||
        renderTexture( buffer, 0, -FRAME, 0, 0, width, FRAME, r, g, b );
 | 
			
		||||
        renderTexture( buffer, width, -FRAME, 24, 28, FRAME, FRAME, r, g, b );
 | 
			
		||||
 | 
			
		||||
        // Left and bright border
 | 
			
		||||
        renderTexture( buffer, -FRAME, 0, 0, 28, FRAME, height, r, g, b );
 | 
			
		||||
        renderTexture( buffer, width, 0, 36, 28, FRAME, height, r, g, b );
 | 
			
		||||
 | 
			
		||||
        // Bottom left, middle, right. We do this in three portions: the top inner corners, an extended region for
 | 
			
		||||
        // lights, and then the bottom outer corners.
 | 
			
		||||
        renderTexture( buffer, -FRAME, height, 12, 40, FRAME, FRAME / 2, r, g, b );
 | 
			
		||||
        renderTexture( buffer, 0, height, 0, 12, width, FRAME / 2, r, g, b );
 | 
			
		||||
        renderTexture( buffer, width, height, 24, 40, FRAME, FRAME / 2, r, g, b );
 | 
			
		||||
 | 
			
		||||
        renderTexture( buffer, -FRAME, height + FRAME / 2, 12, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
 | 
			
		||||
        renderTexture( buffer, 0, height + FRAME / 2, 0, 16, width, LIGHT_HEIGHT, FRAME, 4, r, g, b );
 | 
			
		||||
        renderTexture( buffer, width, height + FRAME / 2, 24, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
 | 
			
		||||
 | 
			
		||||
        renderTexture( buffer, -FRAME, height + LIGHT_HEIGHT + FRAME / 2, 12, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
 | 
			
		||||
        renderTexture( buffer, 0, height + LIGHT_HEIGHT + FRAME / 2, 0, 12 + FRAME / 2, width, FRAME / 2, r, g, b );
 | 
			
		||||
        renderTexture( buffer, width, height + LIGHT_HEIGHT + FRAME / 2, 24, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
 | 
			
		||||
 | 
			
		||||
        tessellator.draw();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderLight( int colour, int width, int height )
 | 
			
		||||
    {
 | 
			
		||||
        GlStateManager.enableBlend();
 | 
			
		||||
        GlStateManager.disableTexture2D();
 | 
			
		||||
 | 
			
		||||
        float r = ((colour >>> 16) & 0xFF) / 255.0f;
 | 
			
		||||
        float g = ((colour >>> 8) & 0xFF) / 255.0f;
 | 
			
		||||
        float b = (colour & 0xFF) / 255.0f;
 | 
			
		||||
 | 
			
		||||
        Tessellator tessellator = Tessellator.getInstance();
 | 
			
		||||
        BufferBuilder buffer = tessellator.getBuffer();
 | 
			
		||||
        buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR );
 | 
			
		||||
        buffer.pos( width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
        buffer.pos( width, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
        buffer.pos( width, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
        buffer.pos( width - LIGHT_HEIGHT * 2, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
 | 
			
		||||
        tessellator.draw();
 | 
			
		||||
        GlStateManager.enableTexture2D();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderTerminal( Terminal terminal, boolean greyscale, int width, int height )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( terminal )
 | 
			
		||||
        {
 | 
			
		||||
            Terminal terminal = computer.getTerminal();
 | 
			
		||||
            if( terminal != null )
 | 
			
		||||
            int termWidth = terminal.getWidth();
 | 
			
		||||
            int termHeight = terminal.getHeight();
 | 
			
		||||
 | 
			
		||||
            FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
 | 
			
		||||
            Palette palette = terminal.getPalette();
 | 
			
		||||
 | 
			
		||||
            // Render top/bottom borders
 | 
			
		||||
            TextBuffer emptyLine = new TextBuffer( ' ', termWidth );
 | 
			
		||||
            fontRenderer.drawString(
 | 
			
		||||
                emptyLine, MARGIN, 0,
 | 
			
		||||
                terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), MARGIN, MARGIN, greyscale, palette
 | 
			
		||||
            );
 | 
			
		||||
            fontRenderer.drawString(
 | 
			
		||||
                emptyLine, MARGIN, 2 * MARGIN + (termHeight - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
 | 
			
		||||
                terminal.getTextColourLine( termHeight - 1 ), terminal.getBackgroundColourLine( termHeight - 1 ), MARGIN, MARGIN, greyscale, palette
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Render the actual text
 | 
			
		||||
            for( int line = 0; line < termWidth; line++ )
 | 
			
		||||
            {
 | 
			
		||||
                synchronized( terminal )
 | 
			
		||||
                {
 | 
			
		||||
                    GlStateManager.pushMatrix();
 | 
			
		||||
                    GlStateManager.disableDepth();
 | 
			
		||||
                TextBuffer text = terminal.getLine( line );
 | 
			
		||||
                TextBuffer colour = terminal.getTextColourLine( line );
 | 
			
		||||
                TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
 | 
			
		||||
                fontRenderer.drawString(
 | 
			
		||||
                    text, MARGIN, MARGIN + line * FONT_HEIGHT,
 | 
			
		||||
                    colour, backgroundColour, MARGIN, MARGIN, greyscale, palette
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                    // Reset the position to be at the top left corner of the pocket computer
 | 
			
		||||
                    // Note we translate towards the screen slightly too.
 | 
			
		||||
                    GlStateManager.translate( -8 / 16.0, -8 / 16.0, 0.5 / 16.0 );
 | 
			
		||||
                    // Translate to the top left of the screen.
 | 
			
		||||
                    GlStateManager.translate( 4 / 16.0, 3 / 16.0, 0 );
 | 
			
		||||
 | 
			
		||||
                    // Work out the scaling required to resize the terminal in order to fit on the computer
 | 
			
		||||
                    final int margin = 2;
 | 
			
		||||
                    int tw = terminal.getWidth();
 | 
			
		||||
                    int th = terminal.getHeight();
 | 
			
		||||
                    int width = tw * FONT_WIDTH + margin * 2;
 | 
			
		||||
                    int height = th * FONT_HEIGHT + margin * 2;
 | 
			
		||||
                    int max = Math.max( height, width );
 | 
			
		||||
 | 
			
		||||
                    // The grid is 8 * 8 wide, so we start with a base of 1/2 (8 / 16).
 | 
			
		||||
                    double scale = 1.0 / 2.0 / max;
 | 
			
		||||
                    GlStateManager.scale( scale, scale, scale );
 | 
			
		||||
 | 
			
		||||
                    // The margin/start positions are determined in order for the terminal to be centred.
 | 
			
		||||
                    int startX = (max - width) / 2 + margin;
 | 
			
		||||
                    int startY = (max - height) / 2 + margin;
 | 
			
		||||
 | 
			
		||||
                    FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
 | 
			
		||||
                    boolean greyscale = !computer.isColour();
 | 
			
		||||
                    Palette palette = terminal.getPalette();
 | 
			
		||||
 | 
			
		||||
                    // Render the actual text
 | 
			
		||||
                    for( int line = 0; line < th; line++ )
 | 
			
		||||
                    {
 | 
			
		||||
                        TextBuffer text = terminal.getLine( line );
 | 
			
		||||
                        TextBuffer colour = terminal.getTextColourLine( line );
 | 
			
		||||
                        TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
 | 
			
		||||
                        fontRenderer.drawString(
 | 
			
		||||
                            text, startX, startY + line * FONT_HEIGHT,
 | 
			
		||||
                            colour, backgroundColour, margin, margin, greyscale, palette
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // And render the cursor;
 | 
			
		||||
                    int tx = terminal.getCursorX(), ty = terminal.getCursorY();
 | 
			
		||||
                    if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
 | 
			
		||||
                        tx >= 0 && ty >= 0 && tx < tw && ty < th )
 | 
			
		||||
                    {
 | 
			
		||||
                        TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
 | 
			
		||||
                        fontRenderer.drawString(
 | 
			
		||||
                            new TextBuffer( '_', 1 ), startX + FONT_WIDTH * tx, startY + FONT_HEIGHT * ty,
 | 
			
		||||
                            cursorColour, null, 0, 0, greyscale, palette
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GlStateManager.enableDepth();
 | 
			
		||||
                    GlStateManager.popMatrix();
 | 
			
		||||
                }
 | 
			
		||||
            // And render the cursor;
 | 
			
		||||
            int tx = terminal.getCursorX(), ty = terminal.getCursorY();
 | 
			
		||||
            if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
 | 
			
		||||
                tx >= 0 && ty >= 0 && tx < termWidth && ty < termHeight )
 | 
			
		||||
            {
 | 
			
		||||
                TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
 | 
			
		||||
                fontRenderer.drawString(
 | 
			
		||||
                    new TextBuffer( '_', 1 ), MARGIN + FONT_WIDTH * tx, MARGIN + FONT_HEIGHT * ty,
 | 
			
		||||
                    cursorColour, null, 0, 0, greyscale, palette
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        GlStateManager.enableLighting();
 | 
			
		||||
    private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
 | 
			
		||||
    {
 | 
			
		||||
        renderTexture( builder, x, y, textureX, textureY, width, height, width, height, r, g, b );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, int textureWidth, int textureHeight, float r, float g, float b )
 | 
			
		||||
    {
 | 
			
		||||
        float scale = 1 / 255.0f;
 | 
			
		||||
        builder.pos( x, y + height, 0 ).tex( textureX * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
        builder.pos( x + width, y + height, 0 ).tex( (textureX + textureWidth) * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
        builder.pos( x + width, y, 0 ).tex( (textureX + textureWidth) * scale, textureY * scale ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
        builder.pos( x, y, 0 ).tex( textureX * scale, textureY * scale ).color( r, g, b, 1.0f ).endVertex();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ public final class ModelTransformer
 | 
			
		||||
        private final Point3f[] before = new Point3f[4];
 | 
			
		||||
        private final Point3f[] after = new Point3f[4];
 | 
			
		||||
 | 
			
		||||
        public NormalAwareTransformer( IVertexConsumer parent, Matrix4f positionMatrix, Matrix4f normalMatrix )
 | 
			
		||||
        NormalAwareTransformer( IVertexConsumer parent, Matrix4f positionMatrix, Matrix4f normalMatrix )
 | 
			
		||||
        {
 | 
			
		||||
            super( parent );
 | 
			
		||||
            this.positionMatrix = positionMatrix;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,118 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.ComputerCraft;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
 | 
			
		||||
import net.minecraft.client.renderer.BufferBuilder;
 | 
			
		||||
import net.minecraft.client.renderer.GlStateManager;
 | 
			
		||||
import net.minecraft.client.renderer.Tessellator;
 | 
			
		||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
 | 
			
		||||
import net.minecraft.entity.player.EntityPlayer;
 | 
			
		||||
import net.minecraft.tileentity.TileEntity;
 | 
			
		||||
import net.minecraft.util.EnumFacing;
 | 
			
		||||
import net.minecraft.util.math.BlockPos;
 | 
			
		||||
import net.minecraft.util.math.RayTraceResult;
 | 
			
		||||
import net.minecraft.world.World;
 | 
			
		||||
import net.minecraftforge.client.event.DrawBlockHighlightEvent;
 | 
			
		||||
import net.minecraftforge.fml.common.Mod;
 | 
			
		||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
 | 
			
		||||
import net.minecraftforge.fml.relauncher.Side;
 | 
			
		||||
import org.lwjgl.opengl.GL11;
 | 
			
		||||
 | 
			
		||||
import java.util.EnumSet;
 | 
			
		||||
 | 
			
		||||
import static net.minecraft.util.EnumFacing.*;
 | 
			
		||||
 | 
			
		||||
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Side.CLIENT )
 | 
			
		||||
public final class MonitorHighlightRenderer
 | 
			
		||||
{
 | 
			
		||||
    private static final float EXPAND = 0.002f;
 | 
			
		||||
 | 
			
		||||
    private MonitorHighlightRenderer()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SubscribeEvent
 | 
			
		||||
    public static void drawHighlight( DrawBlockHighlightEvent event )
 | 
			
		||||
    {
 | 
			
		||||
        if( event.getTarget().typeOfHit != RayTraceResult.Type.BLOCK || event.getPlayer().isSneaking() ) return;
 | 
			
		||||
 | 
			
		||||
        World world = event.getPlayer().getEntityWorld();
 | 
			
		||||
        BlockPos pos = event.getTarget().getBlockPos();
 | 
			
		||||
 | 
			
		||||
        if( world.getBlockState( pos ).getBlock() != ComputerCraft.Blocks.peripheral ) return;
 | 
			
		||||
 | 
			
		||||
        TileEntity tile = world.getTileEntity( pos );
 | 
			
		||||
        if( !(tile instanceof TileMonitor) ) return;
 | 
			
		||||
 | 
			
		||||
        TileMonitor monitor = (TileMonitor) tile;
 | 
			
		||||
        event.setCanceled( true );
 | 
			
		||||
 | 
			
		||||
        // Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
 | 
			
		||||
        EnumSet<EnumFacing> faces = EnumSet.allOf( EnumFacing.class );
 | 
			
		||||
        EnumFacing front = monitor.getFront();
 | 
			
		||||
        faces.remove( front );
 | 
			
		||||
        if( monitor.getXIndex() != 0 ) faces.remove( monitor.getRight().getOpposite() );
 | 
			
		||||
        if( monitor.getXIndex() != monitor.getWidth() - 1 ) faces.remove( monitor.getRight() );
 | 
			
		||||
        if( monitor.getYIndex() != 0 ) faces.remove( monitor.getDown().getOpposite() );
 | 
			
		||||
        if( monitor.getYIndex() != monitor.getHeight() - 1 ) faces.remove( monitor.getDown() );
 | 
			
		||||
 | 
			
		||||
        GlStateManager.enableBlend();
 | 
			
		||||
        GlStateManager.tryBlendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
 | 
			
		||||
        GL11.glLineWidth( 2.0F );
 | 
			
		||||
        GlStateManager.disableTexture2D();
 | 
			
		||||
        GlStateManager.depthMask( false );
 | 
			
		||||
        GlStateManager.pushMatrix();
 | 
			
		||||
 | 
			
		||||
        EntityPlayer player = event.getPlayer();
 | 
			
		||||
        double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks();
 | 
			
		||||
        double y = player.lastTickPosY + (player.posY - player.lastTickPosY) * event.getPartialTicks();
 | 
			
		||||
        double z = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * event.getPartialTicks();
 | 
			
		||||
 | 
			
		||||
        GlStateManager.translate( -x + pos.getX(), -y + pos.getY(), -z + pos.getZ() );
 | 
			
		||||
 | 
			
		||||
        Tessellator tessellator = Tessellator.getInstance();
 | 
			
		||||
        BufferBuilder buffer = tessellator.getBuffer();
 | 
			
		||||
        buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION_COLOR );
 | 
			
		||||
 | 
			
		||||
        // I wish I could think of a better way to do this
 | 
			
		||||
        if( faces.contains( NORTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 0, UP );
 | 
			
		||||
        if( faces.contains( SOUTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 1, UP );
 | 
			
		||||
        if( faces.contains( NORTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 0, UP );
 | 
			
		||||
        if( faces.contains( SOUTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 1, UP );
 | 
			
		||||
        if( faces.contains( NORTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, EAST );
 | 
			
		||||
        if( faces.contains( SOUTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 1, EAST );
 | 
			
		||||
        if( faces.contains( NORTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, EAST );
 | 
			
		||||
        if( faces.contains( SOUTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 1, EAST );
 | 
			
		||||
        if( faces.contains( WEST ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, SOUTH );
 | 
			
		||||
        if( faces.contains( EAST ) || faces.contains( DOWN ) ) line( buffer, 1, 0, 0, SOUTH );
 | 
			
		||||
        if( faces.contains( WEST ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, SOUTH );
 | 
			
		||||
        if( faces.contains( EAST ) || faces.contains( UP ) ) line( buffer, 1, 1, 0, SOUTH );
 | 
			
		||||
 | 
			
		||||
        tessellator.draw();
 | 
			
		||||
 | 
			
		||||
        GlStateManager.popMatrix();
 | 
			
		||||
        GlStateManager.depthMask( true );
 | 
			
		||||
        GlStateManager.enableTexture2D();
 | 
			
		||||
        GlStateManager.disableBlend();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void line( BufferBuilder buffer, int x, int y, int z, EnumFacing direction )
 | 
			
		||||
    {
 | 
			
		||||
        double minX = x == 0 ? -EXPAND : 1 + EXPAND;
 | 
			
		||||
        double minY = y == 0 ? -EXPAND : 1 + EXPAND;
 | 
			
		||||
        double minZ = z == 0 ? -EXPAND : 1 + EXPAND;
 | 
			
		||||
 | 
			
		||||
        buffer.pos( minX, minY, minZ ).color( 0, 0, 0, 0.4f ).endVertex();
 | 
			
		||||
        buffer.pos(
 | 
			
		||||
            minX + direction.getXOffset() * (1 + EXPAND * 2),
 | 
			
		||||
            minY + direction.getYOffset() * (1 + EXPAND * 2),
 | 
			
		||||
            minZ + direction.getZOffset() * (1 + EXPAND * 2)
 | 
			
		||||
        ).color( 0, 0, 0, 0.4f ).endVertex();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -43,9 +43,7 @@ public class FSAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "fs"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "fs" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,7 @@ public class HTTPAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "http"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "http" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -83,6 +81,7 @@ public class HTTPAPI implements ILuaAPI
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @SuppressWarnings( "resource" )
 | 
			
		||||
    public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        switch( method )
 | 
			
		||||
@@ -95,7 +94,7 @@ public class HTTPAPI implements ILuaAPI
 | 
			
		||||
 | 
			
		||||
                if( args.length >= 1 && args[0] instanceof Map )
 | 
			
		||||
                {
 | 
			
		||||
                    Map<?, ?> options = (Map) args[0];
 | 
			
		||||
                    Map<?, ?> options = (Map<?, ?>) args[0];
 | 
			
		||||
                    address = getStringField( options, "url" );
 | 
			
		||||
                    postString = optStringField( options, "body", null );
 | 
			
		||||
                    headerTable = optTableField( options, "headers", Collections.emptyMap() );
 | 
			
		||||
@@ -135,7 +134,6 @@ public class HTTPAPI implements ILuaAPI
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    URI uri = HttpRequest.checkUri( address );
 | 
			
		||||
 | 
			
		||||
                    HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
 | 
			
		||||
 | 
			
		||||
                    long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers );
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ package dan200.computercraft.core.apis;
 | 
			
		||||
 * This exists purely to ensure binary compatibility.
 | 
			
		||||
 *
 | 
			
		||||
 * @see dan200.computercraft.api.lua.ILuaAPI
 | 
			
		||||
 * @deprecated Use the version in the public API. Only exists for compatibility with CCEmuX.
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
public interface ILuaAPI extends dan200.computercraft.api.lua.ILuaAPI
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										281
									
								
								src/main/java/dan200/computercraft/core/apis/LuaDateTime.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								src/main/java/dan200/computercraft/core/apis/LuaDateTime.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.core.apis;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.ZoneId;
 | 
			
		||||
import java.time.ZoneOffset;
 | 
			
		||||
import java.time.format.DateTimeFormatterBuilder;
 | 
			
		||||
import java.time.format.TextStyle;
 | 
			
		||||
import java.time.temporal.*;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.function.LongUnaryOperator;
 | 
			
		||||
 | 
			
		||||
final class LuaDateTime
 | 
			
		||||
{
 | 
			
		||||
    private LuaDateTime()
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffset offset ) throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        for( int i = 0; i < format.length(); )
 | 
			
		||||
        {
 | 
			
		||||
            char c;
 | 
			
		||||
            switch( c = format.charAt( i++ ) )
 | 
			
		||||
            {
 | 
			
		||||
                case '\n':
 | 
			
		||||
                    formatter.appendLiteral( '\n' );
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    formatter.appendLiteral( c );
 | 
			
		||||
                    break;
 | 
			
		||||
                case '%':
 | 
			
		||||
                    if( i >= format.length() ) break;
 | 
			
		||||
                    switch( c = format.charAt( i++ ) )
 | 
			
		||||
                    {
 | 
			
		||||
                        default:
 | 
			
		||||
                            throw new LuaException( "bad argument #1: invalid conversion specifier '%" + c + "'" );
 | 
			
		||||
 | 
			
		||||
                        case '%':
 | 
			
		||||
                            formatter.appendLiteral( '%' );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'a':
 | 
			
		||||
                            formatter.appendText( ChronoField.DAY_OF_WEEK, TextStyle.SHORT );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'A':
 | 
			
		||||
                            formatter.appendText( ChronoField.DAY_OF_WEEK, TextStyle.FULL );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'b':
 | 
			
		||||
                        case 'h':
 | 
			
		||||
                            formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.SHORT );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'B':
 | 
			
		||||
                            formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.FULL );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'c':
 | 
			
		||||
                            format( formatter, "%a %b %e %H:%M:%S %Y", offset );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'C':
 | 
			
		||||
                            formatter.appendValueReduced( CENTURY, 2, 2, 0 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'd':
 | 
			
		||||
                            formatter.appendValue( ChronoField.DAY_OF_MONTH, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'D':
 | 
			
		||||
                        case 'x':
 | 
			
		||||
                            format( formatter, "%m/%d/%y", offset );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'e':
 | 
			
		||||
                            formatter.padNext( 2 ).appendValue( ChronoField.DAY_OF_MONTH );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'F':
 | 
			
		||||
                            format( formatter, "%Y-%m-%d", offset );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'g':
 | 
			
		||||
                            formatter.appendValueReduced( IsoFields.WEEK_BASED_YEAR, 2, 2, 0 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'G':
 | 
			
		||||
                            formatter.appendValue( IsoFields.WEEK_BASED_YEAR );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'H':
 | 
			
		||||
                            formatter.appendValue( ChronoField.HOUR_OF_DAY, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'I':
 | 
			
		||||
                            formatter.appendValue( ChronoField.HOUR_OF_AMPM );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'j':
 | 
			
		||||
                            formatter.appendValue( ChronoField.DAY_OF_YEAR, 3 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'm':
 | 
			
		||||
                            formatter.appendValue( ChronoField.MONTH_OF_YEAR, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'M':
 | 
			
		||||
                            formatter.appendValue( ChronoField.MINUTE_OF_HOUR, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'n':
 | 
			
		||||
                            formatter.appendLiteral( '\n' );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'p':
 | 
			
		||||
                            formatter.appendText( ChronoField.AMPM_OF_DAY );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'r':
 | 
			
		||||
                            format( formatter, "%I:%M:%S %p", offset );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'R':
 | 
			
		||||
                            format( formatter, "%H:%M", offset );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'S':
 | 
			
		||||
                            formatter.appendValue( ChronoField.SECOND_OF_MINUTE, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 't':
 | 
			
		||||
                            formatter.appendLiteral( '\t' );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'T':
 | 
			
		||||
                        case 'X':
 | 
			
		||||
                            format( formatter, "%H:%M:%S", offset );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'u':
 | 
			
		||||
                            formatter.appendValue( ChronoField.DAY_OF_WEEK );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'U':
 | 
			
		||||
                            formatter.appendValue( ChronoField.ALIGNED_WEEK_OF_YEAR, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'V':
 | 
			
		||||
                            formatter.appendValue( IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'w':
 | 
			
		||||
                            formatter.appendValue( ZERO_WEEK );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'W':
 | 
			
		||||
                            formatter.appendValue( WeekFields.ISO.weekOfYear(), 2 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'y':
 | 
			
		||||
                            formatter.appendValueReduced( ChronoField.YEAR, 2, 2, 0 );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'Y':
 | 
			
		||||
                            formatter.appendValue( ChronoField.YEAR );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'z':
 | 
			
		||||
                            formatter.appendOffset( "+HHMM", "+0000" );
 | 
			
		||||
                            break;
 | 
			
		||||
                        case 'Z':
 | 
			
		||||
                            formatter.appendChronologyId();
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static long fromTable( Map<?, ?> table ) throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        int year = getField( table, "year", -1 );
 | 
			
		||||
        int month = getField( table, "month", -1 );
 | 
			
		||||
        int day = getField( table, "day", -1 );
 | 
			
		||||
        int hour = getField( table, "hour", 12 );
 | 
			
		||||
        int minute = getField( table, "min", 12 );
 | 
			
		||||
        int second = getField( table, "sec", 12 );
 | 
			
		||||
        LocalDateTime time = LocalDateTime.of( year, month, day, hour, minute, second );
 | 
			
		||||
 | 
			
		||||
        Boolean isDst = getBoolField( table, "isdst" );
 | 
			
		||||
        if( isDst != null )
 | 
			
		||||
        {
 | 
			
		||||
            boolean requireDst = isDst;
 | 
			
		||||
            for( ZoneOffset possibleOffset : ZoneOffset.systemDefault().getRules().getValidOffsets( time ) )
 | 
			
		||||
            {
 | 
			
		||||
                Instant instant = time.toInstant( possibleOffset );
 | 
			
		||||
                if( possibleOffset.getRules().getDaylightSavings( instant ).isZero() == requireDst )
 | 
			
		||||
                {
 | 
			
		||||
                    return instant.getEpochSecond();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ZoneOffset offset = ZoneOffset.systemDefault().getRules().getOffset( time );
 | 
			
		||||
        return time.toInstant( offset ).getEpochSecond();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static Map<String, ?> toTable( TemporalAccessor date, ZoneId offset, Instant instant )
 | 
			
		||||
    {
 | 
			
		||||
        HashMap<String, Object> table = new HashMap<>( 9 );
 | 
			
		||||
        table.put( "year", date.getLong( ChronoField.YEAR ) );
 | 
			
		||||
        table.put( "month", date.getLong( ChronoField.MONTH_OF_YEAR ) );
 | 
			
		||||
        table.put( "day", date.getLong( ChronoField.DAY_OF_MONTH ) );
 | 
			
		||||
        table.put( "hour", date.getLong( ChronoField.HOUR_OF_DAY ) );
 | 
			
		||||
        table.put( "min", date.getLong( ChronoField.MINUTE_OF_HOUR ) );
 | 
			
		||||
        table.put( "sec", date.getLong( ChronoField.SECOND_OF_MINUTE ) );
 | 
			
		||||
        table.put( "wday", date.getLong( WeekFields.SUNDAY_START.dayOfWeek() ) );
 | 
			
		||||
        table.put( "yday", date.getLong( ChronoField.DAY_OF_YEAR ) );
 | 
			
		||||
        table.put( "isdst", offset.getRules().isDaylightSavings( instant ) );
 | 
			
		||||
        return table;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getField( Map<?, ?> table, String field, int def ) throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        Object value = table.get( field );
 | 
			
		||||
        if( value instanceof Number ) return ((Number) value).intValue();
 | 
			
		||||
        if( def < 0 ) throw new LuaException( "field \"" + field + "\" missing in date table" );
 | 
			
		||||
        return def;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Boolean getBoolField( Map<?, ?> table, String field ) throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        Object value = table.get( field );
 | 
			
		||||
        if( value instanceof Boolean || value == null ) return (Boolean) value;
 | 
			
		||||
        throw new LuaException( "field \"" + field + "\" missing in date table" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 6 ), x -> (x / 100) % 100 );
 | 
			
		||||
    private static final TemporalField ZERO_WEEK = map( WeekFields.SUNDAY_START.dayOfWeek(), ValueRange.of( 0, 6 ), x -> x - 1 );
 | 
			
		||||
 | 
			
		||||
    private static TemporalField map( TemporalField field, ValueRange range, LongUnaryOperator convert )
 | 
			
		||||
    {
 | 
			
		||||
        return new TemporalField()
 | 
			
		||||
        {
 | 
			
		||||
            private final ValueRange range = ValueRange.of( 0, 99 );
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public TemporalUnit getBaseUnit()
 | 
			
		||||
            {
 | 
			
		||||
                return field.getBaseUnit();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public TemporalUnit getRangeUnit()
 | 
			
		||||
            {
 | 
			
		||||
                return field.getRangeUnit();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public ValueRange range()
 | 
			
		||||
            {
 | 
			
		||||
                return range;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean isDateBased()
 | 
			
		||||
            {
 | 
			
		||||
                return field.isDateBased();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean isTimeBased()
 | 
			
		||||
            {
 | 
			
		||||
                return field.isTimeBased();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean isSupportedBy( TemporalAccessor temporal )
 | 
			
		||||
            {
 | 
			
		||||
                return field.isSupportedBy( temporal );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public ValueRange rangeRefinedBy( TemporalAccessor temporal )
 | 
			
		||||
            {
 | 
			
		||||
                return range;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public long getFrom( TemporalAccessor temporal )
 | 
			
		||||
            {
 | 
			
		||||
                return convert.applyAsLong( temporal.getLong( field ) );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            @SuppressWarnings( "unchecked" )
 | 
			
		||||
            public <R extends Temporal> R adjustInto( R temporal, long newValue )
 | 
			
		||||
            {
 | 
			
		||||
                return (R) temporal.with( field, newValue );
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,11 @@ import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.shared.util.StringUtil;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
import java.time.ZoneId;
 | 
			
		||||
import java.time.ZoneOffset;
 | 
			
		||||
import java.time.ZonedDateTime;
 | 
			
		||||
import java.time.format.DateTimeFormatterBuilder;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
 | 
			
		||||
@@ -31,9 +36,9 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
 | 
			
		||||
    private static class Timer
 | 
			
		||||
    {
 | 
			
		||||
        public int m_ticksLeft;
 | 
			
		||||
        int m_ticksLeft;
 | 
			
		||||
 | 
			
		||||
        public Timer( int ticksLeft )
 | 
			
		||||
        Timer( int ticksLeft )
 | 
			
		||||
        {
 | 
			
		||||
            m_ticksLeft = ticksLeft;
 | 
			
		||||
        }
 | 
			
		||||
@@ -41,10 +46,10 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
 | 
			
		||||
    private static class Alarm implements Comparable<Alarm>
 | 
			
		||||
    {
 | 
			
		||||
        public final double m_time;
 | 
			
		||||
        public final int m_day;
 | 
			
		||||
        final double m_time;
 | 
			
		||||
        final int m_day;
 | 
			
		||||
 | 
			
		||||
        public Alarm( double time, int day )
 | 
			
		||||
        Alarm( double time, int day )
 | 
			
		||||
        {
 | 
			
		||||
            m_time = time;
 | 
			
		||||
            m_day = day;
 | 
			
		||||
@@ -73,9 +78,7 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "os"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "os" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -184,11 +187,12 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
            "day",
 | 
			
		||||
            "cancelTimer",
 | 
			
		||||
            "cancelAlarm",
 | 
			
		||||
            "epoch"
 | 
			
		||||
            "epoch",
 | 
			
		||||
            "date",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private float getTimeForCalendar( Calendar c )
 | 
			
		||||
    private static float getTimeForCalendar( Calendar c )
 | 
			
		||||
    {
 | 
			
		||||
        float time = c.get( Calendar.HOUR_OF_DAY );
 | 
			
		||||
        time += c.get( Calendar.MINUTE ) / 60.0f;
 | 
			
		||||
@@ -196,7 +200,7 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
        return time;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int getDayForCalendar( Calendar c )
 | 
			
		||||
    private static int getDayForCalendar( Calendar c )
 | 
			
		||||
    {
 | 
			
		||||
        GregorianCalendar g = c instanceof GregorianCalendar ? (GregorianCalendar) c : new GregorianCalendar();
 | 
			
		||||
        int year = c.get( Calendar.YEAR );
 | 
			
		||||
@@ -209,7 +213,7 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
        return day;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long getEpochForCalendar( Calendar c )
 | 
			
		||||
    private static long getEpochForCalendar( Calendar c )
 | 
			
		||||
    {
 | 
			
		||||
        return c.getTime().getTime();
 | 
			
		||||
    }
 | 
			
		||||
@@ -282,6 +286,9 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
            case 11:
 | 
			
		||||
            {
 | 
			
		||||
                // time
 | 
			
		||||
                Object value = args.length > 0 ? args[0] : null;
 | 
			
		||||
                if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map<?, ?>) value ) };
 | 
			
		||||
 | 
			
		||||
                String param = optString( args, 0, "ingame" );
 | 
			
		||||
                switch( param.toLowerCase( Locale.ROOT ) )
 | 
			
		||||
                {
 | 
			
		||||
@@ -355,9 +362,8 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            case 15:
 | 
			
		||||
            case 15: // epoch
 | 
			
		||||
            {
 | 
			
		||||
                // epoch
 | 
			
		||||
                String param = optString( args, 0, "ingame" );
 | 
			
		||||
                switch( param.toLowerCase( Locale.ROOT ) )
 | 
			
		||||
                {
 | 
			
		||||
@@ -377,14 +383,39 @@ public class OSAPI implements ILuaAPI
 | 
			
		||||
                        // Get in-game epoch
 | 
			
		||||
                        synchronized( m_alarms )
 | 
			
		||||
                        {
 | 
			
		||||
                            return new Object[] {
 | 
			
		||||
                                m_day * 86400000 + (int) (m_time * 3600000.0f)
 | 
			
		||||
                            };
 | 
			
		||||
                            return new Object[] { m_day * 86400000 + (int) (m_time * 3600000.0f) };
 | 
			
		||||
                        }
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new LuaException( "Unsupported operation" );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            case 16: // date
 | 
			
		||||
            {
 | 
			
		||||
                String format = optString( args, 0, "%c" );
 | 
			
		||||
                long time = optLong( args, 1, Instant.now().getEpochSecond() );
 | 
			
		||||
 | 
			
		||||
                Instant instant = Instant.ofEpochSecond( time );
 | 
			
		||||
                ZonedDateTime date;
 | 
			
		||||
                ZoneOffset offset;
 | 
			
		||||
                if( format.startsWith( "!" ) )
 | 
			
		||||
                {
 | 
			
		||||
                    offset = ZoneOffset.UTC;
 | 
			
		||||
                    date = ZonedDateTime.ofInstant( instant, offset );
 | 
			
		||||
                    format = format.substring( 1 );
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    ZoneId id = ZoneId.systemDefault();
 | 
			
		||||
                    offset = id.getRules().getOffset( instant );
 | 
			
		||||
                    date = ZonedDateTime.ofInstant( instant, id );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
 | 
			
		||||
 | 
			
		||||
                DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
 | 
			
		||||
                LuaDateTime.format( formatter, format, offset );
 | 
			
		||||
                return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                return null;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
 | 
			
		||||
        private Map<String, Integer> m_methodMap;
 | 
			
		||||
        private boolean m_attached;
 | 
			
		||||
 | 
			
		||||
        public PeripheralWrapper( IPeripheral peripheral, String side )
 | 
			
		||||
        PeripheralWrapper( IPeripheral peripheral, String side )
 | 
			
		||||
        {
 | 
			
		||||
            super( m_environment );
 | 
			
		||||
            m_side = side;
 | 
			
		||||
@@ -282,9 +282,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "peripheral"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "peripheral" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -326,7 +324,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
 | 
			
		||||
            "isPresent",
 | 
			
		||||
            "getType",
 | 
			
		||||
            "getMethods",
 | 
			
		||||
            "call"
 | 
			
		||||
            "call",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -356,7 +354,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
 | 
			
		||||
                ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
 | 
			
		||||
                if( side != null )
 | 
			
		||||
                {
 | 
			
		||||
                    String type = null;
 | 
			
		||||
                    synchronized( m_peripherals )
 | 
			
		||||
                    {
 | 
			
		||||
                        PeripheralWrapper p = m_peripherals[side.ordinal()];
 | 
			
		||||
 
 | 
			
		||||
@@ -29,9 +29,7 @@ public class RedstoneAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "rs", "redstone"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "rs", "redstone" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
 
 | 
			
		||||
@@ -33,9 +33,7 @@ public class TermAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "term"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "term" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -89,9 +87,7 @@ public class TermAPI implements ILuaAPI
 | 
			
		||||
 | 
			
		||||
    public static Object[] encodeColour( int colour ) throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        return new Object[] {
 | 
			
		||||
            1 << colour
 | 
			
		||||
        };
 | 
			
		||||
        return new Object[] { 1 << colour };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void setColour( Terminal terminal, int colour, double r, double g, double b )
 | 
			
		||||
 
 | 
			
		||||
@@ -212,6 +212,7 @@ public class BinaryReadableHandle extends HandleGeneric
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            case 3: // close
 | 
			
		||||
                checkOpen();
 | 
			
		||||
                close();
 | 
			
		||||
                return null;
 | 
			
		||||
            case 4: // seek
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,7 @@ public class BinaryWritableHandle extends HandleGeneric
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            case 2: // close
 | 
			
		||||
                checkOpen();
 | 
			
		||||
                close();
 | 
			
		||||
                return null;
 | 
			
		||||
            case 3: // seek
 | 
			
		||||
 
 | 
			
		||||
@@ -152,6 +152,7 @@ public class EncodedReadableHandle extends HandleGeneric
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            case 3: // close
 | 
			
		||||
                checkOpen();
 | 
			
		||||
                close();
 | 
			
		||||
                return null;
 | 
			
		||||
            default:
 | 
			
		||||
 
 | 
			
		||||
@@ -93,6 +93,7 @@ public class EncodedWritableHandle extends HandleGeneric
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            case 3: // close
 | 
			
		||||
                checkOpen();
 | 
			
		||||
                close();
 | 
			
		||||
                return null;
 | 
			
		||||
            default:
 | 
			
		||||
 
 | 
			
		||||
@@ -37,8 +37,13 @@ public abstract class HandleGeneric implements ILuaObject
 | 
			
		||||
    protected final void close()
 | 
			
		||||
    {
 | 
			
		||||
        m_open = false;
 | 
			
		||||
        IoUtil.closeQuietly( m_closable );
 | 
			
		||||
        m_closable = null;
 | 
			
		||||
 | 
			
		||||
        Closeable closeable = m_closable;
 | 
			
		||||
        if( closeable != null )
 | 
			
		||||
        {
 | 
			
		||||
            IoUtil.closeQuietly( closeable );
 | 
			
		||||
            m_closable = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,8 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
 | 
			
		||||
     */
 | 
			
		||||
    protected void dispose()
 | 
			
		||||
    {
 | 
			
		||||
        @SuppressWarnings( "unchecked" ) T thisT = (T) this;
 | 
			
		||||
        @SuppressWarnings( "unchecked" )
 | 
			
		||||
        T thisT = (T) this;
 | 
			
		||||
        limiter.release( thisT );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +96,8 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
 | 
			
		||||
 | 
			
		||||
    public boolean queue( Consumer<T> task )
 | 
			
		||||
    {
 | 
			
		||||
        @SuppressWarnings( "unchecked" ) T thisT = (T) this;
 | 
			
		||||
        @SuppressWarnings( "unchecked" )
 | 
			
		||||
        T thisT = (T) this;
 | 
			
		||||
        return limiter.queue( thisT, () -> task.accept( thisT ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ import java.io.Closeable;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
 | 
			
		||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
 | 
			
		||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
 | 
			
		||||
 | 
			
		||||
public class WebsocketHandle implements ILuaObject, Closeable
 | 
			
		||||
@@ -53,15 +54,18 @@ public class WebsocketHandle implements ILuaObject, Closeable
 | 
			
		||||
        switch( method )
 | 
			
		||||
        {
 | 
			
		||||
            case 0: // receive
 | 
			
		||||
                checkOpen();
 | 
			
		||||
                while( true )
 | 
			
		||||
                {
 | 
			
		||||
                    checkOpen();
 | 
			
		||||
 | 
			
		||||
                    Object[] event = context.pullEvent( MESSAGE_EVENT );
 | 
			
		||||
                    if( event.length >= 3 && Objects.equal( event[1], websocket.address() ) )
 | 
			
		||||
                    Object[] event = context.pullEvent( null );
 | 
			
		||||
                    if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) )
 | 
			
		||||
                    {
 | 
			
		||||
                        return Arrays.copyOfRange( event, 2, event.length );
 | 
			
		||||
                    }
 | 
			
		||||
                    else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
 | 
			
		||||
                    {
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            case 1: // send
 | 
			
		||||
 
 | 
			
		||||
@@ -239,7 +239,6 @@ public class Computer
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    @SuppressWarnings( "unused" )
 | 
			
		||||
    public void advance( double dt )
 | 
			
		||||
    {
 | 
			
		||||
        tick();
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,10 @@ public enum ComputerSide
 | 
			
		||||
 | 
			
		||||
    private final String name;
 | 
			
		||||
 | 
			
		||||
    ComputerSide( String name ) {this.name = name;}
 | 
			
		||||
    ComputerSide( String name )
 | 
			
		||||
    {
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public static ComputerSide valueOf( int side )
 | 
			
		||||
 
 | 
			
		||||
@@ -133,7 +133,6 @@ public final class ComputerThread
 | 
			
		||||
        synchronized( threadLock )
 | 
			
		||||
        {
 | 
			
		||||
            running = true;
 | 
			
		||||
            if( monitor == null || !monitor.isAlive() ) (monitor = monitorFactory.newThread( new Monitor() )).start();
 | 
			
		||||
 | 
			
		||||
            if( runners == null )
 | 
			
		||||
            {
 | 
			
		||||
@@ -158,6 +157,8 @@ public final class ComputerThread
 | 
			
		||||
                    runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if( monitor == null || !monitor.isAlive() ) (monitor = monitorFactory.newThread( new Monitor() )).start();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -368,7 +369,16 @@ public final class ComputerThread
 | 
			
		||||
                        {
 | 
			
		||||
                            TaskRunner runner = currentRunners[i];
 | 
			
		||||
                            // If we've no runner, skip.
 | 
			
		||||
                            if( runner == null ) continue;
 | 
			
		||||
                            if( runner == null || runner.owner == null || !runner.owner.isAlive() )
 | 
			
		||||
                            {
 | 
			
		||||
                                if( !running ) continue;
 | 
			
		||||
 | 
			
		||||
                                // Mark the old runner as dead and start a new one.
 | 
			
		||||
                                ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!",
 | 
			
		||||
                                    runner != null && runner.owner != null ? runner.owner.getName() : runner );
 | 
			
		||||
                                if( runner != null ) runner.running = false;
 | 
			
		||||
                                runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // If the runner has no work, skip
 | 
			
		||||
                            ComputerExecutor executor = runner.currentExecutor.get();
 | 
			
		||||
@@ -492,7 +502,7 @@ public final class ComputerThread
 | 
			
		||||
                {
 | 
			
		||||
                    executor.work();
 | 
			
		||||
                }
 | 
			
		||||
                catch( Exception e )
 | 
			
		||||
                catch( Exception | LinkageError | VirtualMachineError e )
 | 
			
		||||
                {
 | 
			
		||||
                    ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), e );
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -134,7 +134,7 @@ public final class MainThread
 | 
			
		||||
        // Of course, we'll go over the MAX_TICK_TIME most of the time, but eventually that overrun will accumulate
 | 
			
		||||
        // and we'll skip a whole tick - bringing the average back down again.
 | 
			
		||||
        currentTick++;
 | 
			
		||||
        budget += Math.min( budget + ComputerCraft.maxMainGlobalTime, ComputerCraft.maxMainGlobalTime );
 | 
			
		||||
        budget = Math.min( budget + ComputerCraft.maxMainGlobalTime, ComputerCraft.maxMainGlobalTime );
 | 
			
		||||
        canExecute = budget > 0;
 | 
			
		||||
 | 
			
		||||
        // Cool down any warm computers.
 | 
			
		||||
 
 | 
			
		||||
@@ -224,7 +224,7 @@ final class MainThreadExecutor implements IWorkMonitor
 | 
			
		||||
    {
 | 
			
		||||
        state = State.COOLING;
 | 
			
		||||
        currentTick = MainThread.currentTick();
 | 
			
		||||
        budget += Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime );
 | 
			
		||||
        budget = Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime );
 | 
			
		||||
        if( budget < ComputerCraft.maxMainComputerTime ) return false;
 | 
			
		||||
 | 
			
		||||
        state = State.COOL;
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.core.filesystem;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.filesystem.FileOperationException;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IMount;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
@@ -95,7 +96,7 @@ public class ComboMount implements IMount
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            throw new IOException( "/" + path + ": Not a directory" );
 | 
			
		||||
            throw new FileOperationException( path, "Not a directory" );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -110,7 +111,7 @@ public class ComboMount implements IMount
 | 
			
		||||
                return part.getSize( path );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -126,7 +127,7 @@ public class ComboMount implements IMount
 | 
			
		||||
                return part.openForRead( path );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -141,6 +142,6 @@ public class ComboMount implements IMount
 | 
			
		||||
                return part.openChannelForRead( path );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.core.filesystem;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.filesystem.FileOperationException;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IMount;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
@@ -44,7 +45,7 @@ public class EmptyMount implements IMount
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public InputStream openForRead( @Nonnull String path ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -52,6 +53,6 @@ public class EmptyMount implements IMount
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
package dan200.computercraft.core.filesystem;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Sets;
 | 
			
		||||
import dan200.computercraft.api.filesystem.FileOperationException;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IWritableMount;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
@@ -167,12 +168,12 @@ public class FileMount implements IWritableMount
 | 
			
		||||
    {
 | 
			
		||||
        if( !created() )
 | 
			
		||||
        {
 | 
			
		||||
            if( !path.isEmpty() ) throw new IOException( "/" + path + ": Not a directory" );
 | 
			
		||||
            if( !path.isEmpty() ) throw new FileOperationException( path, "Not a directory" );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        File file = getRealPath( path );
 | 
			
		||||
        if( !file.exists() || !file.isDirectory() ) throw new IOException( "/" + path + ": Not a directory" );
 | 
			
		||||
        if( !file.exists() || !file.isDirectory() ) throw new FileOperationException( path, "Not a directory" );
 | 
			
		||||
 | 
			
		||||
        String[] paths = file.list();
 | 
			
		||||
        for( String subPath : paths )
 | 
			
		||||
@@ -194,7 +195,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
            if( file.exists() ) return file.isDirectory() ? 0 : file.length();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -208,7 +209,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
            if( file.exists() && !file.isDirectory() ) return new FileInputStream( file );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -221,7 +222,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
            if( file.exists() && !file.isDirectory() ) return FileChannel.open( file.toPath(), READ_OPTIONS );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // IWritableMount implementation
 | 
			
		||||
@@ -233,7 +234,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
        File file = getRealPath( path );
 | 
			
		||||
        if( file.exists() )
 | 
			
		||||
        {
 | 
			
		||||
            if( !file.isDirectory() ) throw new IOException( "/" + path + ": File exists" );
 | 
			
		||||
            if( !file.isDirectory() ) throw new FileOperationException( path, "File exists" );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -247,7 +248,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
 | 
			
		||||
        if( getRemainingSpace() < dirsToCreate * MINIMUM_FILE_SIZE )
 | 
			
		||||
        {
 | 
			
		||||
            throw new IOException( "/" + path + ": Out of space" );
 | 
			
		||||
            throw new FileOperationException( path, "Out of space" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if( file.mkdirs() )
 | 
			
		||||
@@ -256,14 +257,14 @@ public class FileMount implements IWritableMount
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            throw new IOException( "/" + path + ": Access denied" );
 | 
			
		||||
            throw new FileOperationException( path, "Access denied" );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void delete( @Nonnull String path ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        if( path.isEmpty() ) throw new IOException( "/" + path + ": Access denied" );
 | 
			
		||||
        if( path.isEmpty() ) throw new FileOperationException( path, "Access denied" );
 | 
			
		||||
 | 
			
		||||
        if( created() )
 | 
			
		||||
        {
 | 
			
		||||
@@ -319,7 +320,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
    {
 | 
			
		||||
        create();
 | 
			
		||||
        File file = getRealPath( path );
 | 
			
		||||
        if( file.exists() && file.isDirectory() ) throw new IOException( "/" + path + ": Cannot write to directory" );
 | 
			
		||||
        if( file.exists() && file.isDirectory() ) throw new FileOperationException( path, "Cannot write to directory" );
 | 
			
		||||
 | 
			
		||||
        if( file.exists() )
 | 
			
		||||
        {
 | 
			
		||||
@@ -327,7 +328,7 @@ public class FileMount implements IWritableMount
 | 
			
		||||
        }
 | 
			
		||||
        else if( getRemainingSpace() < MINIMUM_FILE_SIZE )
 | 
			
		||||
        {
 | 
			
		||||
            throw new IOException( "/" + path + ": Out of space" );
 | 
			
		||||
            throw new FileOperationException( path, "Out of space" );
 | 
			
		||||
        }
 | 
			
		||||
        m_usedSpace += MINIMUM_FILE_SIZE;
 | 
			
		||||
 | 
			
		||||
@@ -340,12 +341,12 @@ public class FileMount implements IWritableMount
 | 
			
		||||
    {
 | 
			
		||||
        if( !created() )
 | 
			
		||||
        {
 | 
			
		||||
            throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
            throw new FileOperationException( path, "No such file" );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        File file = getRealPath( path );
 | 
			
		||||
        if( !file.exists() ) throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        if( file.isDirectory() ) throw new IOException( "/" + path + ": Cannot write to directory" );
 | 
			
		||||
        if( !file.exists() ) throw new FileOperationException( path, "No such file" );
 | 
			
		||||
        if( file.isDirectory() ) throw new FileOperationException( path, "Cannot write to directory" );
 | 
			
		||||
 | 
			
		||||
        // Allowing seeking when appending is not recommended, so we use a separate channel.
 | 
			
		||||
        return new WritableCountingChannel(
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ package dan200.computercraft.core.filesystem;
 | 
			
		||||
 | 
			
		||||
import com.google.common.io.ByteStreams;
 | 
			
		||||
import dan200.computercraft.ComputerCraft;
 | 
			
		||||
import dan200.computercraft.api.filesystem.FileOperationException;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IFileSystem;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IMount;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IWritableMount;
 | 
			
		||||
@@ -45,7 +46,7 @@ public class FileSystem
 | 
			
		||||
            m_writableMount = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public MountWrapper( String label, String location, IWritableMount mount )
 | 
			
		||||
        MountWrapper( String label, String location, IWritableMount mount )
 | 
			
		||||
        {
 | 
			
		||||
            this( label, location, (IMount) mount );
 | 
			
		||||
            m_writableMount = mount;
 | 
			
		||||
@@ -107,7 +108,7 @@ public class FileSystem
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -122,12 +123,12 @@ public class FileSystem
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    throw new FileSystemException( "/" + path + ": Not a directory" );
 | 
			
		||||
                    throw localExceptionOf( path, "Not a directory" );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -149,12 +150,12 @@ public class FileSystem
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    throw new FileSystemException( "/" + path + ": No such file" );
 | 
			
		||||
                    throw localExceptionOf( path, "No such file" );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -169,12 +170,12 @@ public class FileSystem
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    throw new FileSystemException( "/" + path + ": No such file" );
 | 
			
		||||
                    throw localExceptionOf( path, "No such file" );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -182,19 +183,14 @@ public class FileSystem
 | 
			
		||||
 | 
			
		||||
        public void makeDirectory( String path ) throws FileSystemException
 | 
			
		||||
        {
 | 
			
		||||
            if( m_writableMount == null )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( "/" + path + ": Access denied" );
 | 
			
		||||
            }
 | 
			
		||||
            if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
 | 
			
		||||
 | 
			
		||||
            path = toLocal( path );
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                path = toLocal( path );
 | 
			
		||||
                if( m_mount.exists( path ) )
 | 
			
		||||
                {
 | 
			
		||||
                    if( !m_mount.isDirectory( path ) )
 | 
			
		||||
                    {
 | 
			
		||||
                        throw new FileSystemException( "/" + path + ": File exists" );
 | 
			
		||||
                    }
 | 
			
		||||
                    if( !m_mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
@@ -203,16 +199,14 @@ public class FileSystem
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void delete( String path ) throws FileSystemException
 | 
			
		||||
        {
 | 
			
		||||
            if( m_writableMount == null )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( "/" + path + ": Access denied" );
 | 
			
		||||
            }
 | 
			
		||||
            if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                path = toLocal( path );
 | 
			
		||||
@@ -227,22 +221,20 @@ public class FileSystem
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public WritableByteChannel openForWrite( String path ) throws FileSystemException
 | 
			
		||||
        {
 | 
			
		||||
            if( m_writableMount == null )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( "/" + path + ": Access denied" );
 | 
			
		||||
            }
 | 
			
		||||
            if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
 | 
			
		||||
 | 
			
		||||
            path = toLocal( path );
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                path = toLocal( path );
 | 
			
		||||
                if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
 | 
			
		||||
                {
 | 
			
		||||
                    throw new FileSystemException( "/" + path + ": Cannot write to directory" );
 | 
			
		||||
                    throw localExceptionOf( path, "Cannot write to directory" );
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
@@ -263,19 +255,17 @@ public class FileSystem
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public WritableByteChannel openForAppend( String path ) throws FileSystemException
 | 
			
		||||
        {
 | 
			
		||||
            if( m_writableMount == null )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( "/" + path + ": Access denied" );
 | 
			
		||||
            }
 | 
			
		||||
            if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
 | 
			
		||||
 | 
			
		||||
            path = toLocal( path );
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                path = toLocal( path );
 | 
			
		||||
                if( !m_mount.exists( path ) )
 | 
			
		||||
                {
 | 
			
		||||
                    if( !path.isEmpty() )
 | 
			
		||||
@@ -290,7 +280,7 @@ public class FileSystem
 | 
			
		||||
                }
 | 
			
		||||
                else if( m_mount.isDirectory( path ) )
 | 
			
		||||
                {
 | 
			
		||||
                    throw new FileSystemException( "/" + path + ": Cannot write to directory" );
 | 
			
		||||
                    throw localExceptionOf( path, "Cannot write to directory" );
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
@@ -303,16 +293,36 @@ public class FileSystem
 | 
			
		||||
            }
 | 
			
		||||
            catch( IOException e )
 | 
			
		||||
            {
 | 
			
		||||
                throw new FileSystemException( e.getMessage() );
 | 
			
		||||
                throw localExceptionOf( e );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // private members
 | 
			
		||||
 | 
			
		||||
        private String toLocal( String path )
 | 
			
		||||
        {
 | 
			
		||||
            return FileSystem.toLocal( path, m_location );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private FileSystemException localExceptionOf( IOException e )
 | 
			
		||||
        {
 | 
			
		||||
            if( !m_location.isEmpty() && e instanceof FileOperationException )
 | 
			
		||||
            {
 | 
			
		||||
                FileOperationException ex = (FileOperationException) e;
 | 
			
		||||
                if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new FileSystemException( e.getMessage() );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private FileSystemException localExceptionOf( String path, String message )
 | 
			
		||||
        {
 | 
			
		||||
            if( !m_location.isEmpty() ) path = path.isEmpty() ? m_location : m_location + "/" + path;
 | 
			
		||||
            return exceptionOf( path, message );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static FileSystemException exceptionOf( String path, String message )
 | 
			
		||||
        {
 | 
			
		||||
            return new FileSystemException( "/" + path + ": " + message );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
 | 
			
		||||
@@ -769,7 +779,7 @@ public class FileSystem
 | 
			
		||||
 | 
			
		||||
        // Clean the path or illegal characters.
 | 
			
		||||
        final char[] specialChars = new char[] {
 | 
			
		||||
            '"', ':', '<', '>', '?', '|' // Sorted by ascii value (important)
 | 
			
		||||
            '"', ':', '<', '>', '?', '|', // Sorted by ascii value (important)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        StringBuilder cleanName = new StringBuilder();
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ package dan200.computercraft.core.filesystem;
 | 
			
		||||
import com.google.common.cache.Cache;
 | 
			
		||||
import com.google.common.cache.CacheBuilder;
 | 
			
		||||
import com.google.common.io.ByteStreams;
 | 
			
		||||
import dan200.computercraft.api.filesystem.FileOperationException;
 | 
			
		||||
import dan200.computercraft.api.filesystem.IMount;
 | 
			
		||||
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
 | 
			
		||||
import dan200.computercraft.shared.util.IoUtil;
 | 
			
		||||
@@ -92,7 +93,7 @@ public class JarMount implements IMount
 | 
			
		||||
        new MountReference( this );
 | 
			
		||||
 | 
			
		||||
        // Read in all the entries
 | 
			
		||||
        root = new FileEntry( "" );
 | 
			
		||||
        root = new FileEntry();
 | 
			
		||||
        Enumeration<? extends ZipEntry> zipEntries = zip.entries();
 | 
			
		||||
        while( zipEntries.hasMoreElements() )
 | 
			
		||||
        {
 | 
			
		||||
@@ -139,7 +140,7 @@ public class JarMount implements IMount
 | 
			
		||||
            FileEntry nextEntry = lastEntry.children.get( part );
 | 
			
		||||
            if( nextEntry == null || !nextEntry.isDirectory() )
 | 
			
		||||
            {
 | 
			
		||||
                lastEntry.children.put( part, nextEntry = new FileEntry( part ) );
 | 
			
		||||
                lastEntry.children.put( part, nextEntry = new FileEntry() );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            lastEntry = nextEntry;
 | 
			
		||||
@@ -166,7 +167,7 @@ public class JarMount implements IMount
 | 
			
		||||
    public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
 | 
			
		||||
    {
 | 
			
		||||
        FileEntry file = get( path );
 | 
			
		||||
        if( file == null || !file.isDirectory() ) throw new IOException( "/" + path + ": Not a directory" );
 | 
			
		||||
        if( file == null || !file.isDirectory() ) throw new FileOperationException( path, "Not a directory" );
 | 
			
		||||
 | 
			
		||||
        file.list( contents );
 | 
			
		||||
    }
 | 
			
		||||
@@ -176,7 +177,7 @@ public class JarMount implements IMount
 | 
			
		||||
    {
 | 
			
		||||
        FileEntry file = get( path );
 | 
			
		||||
        if( file != null ) return file.size;
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -218,22 +219,15 @@ public class JarMount implements IMount
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new IOException( "/" + path + ": No such file" );
 | 
			
		||||
        throw new FileOperationException( path, "No such file" );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class FileEntry
 | 
			
		||||
    {
 | 
			
		||||
        final String name;
 | 
			
		||||
 | 
			
		||||
        String path;
 | 
			
		||||
        long size;
 | 
			
		||||
        Map<String, FileEntry> children;
 | 
			
		||||
 | 
			
		||||
        FileEntry( String name )
 | 
			
		||||
        {
 | 
			
		||||
            this.name = name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void setup( ZipEntry entry )
 | 
			
		||||
        {
 | 
			
		||||
            path = entry.getName();
 | 
			
		||||
 
 | 
			
		||||
@@ -242,7 +242,7 @@ public class CobaltLuaMachine implements ILuaMachine
 | 
			
		||||
                        }
 | 
			
		||||
                        catch( InterruptedException e )
 | 
			
		||||
                        {
 | 
			
		||||
                            throw new OrphanedThread();
 | 
			
		||||
                            throw new InterruptedError( e );
 | 
			
		||||
                        }
 | 
			
		||||
                        catch( LuaException e )
 | 
			
		||||
                        {
 | 
			
		||||
@@ -550,7 +550,7 @@ public class CobaltLuaMachine implements ILuaMachine
 | 
			
		||||
                {
 | 
			
		||||
                    if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running task", t );
 | 
			
		||||
                    m_computer.queueEvent( "task_complete", new Object[] {
 | 
			
		||||
                        taskID, false, "Java Exception Thrown: " + t
 | 
			
		||||
                        taskID, false, "Java Exception Thrown: " + t,
 | 
			
		||||
                    } );
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ package dan200.computercraft.core.terminal;
 | 
			
		||||
 | 
			
		||||
public class TextBuffer
 | 
			
		||||
{
 | 
			
		||||
    public char[] m_text;
 | 
			
		||||
    private final char[] m_text;
 | 
			
		||||
 | 
			
		||||
    public TextBuffer( char c, int length )
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -402,7 +402,7 @@ public final class Config
 | 
			
		||||
 | 
			
		||||
            if( oldProperty.isList() )
 | 
			
		||||
            {
 | 
			
		||||
                oldProperty.setValues( oldProperty.getStringList() );
 | 
			
		||||
                oldProperty.setValues( newProperty.getStringList() );
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,6 @@ public abstract class BlockGeneric extends Block implements ITileEntityProvider
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    @SuppressWarnings( "deprecation" )
 | 
			
		||||
    public final void neighborChanged( IBlockState state, World world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos )
 | 
			
		||||
    {
 | 
			
		||||
        TileEntity tile = world.getTileEntity( pos );
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,6 @@ public abstract class TileGeneric extends TileEntity
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings( "deprecation" )
 | 
			
		||||
    public void onNeighbourChange( @Nonnull BlockPos neighbour )
 | 
			
		||||
    {
 | 
			
		||||
        onNeighbourChange();
 | 
			
		||||
 
 | 
			
		||||
@@ -47,9 +47,7 @@ public class CommandAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "commands"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "commands" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -62,7 +60,7 @@ public class CommandAPI implements ILuaAPI
 | 
			
		||||
            "list",
 | 
			
		||||
            "getBlockPosition",
 | 
			
		||||
            "getBlockInfos",
 | 
			
		||||
            "getBlockInfo"
 | 
			
		||||
            "getBlockInfo",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ public class TileComputer extends TileComputerBase
 | 
			
		||||
        ServerComputer computer = new ServerComputer(
 | 
			
		||||
            getWorld(),
 | 
			
		||||
            id,
 | 
			
		||||
            m_label,
 | 
			
		||||
            label,
 | 
			
		||||
            instanceID,
 | 
			
		||||
            family,
 | 
			
		||||
            ComputerCraft.terminalWidth_computer,
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,10 @@ import dan200.computercraft.shared.util.DirectionUtil;
 | 
			
		||||
import dan200.computercraft.shared.util.RedstoneUtil;
 | 
			
		||||
import joptsimple.internal.Strings;
 | 
			
		||||
import net.minecraft.block.Block;
 | 
			
		||||
import net.minecraft.block.BlockRedstoneWire;
 | 
			
		||||
import net.minecraft.block.state.IBlockState;
 | 
			
		||||
import net.minecraft.entity.player.EntityPlayer;
 | 
			
		||||
import net.minecraft.init.Blocks;
 | 
			
		||||
import net.minecraft.init.Items;
 | 
			
		||||
import net.minecraft.item.ItemStack;
 | 
			
		||||
import net.minecraft.nbt.NBTTagCompound;
 | 
			
		||||
@@ -34,6 +37,7 @@ import net.minecraft.util.text.ITextComponent;
 | 
			
		||||
import net.minecraft.util.text.TextComponentString;
 | 
			
		||||
import net.minecraft.util.text.TextComponentTranslation;
 | 
			
		||||
import net.minecraft.world.IWorldNameable;
 | 
			
		||||
import net.minecraft.world.World;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -43,7 +47,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
{
 | 
			
		||||
    private int m_instanceID = -1;
 | 
			
		||||
    private int m_computerID = -1;
 | 
			
		||||
    protected String m_label = null;
 | 
			
		||||
    protected String label = null;
 | 
			
		||||
    private boolean m_on = false;
 | 
			
		||||
    boolean m_startOn = false;
 | 
			
		||||
    private boolean m_fresh = false;
 | 
			
		||||
@@ -124,16 +128,14 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean getRedstoneConnectivity( EnumFacing side )
 | 
			
		||||
    {
 | 
			
		||||
        if( side == null ) return false;
 | 
			
		||||
        ComputerSide localDir = remapLocalSide( DirectionUtil.toLocal( this, side.getOpposite() ) );
 | 
			
		||||
        return !isRedstoneBlockedOnSide( localDir );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getRedstoneOutput( EnumFacing side )
 | 
			
		||||
    {
 | 
			
		||||
        ComputerSide localDir = remapLocalSide( DirectionUtil.toLocal( this, side ) );
 | 
			
		||||
        if( !isRedstoneBlockedOnSide( localDir ) && world != null && !world.isRemote )
 | 
			
		||||
        if( world != null && !world.isRemote )
 | 
			
		||||
        {
 | 
			
		||||
            ServerComputer computer = getServerComputer();
 | 
			
		||||
            if( computer != null ) return computer.getRedstoneOutput( localDir );
 | 
			
		||||
@@ -144,15 +146,14 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean getBundledRedstoneConnectivity( @Nonnull EnumFacing side )
 | 
			
		||||
    {
 | 
			
		||||
        ComputerSide localDir = remapLocalSide( DirectionUtil.toLocal( this, side ) );
 | 
			
		||||
        return !isRedstoneBlockedOnSide( localDir );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getBundledRedstoneOutput( @Nonnull EnumFacing side )
 | 
			
		||||
    {
 | 
			
		||||
        ComputerSide localDir = remapLocalSide( DirectionUtil.toLocal( this, side ) );
 | 
			
		||||
        if( !isRedstoneBlockedOnSide( localDir ) && !world.isRemote )
 | 
			
		||||
        if( !world.isRemote )
 | 
			
		||||
        {
 | 
			
		||||
            ServerComputer computer = getServerComputer();
 | 
			
		||||
            if( computer != null ) return computer.getBundledRedstoneOutput( localDir );
 | 
			
		||||
@@ -189,7 +190,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
 | 
			
		||||
                m_fresh = false;
 | 
			
		||||
                m_computerID = computer.getID();
 | 
			
		||||
                m_label = computer.getLabel();
 | 
			
		||||
                label = computer.getLabel();
 | 
			
		||||
                m_on = computer.isOn();
 | 
			
		||||
 | 
			
		||||
                if( computer.hasOutputChanged() ) updateOutput();
 | 
			
		||||
@@ -211,9 +212,9 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
        {
 | 
			
		||||
            nbt.setInteger( "computerID", m_computerID );
 | 
			
		||||
        }
 | 
			
		||||
        if( m_label != null )
 | 
			
		||||
        if( label != null )
 | 
			
		||||
        {
 | 
			
		||||
            nbt.setString( "label", m_label );
 | 
			
		||||
            nbt.setString( "label", label );
 | 
			
		||||
        }
 | 
			
		||||
        nbt.setBoolean( "on", m_on );
 | 
			
		||||
        return super.writeToNBT( nbt );
 | 
			
		||||
@@ -247,7 +248,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
        m_computerID = id;
 | 
			
		||||
 | 
			
		||||
        // Load label
 | 
			
		||||
        m_label = nbt.hasKey( "label" ) ? nbt.getString( "label" ) : null;
 | 
			
		||||
        label = nbt.hasKey( "label" ) ? nbt.getString( "label" ) : null;
 | 
			
		||||
 | 
			
		||||
        // Load power state
 | 
			
		||||
        m_startOn = nbt.getBoolean( "on" );
 | 
			
		||||
@@ -259,11 +260,6 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected boolean isRedstoneBlockedOnSide( ComputerSide localSide )
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected ComputerSide remapLocalSide( ComputerSide localSide )
 | 
			
		||||
    {
 | 
			
		||||
        return localSide;
 | 
			
		||||
@@ -273,17 +269,36 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    {
 | 
			
		||||
        EnumFacing offsetSide = dir.getOpposite();
 | 
			
		||||
        ComputerSide localDir = remapLocalSide( DirectionUtil.toLocal( this, dir ) );
 | 
			
		||||
        if( !isRedstoneBlockedOnSide( localDir ) )
 | 
			
		||||
        {
 | 
			
		||||
            computer.setRedstoneInput( localDir, getWorld().getRedstonePower( offset, dir ) );
 | 
			
		||||
            computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getWorld(), offset, offsetSide ) );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        computer.setRedstoneInput( localDir, getRedstoneInput( world, offset, dir ) );
 | 
			
		||||
        computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getWorld(), offset, offsetSide ) );
 | 
			
		||||
 | 
			
		||||
        if( !isPeripheralBlockedOnSide( localDir ) )
 | 
			
		||||
        {
 | 
			
		||||
            computer.setPeripheral( localDir, Peripherals.getPeripheral( getWorld(), offset, offsetSide ) );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the redstone input for an adjacent block
 | 
			
		||||
     *
 | 
			
		||||
     * @param world The world we exist in
 | 
			
		||||
     * @param pos   The position of the neighbour
 | 
			
		||||
     * @param side  The side we are reading from
 | 
			
		||||
     * @return The effective redstone power
 | 
			
		||||
     * @see net.minecraft.block.BlockRedstoneDiode#calculateInputStrength(World, BlockPos, IBlockState)
 | 
			
		||||
     */
 | 
			
		||||
    protected static int getRedstoneInput( World world, BlockPos pos, EnumFacing side )
 | 
			
		||||
    {
 | 
			
		||||
        int power = world.getRedstonePower( pos, side );
 | 
			
		||||
        if( power >= 15 ) return power;
 | 
			
		||||
 | 
			
		||||
        IBlockState neighbour = world.getBlockState( pos );
 | 
			
		||||
        return neighbour.getBlock() == Blocks.REDSTONE_WIRE
 | 
			
		||||
            ? Math.max( power, neighbour.getValue( BlockRedstoneWire.POWER ) )
 | 
			
		||||
            : power;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void updateInput()
 | 
			
		||||
    {
 | 
			
		||||
        if( getWorld() == null || getWorld().isRemote ) return;
 | 
			
		||||
@@ -346,7 +361,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getLabel()
 | 
			
		||||
    {
 | 
			
		||||
        return m_label;
 | 
			
		||||
        return label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -363,9 +378,9 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void setLabel( String label )
 | 
			
		||||
    {
 | 
			
		||||
        if( getWorld().isRemote || Objects.equals( m_label, label ) ) return;
 | 
			
		||||
        if( getWorld().isRemote || Objects.equals( this.label, label ) ) return;
 | 
			
		||||
 | 
			
		||||
        m_label = label;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
        ServerComputer computer = getServerComputer();
 | 
			
		||||
        if( computer != null ) computer.setLabel( label );
 | 
			
		||||
        markDirty();
 | 
			
		||||
@@ -438,8 +453,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    protected void writeDescription( @Nonnull NBTTagCompound nbt )
 | 
			
		||||
    {
 | 
			
		||||
        super.writeDescription( nbt );
 | 
			
		||||
        nbt.setInteger( "instanceID", createServerComputer().getInstanceID() );
 | 
			
		||||
        if( m_label != null ) nbt.setString( "label", m_label );
 | 
			
		||||
        if( m_instanceID >= 0 ) nbt.setInteger( "instanceID", m_instanceID );
 | 
			
		||||
        if( label != null ) nbt.setString( "label", label );
 | 
			
		||||
        if( m_computerID >= 0 ) nbt.setInteger( "computerID", m_computerID );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -448,7 +463,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    {
 | 
			
		||||
        super.readDescription( nbt );
 | 
			
		||||
        m_instanceID = nbt.getInteger( "instanceID" );
 | 
			
		||||
        m_label = nbt.hasKey( "label" ) ? nbt.getString( "label" ) : null;
 | 
			
		||||
        label = nbt.hasKey( "label" ) ? nbt.getString( "label" ) : null;
 | 
			
		||||
        m_computerID = nbt.hasKey( "computerID" ) ? nbt.getInteger( "computerID" ) : -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -459,7 +474,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
            unload();
 | 
			
		||||
            m_instanceID = copy.m_instanceID;
 | 
			
		||||
            m_computerID = copy.m_computerID;
 | 
			
		||||
            m_label = copy.m_label;
 | 
			
		||||
            label = copy.label;
 | 
			
		||||
            m_on = copy.m_on;
 | 
			
		||||
            m_startOn = copy.m_startOn;
 | 
			
		||||
            updateBlock();
 | 
			
		||||
@@ -478,13 +493,13 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getName()
 | 
			
		||||
    {
 | 
			
		||||
        return hasCustomName() ? m_label : getBlockType().getTranslationKey();
 | 
			
		||||
        return hasCustomName() ? label : getBlockType().getTranslationKey();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasCustomName()
 | 
			
		||||
    {
 | 
			
		||||
        return !Strings.isNullOrEmpty( m_label );
 | 
			
		||||
        return !Strings.isNullOrEmpty( label );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,9 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
 | 
			
		||||
public class ComputerRegistry<TComputer extends IComputer>
 | 
			
		||||
public class ComputerRegistry<T extends IComputer>
 | 
			
		||||
{
 | 
			
		||||
    private Map<Integer, TComputer> m_computers;
 | 
			
		||||
    private Map<Integer, T> m_computers;
 | 
			
		||||
    private int m_nextUnusedInstanceID;
 | 
			
		||||
    private int m_sessionID;
 | 
			
		||||
 | 
			
		||||
@@ -33,12 +33,12 @@ public class ComputerRegistry<TComputer extends IComputer>
 | 
			
		||||
        return m_nextUnusedInstanceID++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Collection<TComputer> getComputers()
 | 
			
		||||
    public Collection<T> getComputers()
 | 
			
		||||
    {
 | 
			
		||||
        return m_computers.values();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TComputer get( int instanceID )
 | 
			
		||||
    public T get( int instanceID )
 | 
			
		||||
    {
 | 
			
		||||
        if( instanceID >= 0 )
 | 
			
		||||
        {
 | 
			
		||||
@@ -55,7 +55,7 @@ public class ComputerRegistry<TComputer extends IComputer>
 | 
			
		||||
        return m_computers.containsKey( instanceID );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void add( int instanceID, TComputer computer )
 | 
			
		||||
    public void add( int instanceID, T computer )
 | 
			
		||||
    {
 | 
			
		||||
        if( m_computers.containsKey( instanceID ) )
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -175,12 +175,13 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
 | 
			
		||||
            FMLCommonHandler handler = FMLCommonHandler.instance();
 | 
			
		||||
            if( handler != null )
 | 
			
		||||
            {
 | 
			
		||||
                IMessage packet = createTerminalPacket();
 | 
			
		||||
                IMessage packet = null;
 | 
			
		||||
                MinecraftServer server = handler.getMinecraftServerInstance();
 | 
			
		||||
                for( EntityPlayerMP player : server.getPlayerList().getPlayers() )
 | 
			
		||||
                {
 | 
			
		||||
                    if( isInteracting( player ) )
 | 
			
		||||
                    {
 | 
			
		||||
                        if( packet == null ) packet = createTerminalPacket();
 | 
			
		||||
                        NetworkHandler.sendToPlayer( player, packet );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -219,6 +220,13 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
 | 
			
		||||
        return m_instanceID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * getID and getLabel are deprecated on IComputer, as they do not make sense in ClientComputer.
 | 
			
		||||
     * However, for compatibility reasons, we still need these here, and have no choice but to override the IComputer methods.
 | 
			
		||||
     *
 | 
			
		||||
     * Hence, we suppress the deprecation warning.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @SuppressWarnings( "deprecation" )
 | 
			
		||||
    public int getID()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.integration.charset;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.common.TileGeneric;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.integration.charset;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.integration.charset;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.ComputerCraft;
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,7 @@ public class JEIComputerCraft implements IModPlugin
 | 
			
		||||
        ingredients.addIngredientsAtRuntime( VanillaTypes.ITEM, upgradeItems );
 | 
			
		||||
 | 
			
		||||
        // Hide all upgrade recipes
 | 
			
		||||
        @SuppressWarnings( "unchecked" )
 | 
			
		||||
        IRecipeCategory<? extends IRecipeWrapper> category = (IRecipeCategory<? extends IRecipeWrapper>) registry.getRecipeCategory( VanillaRecipeCategoryUid.CRAFTING );
 | 
			
		||||
        if( category != null )
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -39,5 +39,8 @@ public enum PeripheralType implements IStringSerializable
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() { return name; }
 | 
			
		||||
    public String toString()
 | 
			
		||||
    {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ public class CommandBlockPeripheral implements IPeripheral
 | 
			
		||||
        {
 | 
			
		||||
            case 0: // getCommand
 | 
			
		||||
                return context.executeMainThreadTask( () -> new Object[] {
 | 
			
		||||
                    m_commandBlock.getCommandBlockLogic().getCommand()
 | 
			
		||||
                    m_commandBlock.getCommandBlockLogic().getCommand(),
 | 
			
		||||
                } );
 | 
			
		||||
            case 1:
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.api.media.IMedia;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IComputerAccess;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.shared.MediaProviders;
 | 
			
		||||
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
 | 
			
		||||
import dan200.computercraft.shared.util.StringUtil;
 | 
			
		||||
import net.minecraft.item.Item;
 | 
			
		||||
@@ -20,11 +21,11 @@ import javax.annotation.Nonnull;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
 | 
			
		||||
 | 
			
		||||
public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
{
 | 
			
		||||
    private final TileDiskDrive m_diskDrive;
 | 
			
		||||
 | 
			
		||||
    public DiskDrivePeripheral( TileDiskDrive diskDrive )
 | 
			
		||||
    DiskDrivePeripheral( TileDiskDrive diskDrive )
 | 
			
		||||
    {
 | 
			
		||||
        m_diskDrive = diskDrive;
 | 
			
		||||
    }
 | 
			
		||||
@@ -51,12 +52,12 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
            "playAudio",
 | 
			
		||||
            "stopAudio",
 | 
			
		||||
            "ejectDisk",
 | 
			
		||||
            "getDiskID"
 | 
			
		||||
            "getDiskID",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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 )
 | 
			
		||||
        {
 | 
			
		||||
@@ -64,21 +65,26 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
                return new Object[] { !m_diskDrive.getDiskStack().isEmpty() };
 | 
			
		||||
            case 1: // getDiskLabel
 | 
			
		||||
            {
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return media == null ? null : new Object[] { media.getLabel( m_diskDrive.getDiskStack() ) };
 | 
			
		||||
                ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                return media == null ? null : new Object[] { media.getLabel( stack ) };
 | 
			
		||||
            }
 | 
			
		||||
            case 2: // setDiskLabel
 | 
			
		||||
            {
 | 
			
		||||
                String label = optString( arguments, 0, null );
 | 
			
		||||
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                if( media == null ) return null;
 | 
			
		||||
                return context.executeMainThreadTask( () -> {
 | 
			
		||||
                    ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                    IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                    if( media == null ) return null;
 | 
			
		||||
 | 
			
		||||
                ItemStack disk = m_diskDrive.getDiskStack();
 | 
			
		||||
                label = StringUtil.normaliseLabel( label );
 | 
			
		||||
                if( !media.setLabel( disk, label ) ) throw new LuaException( "Disk label cannot be changed" );
 | 
			
		||||
                m_diskDrive.setDiskStack( disk );
 | 
			
		||||
                return null;
 | 
			
		||||
                    if( !media.setLabel( stack, StringUtil.normaliseLabel( label ) ) )
 | 
			
		||||
                    {
 | 
			
		||||
                        throw new LuaException( "Disk label cannot be changed" );
 | 
			
		||||
                    }
 | 
			
		||||
                    m_diskDrive.setDiskStack( stack );
 | 
			
		||||
                    return null;
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
            case 3: // hasData
 | 
			
		||||
                return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null };
 | 
			
		||||
@@ -87,14 +93,16 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
            case 5:
 | 
			
		||||
            {
 | 
			
		||||
                // hasAudio
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return new Object[] { media != null && media.getAudio( m_diskDrive.getDiskStack() ) != null };
 | 
			
		||||
                ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                return new Object[] { media != null && media.getAudio( stack ) != null };
 | 
			
		||||
            }
 | 
			
		||||
            case 6:
 | 
			
		||||
            {
 | 
			
		||||
                // getAudioTitle
 | 
			
		||||
                IMedia media = m_diskDrive.getDiskMedia();
 | 
			
		||||
                return new Object[] { media != null ? media.getAudioTitle( m_diskDrive.getDiskStack() ) : false };
 | 
			
		||||
                ItemStack stack = m_diskDrive.getDiskStack();
 | 
			
		||||
                IMedia media = MediaProviders.get( stack );
 | 
			
		||||
                return new Object[] { media != null ? media.getAudioTitle( stack ) : false };
 | 
			
		||||
            }
 | 
			
		||||
            case 7: // playAudio
 | 
			
		||||
                m_diskDrive.playDiskAudio();
 | 
			
		||||
@@ -131,8 +139,7 @@ public class DiskDrivePeripheral implements IPeripheral
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals( IPeripheral other )
 | 
			
		||||
    {
 | 
			
		||||
        if( this == other ) return true;
 | 
			
		||||
        return other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
 | 
			
		||||
        return this == other || other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
 
 | 
			
		||||
@@ -318,35 +318,31 @@ public class TileDiskDrive extends TilePeripheralBase implements DefaultInventor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public ItemStack getDiskStack()
 | 
			
		||||
    ItemStack getDiskStack()
 | 
			
		||||
    {
 | 
			
		||||
        return getStackInSlot( 0 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDiskStack( @Nonnull ItemStack stack )
 | 
			
		||||
    void setDiskStack( @Nonnull ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        setInventorySlotContents( 0, stack );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IMedia getDiskMedia()
 | 
			
		||||
    private IMedia getDiskMedia()
 | 
			
		||||
    {
 | 
			
		||||
        return MediaProviders.get( getDiskStack() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDiskMountPath( IComputerAccess computer )
 | 
			
		||||
    String getDiskMountPath( IComputerAccess computer )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
            if( m_computers.containsKey( computer ) )
 | 
			
		||||
            {
 | 
			
		||||
                MountInfo info = m_computers.get( computer );
 | 
			
		||||
                return info.mountPath;
 | 
			
		||||
            }
 | 
			
		||||
            MountInfo info = m_computers.get( computer );
 | 
			
		||||
            return info != null ? info.mountPath : null;
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void mount( IComputerAccess computer )
 | 
			
		||||
    void mount( IComputerAccess computer )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -355,7 +351,7 @@ public class TileDiskDrive extends TilePeripheralBase implements DefaultInventor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void unmount( IComputerAccess computer )
 | 
			
		||||
    void unmount( IComputerAccess computer )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -364,7 +360,7 @@ public class TileDiskDrive extends TilePeripheralBase implements DefaultInventor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void playDiskAudio()
 | 
			
		||||
    void playDiskAudio()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -377,7 +373,7 @@ public class TileDiskDrive extends TilePeripheralBase implements DefaultInventor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void stopDiskAudio()
 | 
			
		||||
    void stopDiskAudio()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
@@ -386,7 +382,7 @@ public class TileDiskDrive extends TilePeripheralBase implements DefaultInventor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void ejectDisk()
 | 
			
		||||
    void ejectDisk()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( this )
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
 | 
			
		||||
            for( IComputerAccess computer : m_computers )
 | 
			
		||||
            {
 | 
			
		||||
                computer.queueEvent( "modem_message", new Object[] {
 | 
			
		||||
                    computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance
 | 
			
		||||
                    computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance,
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -103,7 +103,7 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
 | 
			
		||||
            for( IComputerAccess computer : m_computers )
 | 
			
		||||
            {
 | 
			
		||||
                computer.queueEvent( "modem_message", new Object[] {
 | 
			
		||||
                    computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload()
 | 
			
		||||
                    computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(),
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -277,7 +277,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
 | 
			
		||||
        private final String[] m_methods;
 | 
			
		||||
        private final Map<String, Integer> m_methodMap;
 | 
			
		||||
 | 
			
		||||
        public RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name )
 | 
			
		||||
        RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name )
 | 
			
		||||
        {
 | 
			
		||||
            m_element = element;
 | 
			
		||||
            m_peripheral = peripheral;
 | 
			
		||||
 
 | 
			
		||||
@@ -142,7 +142,7 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
 | 
			
		||||
                    for( IComputerAccess computer : monitor.m_computers )
 | 
			
		||||
                    {
 | 
			
		||||
                        computer.queueEvent( "monitor_resize", new Object[] {
 | 
			
		||||
                            computer.getAttachmentName()
 | 
			
		||||
                            computer.getAttachmentName(),
 | 
			
		||||
                        } );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -339,7 +339,7 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
 | 
			
		||||
        return EnumFacing.WEST;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private EnumFacing getDown()
 | 
			
		||||
    public EnumFacing getDown()
 | 
			
		||||
    {
 | 
			
		||||
        int dir = getDir();
 | 
			
		||||
        if( dir <= 5 ) return EnumFacing.UP;
 | 
			
		||||
@@ -660,7 +660,7 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
 | 
			
		||||
                for( IComputerAccess computer : monitor.m_computers )
 | 
			
		||||
                {
 | 
			
		||||
                    computer.queueEvent( "monitor_touch", new Object[] {
 | 
			
		||||
                        computer.getAttachmentName(), xCharPos, yCharPos
 | 
			
		||||
                        computer.getAttachmentName(), xCharPos, yCharPos,
 | 
			
		||||
                    } );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IComputerAccess;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.shared.util.StringUtil;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
 | 
			
		||||
@@ -51,8 +52,13 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
 | 
			
		||||
    public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
 | 
			
		||||
    {
 | 
			
		||||
        // FIXME: There's a theoretical race condition here between getCurrentPage and then using the page. Ideally
 | 
			
		||||
        //  we'd lock on the page, consume it, and unlock.
 | 
			
		||||
 | 
			
		||||
        // FIXME: None of our page modification functions actually mark the tile as dirty, so the page may not be
 | 
			
		||||
        //  persisted correctly.
 | 
			
		||||
        switch( method )
 | 
			
		||||
        {
 | 
			
		||||
            case 0: // write
 | 
			
		||||
@@ -89,10 +95,13 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
                return new Object[] { width, height };
 | 
			
		||||
            }
 | 
			
		||||
            case 4: // newPage
 | 
			
		||||
                return new Object[] { m_printer.startNewPage() };
 | 
			
		||||
                return context.executeMainThreadTask( () -> new Object[] { m_printer.startNewPage() } );
 | 
			
		||||
            case 5: // endPage
 | 
			
		||||
                getCurrentPage();
 | 
			
		||||
                return new Object[] { m_printer.endCurrentPage() };
 | 
			
		||||
                return context.executeMainThreadTask( () -> {
 | 
			
		||||
                    getCurrentPage();
 | 
			
		||||
                    return new Object[] { m_printer.endCurrentPage() };
 | 
			
		||||
                } );
 | 
			
		||||
            case 6: // getInkLevel
 | 
			
		||||
                return new Object[] { m_printer.getInkLevel() };
 | 
			
		||||
            case 7:
 | 
			
		||||
@@ -100,7 +109,7 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
                // setPageTitle
 | 
			
		||||
                String title = optString( args, 0, "" );
 | 
			
		||||
                getCurrentPage();
 | 
			
		||||
                m_printer.setPageTitle( title );
 | 
			
		||||
                m_printer.setPageTitle( StringUtil.normaliseLabel( title ) );
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            case 8: // getPaperLevel
 | 
			
		||||
@@ -123,13 +132,11 @@ public class PrinterPeripheral implements IPeripheral
 | 
			
		||||
        return m_printer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    private Terminal getCurrentPage() throws LuaException
 | 
			
		||||
    {
 | 
			
		||||
        Terminal currentPage = m_printer.getCurrentPage();
 | 
			
		||||
        if( currentPage == null )
 | 
			
		||||
        {
 | 
			
		||||
            throw new LuaException( "Page not started" );
 | 
			
		||||
        }
 | 
			
		||||
        if( currentPage == null ) throw new LuaException( "Page not started" );
 | 
			
		||||
        return currentPage;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,9 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
{
 | 
			
		||||
    // Statics
 | 
			
		||||
 | 
			
		||||
    private static final int[] bottomSlots = new int[] { 7, 8, 9, 10, 11, 12 };
 | 
			
		||||
    private static final int[] topSlots = new int[] { 1, 2, 3, 4, 5, 6 };
 | 
			
		||||
    private static final int[] sideSlots = new int[] { 0 };
 | 
			
		||||
    private static final int[] BOTTOM_SLOTS = new int[] { 7, 8, 9, 10, 11, 12 };
 | 
			
		||||
    private static final int[] TOP_SLOTS = new int[] { 1, 2, 3, 4, 5, 6 };
 | 
			
		||||
    private static final int[] SIDE_SLOTS = new int[] { 0 };
 | 
			
		||||
 | 
			
		||||
    // Members
 | 
			
		||||
 | 
			
		||||
@@ -88,18 +88,12 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Read inventory
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        NBTTagList itemList = nbt.getTagList( "Items", Constants.NBT.TAG_COMPOUND );
 | 
			
		||||
        for( int i = 0; i < itemList.tagCount(); i++ )
 | 
			
		||||
        {
 | 
			
		||||
            NBTTagList nbttaglist = nbt.getTagList( "Items", Constants.NBT.TAG_COMPOUND );
 | 
			
		||||
            for( int i = 0; i < nbttaglist.tagCount(); i++ )
 | 
			
		||||
            {
 | 
			
		||||
                NBTTagCompound itemTag = nbttaglist.getCompoundTagAt( i );
 | 
			
		||||
                int j = itemTag.getByte( "Slot" ) & 0xff;
 | 
			
		||||
                if( j < m_inventory.size() )
 | 
			
		||||
                {
 | 
			
		||||
                    m_inventory.set( j, new ItemStack( itemTag ) );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            NBTTagCompound itemTag = itemList.getCompoundTagAt( i );
 | 
			
		||||
            int slot = itemTag.getByte( "Slot" ) & 0xff;
 | 
			
		||||
            if( slot < m_inventory.size() ) m_inventory.set( slot, new ItemStack( itemTag ) );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -116,21 +110,18 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Write inventory
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        NBTTagList itemList = new NBTTagList();
 | 
			
		||||
        for( int i = 0; i < m_inventory.size(); i++ )
 | 
			
		||||
        {
 | 
			
		||||
            NBTTagList nbttaglist = new NBTTagList();
 | 
			
		||||
            for( int i = 0; i < m_inventory.size(); i++ )
 | 
			
		||||
            {
 | 
			
		||||
                if( !m_inventory.get( i ).isEmpty() )
 | 
			
		||||
                {
 | 
			
		||||
                    NBTTagCompound tag = new NBTTagCompound();
 | 
			
		||||
                    tag.setByte( "Slot", (byte) i );
 | 
			
		||||
                    m_inventory.get( i ).writeToNBT( tag );
 | 
			
		||||
                    nbttaglist.appendTag( tag );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            nbt.setTag( "Items", nbttaglist );
 | 
			
		||||
            ItemStack stack = m_inventory.get( i );
 | 
			
		||||
            if( stack.isEmpty() ) continue;
 | 
			
		||||
 | 
			
		||||
            NBTTagCompound tag = new NBTTagCompound();
 | 
			
		||||
            tag.setByte( "Slot", (byte) i );
 | 
			
		||||
            stack.writeToNBT( tag );
 | 
			
		||||
            itemList.appendTag( tag );
 | 
			
		||||
        }
 | 
			
		||||
        nbt.setTag( "Items", itemList );
 | 
			
		||||
 | 
			
		||||
        return super.writeToNBT( nbt );
 | 
			
		||||
    }
 | 
			
		||||
@@ -148,7 +139,7 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        return super.shouldRefresh( world, pos, oldState, newState ) || BlockPeripheral.getPeripheralType( newState ) != PeripheralType.Printer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isPrinting()
 | 
			
		||||
    boolean isPrinting()
 | 
			
		||||
    {
 | 
			
		||||
        return m_printing;
 | 
			
		||||
    }
 | 
			
		||||
@@ -173,73 +164,59 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack getStackInSlot( int i )
 | 
			
		||||
    public ItemStack getStackInSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        return m_inventory.get( i );
 | 
			
		||||
        return m_inventory.get( slot );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack removeStackFromSlot( int i )
 | 
			
		||||
    public ItemStack removeStackFromSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack result = m_inventory.get( i );
 | 
			
		||||
            m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
            markDirty();
 | 
			
		||||
            updateAnim();
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
        ItemStack result = m_inventory.get( slot );
 | 
			
		||||
        m_inventory.set( slot, ItemStack.EMPTY );
 | 
			
		||||
        markDirty();
 | 
			
		||||
        updateAnim();
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @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;
 | 
			
		||||
 | 
			
		||||
            if( m_inventory.get( i ).getCount() <= j )
 | 
			
		||||
            {
 | 
			
		||||
                ItemStack itemstack = m_inventory.get( i );
 | 
			
		||||
                m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
                markDirty();
 | 
			
		||||
                updateAnim();
 | 
			
		||||
                return itemstack;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ItemStack part = m_inventory.get( i ).splitStack( j );
 | 
			
		||||
            if( m_inventory.get( i ).isEmpty() )
 | 
			
		||||
            {
 | 
			
		||||
                m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
                updateAnim();
 | 
			
		||||
            }
 | 
			
		||||
            markDirty();
 | 
			
		||||
            return part;
 | 
			
		||||
            setInventorySlotContents( slot, ItemStack.EMPTY );
 | 
			
		||||
            return stack;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ItemStack part = stack.splitStack( count );
 | 
			
		||||
        if( m_inventory.get( slot ).isEmpty() )
 | 
			
		||||
        {
 | 
			
		||||
            m_inventory.set( slot, ItemStack.EMPTY );
 | 
			
		||||
            updateAnim();
 | 
			
		||||
        }
 | 
			
		||||
        markDirty();
 | 
			
		||||
        return part;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setInventorySlotContents( int i, @Nonnull ItemStack stack )
 | 
			
		||||
    public void setInventorySlotContents( int slot, @Nonnull ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            m_inventory.set( i, stack );
 | 
			
		||||
            markDirty();
 | 
			
		||||
            updateAnim();
 | 
			
		||||
        }
 | 
			
		||||
        m_inventory.set( slot, stack );
 | 
			
		||||
        markDirty();
 | 
			
		||||
        updateAnim();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clear()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
            markDirty();
 | 
			
		||||
            updateAnim();
 | 
			
		||||
        }
 | 
			
		||||
        for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
        markDirty();
 | 
			
		||||
        updateAnim();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -249,7 +226,7 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        {
 | 
			
		||||
            return isInk( stack );
 | 
			
		||||
        }
 | 
			
		||||
        else if( slot >= topSlots[0] && slot <= topSlots[topSlots.length - 1] )
 | 
			
		||||
        else if( slot >= TOP_SLOTS[0] && slot <= TOP_SLOTS[TOP_SLOTS.length - 1] )
 | 
			
		||||
        {
 | 
			
		||||
            return isPaper( stack );
 | 
			
		||||
        }
 | 
			
		||||
@@ -295,11 +272,11 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        switch( side )
 | 
			
		||||
        {
 | 
			
		||||
            case DOWN: // Bottom (Out tray)
 | 
			
		||||
                return bottomSlots;
 | 
			
		||||
                return BOTTOM_SLOTS;
 | 
			
		||||
            case UP: // Top (In tray)
 | 
			
		||||
                return topSlots;
 | 
			
		||||
                return TOP_SLOTS;
 | 
			
		||||
            default: // Sides (Ink)
 | 
			
		||||
                return sideSlots;
 | 
			
		||||
                return SIDE_SLOTS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -311,14 +288,18 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        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( m_printing && !outputPage() ) return false;
 | 
			
		||||
@@ -326,49 +307,36 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean endCurrentPage()
 | 
			
		||||
    boolean endCurrentPage()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
        {
 | 
			
		||||
            if( 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;
 | 
			
		||||
            return m_printing && outputPage();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getPaperLevel()
 | 
			
		||||
    int getInkLevel()
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack inkStack = m_inventory.get( 0 );
 | 
			
		||||
        return isInk( inkStack ) ? inkStack.getCount() : 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int getPaperLevel()
 | 
			
		||||
    {
 | 
			
		||||
        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( !paperStack.isEmpty() && isPaper( paperStack ) )
 | 
			
		||||
                {
 | 
			
		||||
                    count += paperStack.getCount();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            ItemStack paperStack = m_inventory.get( i );
 | 
			
		||||
            if( isPaper( paperStack ) ) count += paperStack.getCount();
 | 
			
		||||
        }
 | 
			
		||||
        return count;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPageTitle( String title )
 | 
			
		||||
    void setPageTitle( String title )
 | 
			
		||||
    {
 | 
			
		||||
        if( m_printing )
 | 
			
		||||
        synchronized( m_page )
 | 
			
		||||
        {
 | 
			
		||||
            m_pageTitle = title;
 | 
			
		||||
            if( m_printing ) m_pageTitle = title;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -385,145 +353,126 @@ public class TilePrinter extends TilePeripheralBase implements DefaultSidedInven
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
    {
 | 
			
		||||
        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 );
 | 
			
		||||
            if( !isInk( inkStack ) ) return false;
 | 
			
		||||
            ItemStack paperStack = m_inventory.get( i );
 | 
			
		||||
            if( paperStack.isEmpty() || !isPaper( paperStack ) ) continue;
 | 
			
		||||
 | 
			
		||||
            for( int i = 1; i < 7; i++ )
 | 
			
		||||
            // Setup the new page
 | 
			
		||||
            int colour = inkStack.getItemDamage();
 | 
			
		||||
            m_page.setTextColour( colour >= 0 && colour < 16 ? 15 - colour : 15 );
 | 
			
		||||
 | 
			
		||||
            m_page.clear();
 | 
			
		||||
            if( paperStack.getItem() instanceof ItemPrintout )
 | 
			
		||||
            {
 | 
			
		||||
                ItemStack paperStack = m_inventory.get( i );
 | 
			
		||||
                if( !paperStack.isEmpty() && isPaper( paperStack ) )
 | 
			
		||||
                m_pageTitle = ItemPrintout.getTitle( paperStack );
 | 
			
		||||
                String[] text = ItemPrintout.getText( paperStack );
 | 
			
		||||
                String[] textColour = ItemPrintout.getColours( paperStack );
 | 
			
		||||
                for( int y = 0; y < m_page.getHeight(); y++ )
 | 
			
		||||
                {
 | 
			
		||||
                    // Setup the new page
 | 
			
		||||
                    int colour = inkStack.getItemDamage();
 | 
			
		||||
                    m_page.setTextColour( colour >= 0 && colour < 16 ? 15 - colour : 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 );
 | 
			
		||||
                        updateAnim();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    markDirty();
 | 
			
		||||
                    m_printing = true;
 | 
			
		||||
                    return true;
 | 
			
		||||
                    m_page.setLine( y, text[y], textColour[y], "" );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            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 );
 | 
			
		||||
                updateAnim();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            markDirty();
 | 
			
		||||
            m_printing = true;
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
            String[] lines = new String[height];
 | 
			
		||||
            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 : bottomSlots )
 | 
			
		||||
                {
 | 
			
		||||
                    if( m_inventory.get( slot ).isEmpty() )
 | 
			
		||||
                    {
 | 
			
		||||
                        setInventorySlotContents( slot, stack );
 | 
			
		||||
                        m_printing = false;
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
            lines[i] = m_page.getLine( i ).toString();
 | 
			
		||||
            colours[i] = m_page.getTextColourLine( i ).toString();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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()
 | 
			
		||||
    {
 | 
			
		||||
        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 );
 | 
			
		||||
                if( !stack.isEmpty() )
 | 
			
		||||
                {
 | 
			
		||||
                    // Remove the stack from the inventory
 | 
			
		||||
                    setInventorySlotContents( i, ItemStack.EMPTY );
 | 
			
		||||
                // Remove the stack from the inventory
 | 
			
		||||
                setInventorySlotContents( i, ItemStack.EMPTY );
 | 
			
		||||
 | 
			
		||||
                    // Spawn the item in the world
 | 
			
		||||
                    BlockPos pos = getPos();
 | 
			
		||||
                    double x = pos.getX() + 0.5;
 | 
			
		||||
                    double y = pos.getY() + 0.75;
 | 
			
		||||
                    double z = pos.getZ() + 0.5;
 | 
			
		||||
                    WorldUtil.dropItemStack( stack, getWorld(), x, y, z );
 | 
			
		||||
                }
 | 
			
		||||
                // Spawn the item in the world
 | 
			
		||||
                BlockPos pos = getPos();
 | 
			
		||||
                double x = pos.getX() + 0.5;
 | 
			
		||||
                double y = pos.getY() + 0.75;
 | 
			
		||||
                double z = pos.getZ() + 0.5;
 | 
			
		||||
                WorldUtil.dropItemStack( stack, getWorld(), x, y, z );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateAnim()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        int anim = 0;
 | 
			
		||||
        for( int i = 1; i < 7; i++ )
 | 
			
		||||
        {
 | 
			
		||||
            int anim = 0;
 | 
			
		||||
            for( int i = 1; i < 7; i++ )
 | 
			
		||||
            ItemStack stack = m_inventory.get( i );
 | 
			
		||||
            if( !stack.isEmpty() && isPaper( stack ) )
 | 
			
		||||
            {
 | 
			
		||||
                ItemStack stack = m_inventory.get( i );
 | 
			
		||||
                if( !stack.isEmpty() && isPaper( stack ) )
 | 
			
		||||
                {
 | 
			
		||||
                    anim += 1;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                anim += 1;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            for( int i = 7; i < 13; i++ )
 | 
			
		||||
            {
 | 
			
		||||
                ItemStack stack = m_inventory.get( i );
 | 
			
		||||
                if( !stack.isEmpty() && isPaper( stack ) )
 | 
			
		||||
                {
 | 
			
		||||
                    anim += 2;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            setAnim( anim );
 | 
			
		||||
        }
 | 
			
		||||
        for( int i = 7; i < 13; i++ )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack stack = m_inventory.get( i );
 | 
			
		||||
            if( !stack.isEmpty() && isPaper( stack ) )
 | 
			
		||||
            {
 | 
			
		||||
                anim += 2;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        setAnim( anim );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
 | 
			
		||||
    {
 | 
			
		||||
        // FIXME: Should be abstract, but we need this for Plethora compat. We'll
 | 
			
		||||
        //  be able to change this in a few versions as we implement both there.
 | 
			
		||||
        @SuppressWarnings( "deprecation" ) BlockPos pos = getPos();
 | 
			
		||||
        BlockPos pos = getPos();
 | 
			
		||||
        return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -71,8 +71,8 @@ public abstract class SpeakerPeripheral implements IPeripheral
 | 
			
		||||
    public String[] getMethodNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "playSound", // Plays sound at resourceLocator
 | 
			
		||||
            "playNote" // Plays note
 | 
			
		||||
            "playSound",
 | 
			
		||||
            "playNote",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,7 @@ public class PocketAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "pocket"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "pocket" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -46,7 +44,7 @@ public class PocketAPI implements ILuaAPI
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "equipBack",
 | 
			
		||||
            "unequipBack"
 | 
			
		||||
            "unequipBack",
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,9 +30,13 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler
 | 
			
		||||
    @Override
 | 
			
		||||
    public int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack currentStack, int slot, int limit )
 | 
			
		||||
    {
 | 
			
		||||
        ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false );
 | 
			
		||||
        int fuelToGive = getFuelPerItem( stack ) * stack.getCount();
 | 
			
		||||
        int fuelSpaceLeft = turtle.getFuelLimit() - turtle.getFuelLevel();
 | 
			
		||||
        int fuelPerItem = getFuelPerItem( turtle.getItemHandler().getStackInSlot( slot ) );
 | 
			
		||||
        int fuelItemLimit = (int) Math.ceil( fuelSpaceLeft / (double) fuelPerItem );
 | 
			
		||||
        if( limit > fuelItemLimit ) limit = fuelItemLimit;
 | 
			
		||||
 | 
			
		||||
        ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false );
 | 
			
		||||
        int fuelToGive = fuelPerItem * stack.getCount();
 | 
			
		||||
        // Store the replacement item in the inventory
 | 
			
		||||
        ItemStack replacementStack = stack.getItem().getContainerItem( stack );
 | 
			
		||||
        if( !replacementStack.isEmpty() )
 | 
			
		||||
 
 | 
			
		||||
@@ -45,9 +45,7 @@ public class TurtleAPI implements ILuaAPI
 | 
			
		||||
    @Override
 | 
			
		||||
    public String[] getNames()
 | 
			
		||||
    {
 | 
			
		||||
        return new String[] {
 | 
			
		||||
            "turtle"
 | 
			
		||||
        };
 | 
			
		||||
        return new String[] { "turtle" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -336,9 +334,11 @@ public class TurtleAPI implements ILuaAPI
 | 
			
		||||
                return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) );
 | 
			
		||||
            case 40: // inspectDown
 | 
			
		||||
                return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) );
 | 
			
		||||
            case 41:
 | 
			
		||||
            case 41: // getItemDetail
 | 
			
		||||
            {
 | 
			
		||||
                // getItemDetail
 | 
			
		||||
                // FIXME: There's a race condition here if the stack is being modified (mutating NBT, etc...)
 | 
			
		||||
                //  on another thread. The obvious solution is to move this into a command, but some programs rely
 | 
			
		||||
                //  on this having a 0-tick delay.
 | 
			
		||||
                int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() );
 | 
			
		||||
                ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
 | 
			
		||||
                if( stack.isEmpty() ) return new Object[] { null };
 | 
			
		||||
 
 | 
			
		||||
@@ -218,7 +218,7 @@ public class BlockTurtle extends BlockComputerBase
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public float getExplosionResistance( Entity exploder )
 | 
			
		||||
    {
 | 
			
		||||
        if( getFamily() == ComputerFamily.Advanced && (exploder instanceof EntityLivingBase || exploder instanceof EntityFireball) )
 | 
			
		||||
        if( getFamily() == ComputerFamily.Advanced || exploder instanceof EntityLivingBase || exploder instanceof EntityFireball )
 | 
			
		||||
        {
 | 
			
		||||
            return 2000;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -43,19 +43,16 @@ import net.minecraftforge.items.wrapper.InvWrapper;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
 | 
			
		||||
import static net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY;
 | 
			
		||||
 | 
			
		||||
public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory
 | 
			
		||||
{
 | 
			
		||||
    // Statics
 | 
			
		||||
 | 
			
		||||
    public static final int INVENTORY_SIZE = 16;
 | 
			
		||||
    public static final int INVENTORY_WIDTH = 4;
 | 
			
		||||
    public static final int INVENTORY_HEIGHT = 4;
 | 
			
		||||
 | 
			
		||||
    // Members
 | 
			
		||||
 | 
			
		||||
    enum MoveState
 | 
			
		||||
    {
 | 
			
		||||
        NOT_MOVED,
 | 
			
		||||
@@ -63,12 +60,12 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        MOVED
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private NonNullList<ItemStack> m_inventory;
 | 
			
		||||
    private NonNullList<ItemStack> m_previousInventory;
 | 
			
		||||
    private final NonNullList<ItemStack> m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
    private final NonNullList<ItemStack> m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
    private final IItemHandlerModifiable m_itemHandler = new InvWrapper( this );
 | 
			
		||||
    private boolean m_inventoryChanged;
 | 
			
		||||
    private TurtleBrain m_brain;
 | 
			
		||||
    private MoveState m_moveState;
 | 
			
		||||
    private boolean m_inventoryChanged = false;
 | 
			
		||||
    private TurtleBrain m_brain = new TurtleBrain( this );
 | 
			
		||||
    private MoveState m_moveState = MoveState.NOT_MOVED;
 | 
			
		||||
    private ComputerFamily m_family;
 | 
			
		||||
 | 
			
		||||
    public TileTurtle()
 | 
			
		||||
@@ -78,15 +75,10 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
 | 
			
		||||
    public TileTurtle( ComputerFamily 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;
 | 
			
		||||
        m_family = family;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean hasMoved()
 | 
			
		||||
    private boolean hasMoved()
 | 
			
		||||
    {
 | 
			
		||||
        return m_moveState == MoveState.MOVED;
 | 
			
		||||
    }
 | 
			
		||||
@@ -95,7 +87,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    protected ServerComputer createComputer( int instanceID, int id )
 | 
			
		||||
    {
 | 
			
		||||
        ServerComputer computer = new ServerComputer(
 | 
			
		||||
            getWorld(), id, m_label, instanceID, getFamily(),
 | 
			
		||||
            getWorld(), id, label, instanceID, getFamily(),
 | 
			
		||||
            ComputerCraft.terminalWidth_turtle, ComputerCraft.terminalHeight_turtle
 | 
			
		||||
        );
 | 
			
		||||
        computer.setPosition( getPos() );
 | 
			
		||||
@@ -224,18 +216,15 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    {
 | 
			
		||||
        super.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;
 | 
			
		||||
                for( int n = 0; n < getSizeInventory(); n++ )
 | 
			
		||||
                {
 | 
			
		||||
                    m_previousInventory.set( n, InventoryUtil.copyItem( getStackInSlot( n ) ) );
 | 
			
		||||
                }
 | 
			
		||||
            m_inventoryChanged = false;
 | 
			
		||||
            for( int n = 0; n < getSizeInventory(); n++ )
 | 
			
		||||
            {
 | 
			
		||||
                m_previousInventory.set( n, InventoryUtil.copyItem( getStackInSlot( n ) ) );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -270,8 +259,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
 | 
			
		||||
        // Read inventory
 | 
			
		||||
        NBTTagList nbttaglist = nbt.getTagList( "Items", Constants.NBT.TAG_COMPOUND );
 | 
			
		||||
        m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
        m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
 | 
			
		||||
        m_inventory.clear();
 | 
			
		||||
        m_previousInventory.clear();
 | 
			
		||||
        for( int i = 0; i < nbttaglist.tagCount(); i++ )
 | 
			
		||||
        {
 | 
			
		||||
            NBTTagCompound tag = nbttaglist.getCompoundTagAt( i );
 | 
			
		||||
@@ -317,12 +306,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        return hasPeripheralUpgradeOnSide( localSide );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected boolean isRedstoneBlockedOnSide( ComputerSide localSide )
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // IDirectionalTile
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -388,7 +371,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
        return m_family;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOwningPlayer( GameProfile player )
 | 
			
		||||
    void setOwningPlayer( GameProfile player )
 | 
			
		||||
    {
 | 
			
		||||
        m_brain.setOwningPlayer( player );
 | 
			
		||||
        markDirty();
 | 
			
		||||
@@ -416,109 +399,76 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack getStackInSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        if( slot >= 0 && slot < INVENTORY_SIZE )
 | 
			
		||||
        {
 | 
			
		||||
            synchronized( m_inventory )
 | 
			
		||||
            {
 | 
			
		||||
                return m_inventory.get( slot );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ItemStack.EMPTY;
 | 
			
		||||
        return slot >= 0 && slot < INVENTORY_SIZE ? m_inventory.get( slot ) : ItemStack.EMPTY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack removeStackFromSlot( int slot )
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack result = getStackInSlot( slot );
 | 
			
		||||
            setInventorySlotContents( slot, ItemStack.EMPTY );
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
        ItemStack result = getStackInSlot( slot );
 | 
			
		||||
        setInventorySlotContents( slot, ItemStack.EMPTY );
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    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 stack = getStackInSlot( slot );
 | 
			
		||||
            if( stack.isEmpty() )
 | 
			
		||||
            {
 | 
			
		||||
                return ItemStack.EMPTY;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if( stack.getCount() <= count )
 | 
			
		||||
            {
 | 
			
		||||
                setInventorySlotContents( slot, ItemStack.EMPTY );
 | 
			
		||||
                return stack;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ItemStack part = stack.splitStack( count );
 | 
			
		||||
            onInventoryDefinitelyChanged();
 | 
			
		||||
            return part;
 | 
			
		||||
        }
 | 
			
		||||
        ItemStack part = stack.splitStack( count );
 | 
			
		||||
        onInventoryDefinitelyChanged();
 | 
			
		||||
        return part;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    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 )
 | 
			
		||||
            {
 | 
			
		||||
                if( !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) )
 | 
			
		||||
                {
 | 
			
		||||
                    m_inventory.set( i, stack );
 | 
			
		||||
                    onInventoryDefinitelyChanged();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            m_inventory.set( i, stack );
 | 
			
		||||
            onInventoryDefinitelyChanged();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clear()
 | 
			
		||||
    {
 | 
			
		||||
        synchronized( m_inventory )
 | 
			
		||||
        boolean changed = false;
 | 
			
		||||
        for( int i = 0; i < INVENTORY_SIZE; i++ )
 | 
			
		||||
        {
 | 
			
		||||
            boolean changed = false;
 | 
			
		||||
            for( int i = 0; i < INVENTORY_SIZE; i++ )
 | 
			
		||||
            if( !m_inventory.get( i ).isEmpty() )
 | 
			
		||||
            {
 | 
			
		||||
                if( !m_inventory.get( i ).isEmpty() )
 | 
			
		||||
                {
 | 
			
		||||
                    m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
                    changed = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if( changed )
 | 
			
		||||
            {
 | 
			
		||||
                onInventoryDefinitelyChanged();
 | 
			
		||||
                m_inventory.set( i, ItemStack.EMPTY );
 | 
			
		||||
                changed = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if( changed ) onInventoryDefinitelyChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void 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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -580,8 +530,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
 | 
			
		||||
    public void transferStateFrom( TileTurtle copy )
 | 
			
		||||
    {
 | 
			
		||||
        super.transferStateFrom( copy );
 | 
			
		||||
        m_inventory = copy.m_inventory;
 | 
			
		||||
        m_previousInventory = copy.m_previousInventory;
 | 
			
		||||
        Collections.copy( m_inventory, copy.m_inventory );
 | 
			
		||||
        Collections.copy( m_previousInventory, copy.m_previousInventory );
 | 
			
		||||
        m_inventoryChanged = copy.m_inventoryChanged;
 | 
			
		||||
        m_brain = copy.m_brain;
 | 
			
		||||
        m_brain.setOwner( this );
 | 
			
		||||
 
 | 
			
		||||
@@ -908,14 +908,14 @@ public class TurtleBrain implements ITurtleAccess
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                computer.queueEvent( "turtle_response", new Object[] {
 | 
			
		||||
                    callbackID, true
 | 
			
		||||
                    callbackID, true,
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            computer.queueEvent( "turtle_response", new Object[] {
 | 
			
		||||
                callbackID, false, result != null ? result.getErrorMessage() : null
 | 
			
		||||
                callbackID, false, result != null ? result.getErrorMessage() : null,
 | 
			
		||||
            } );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -53,19 +53,15 @@ public class TurtleCompareCommand implements ITurtleCommand
 | 
			
		||||
            Block lookAtBlock = lookAtState.getBlock();
 | 
			
		||||
            if( !lookAtBlock.isAir( lookAtState, world, newPosition ) )
 | 
			
		||||
            {
 | 
			
		||||
                // Try createStackedBlock first
 | 
			
		||||
                // Try getSilkTouchDrop first
 | 
			
		||||
                if( !lookAtBlock.hasTileEntity( lookAtState ) )
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        Method method = ReflectionHelper.findMethod(
 | 
			
		||||
                            Block.class,
 | 
			
		||||
                            "func_180643_i", "getSilkTouchDrop",
 | 
			
		||||
                            IBlockState.class
 | 
			
		||||
                        );
 | 
			
		||||
                        Method method = ReflectionHelper.findMethod( Block.class, "getSilkTouchDrop", "func_180643_i", IBlockState.class );
 | 
			
		||||
                        lookAtStack = (ItemStack) method.invoke( lookAtBlock, lookAtState );
 | 
			
		||||
                    }
 | 
			
		||||
                    catch( ReflectiveOperationException ignored )
 | 
			
		||||
                    catch( ReflectiveOperationException | RuntimeException ignored )
 | 
			
		||||
                    {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,6 @@ public class TurtlePlaceCommand implements ITurtleCommand
 | 
			
		||||
 | 
			
		||||
        // Remember old block
 | 
			
		||||
        EnumFacing direction = m_direction.toWorldDir( turtle );
 | 
			
		||||
        World world = turtle.getWorld();
 | 
			
		||||
        BlockPos coordinates = turtle.getPosition().offset( direction );
 | 
			
		||||
 | 
			
		||||
        // Create a fake player, and orient it appropriately
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@ public class ContainerTurtle extends Container implements IContainerComputer
 | 
			
		||||
{
 | 
			
		||||
    private static final int PROGRESS_ID_SELECTED_SLOT = 0;
 | 
			
		||||
 | 
			
		||||
    public final int m_playerInvStartY;
 | 
			
		||||
    public final int m_turtleInvStartX;
 | 
			
		||||
    public final int playerInvStartY;
 | 
			
		||||
    public final int turtleInvStartX;
 | 
			
		||||
 | 
			
		||||
    private final ITurtleAccess m_turtle;
 | 
			
		||||
    private IComputer m_computer;
 | 
			
		||||
@@ -36,8 +36,8 @@ public class ContainerTurtle extends Container implements IContainerComputer
 | 
			
		||||
 | 
			
		||||
    protected ContainerTurtle( IInventory playerInventory, ITurtleAccess turtle, int playerInvStartY, int turtleInvStartX )
 | 
			
		||||
    {
 | 
			
		||||
        m_playerInvStartY = playerInvStartY;
 | 
			
		||||
        m_turtleInvStartX = turtleInvStartX;
 | 
			
		||||
        this.playerInvStartY = playerInvStartY;
 | 
			
		||||
        this.turtleInvStartX = turtleInvStartX;
 | 
			
		||||
 | 
			
		||||
        m_turtle = turtle;
 | 
			
		||||
        m_selectedSlot = m_turtle.getWorld().isRemote ? 0 : m_turtle.getSelectedSlot();
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,10 @@ public class ItemTurtleLegacy extends ItemTurtleBase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ResourceLocation getOverlay( @Nonnull ItemStack stack ) { return null; }
 | 
			
		||||
    public ResourceLocation getOverlay( @Nonnull ItemStack stack )
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getFuelLevel( @Nonnull ItemStack stack )
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ public class TurtleHoe extends TurtleTool
 | 
			
		||||
    {
 | 
			
		||||
        if( verb == TurtleVerb.Dig )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack hoe = m_item.copy();
 | 
			
		||||
            ItemStack hoe = item.copy();
 | 
			
		||||
            ItemStack remainder = TurtlePlaceCommand.deploy( hoe, turtle, direction, null, null );
 | 
			
		||||
            if( remainder != hoe )
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ public class TurtleShovel extends TurtleTool
 | 
			
		||||
    {
 | 
			
		||||
        if( verb == TurtleVerb.Dig )
 | 
			
		||||
        {
 | 
			
		||||
            ItemStack shovel = m_item.copy();
 | 
			
		||||
            ItemStack shovel = item.copy();
 | 
			
		||||
            ItemStack remainder = TurtlePlaceCommand.deploy( shovel, turtle, direction, null, null );
 | 
			
		||||
            if( remainder != shovel )
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -48,18 +48,18 @@ import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
public class TurtleTool extends AbstractTurtleUpgrade
 | 
			
		||||
{
 | 
			
		||||
    protected ItemStack m_item;
 | 
			
		||||
    protected final ItemStack item;
 | 
			
		||||
 | 
			
		||||
    public TurtleTool( ResourceLocation id, int legacyID, String adjective, Item item )
 | 
			
		||||
    {
 | 
			
		||||
        super( id, legacyID, TurtleUpgradeType.Tool, adjective, item );
 | 
			
		||||
        m_item = new ItemStack( item, 1, 0 );
 | 
			
		||||
        this.item = new ItemStack( item, 1, 0 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TurtleTool( ResourceLocation id, int legacyID, Item item )
 | 
			
		||||
    {
 | 
			
		||||
        super( id, legacyID, TurtleUpgradeType.Tool, item );
 | 
			
		||||
        m_item = new ItemStack( item, 1, 0 );
 | 
			
		||||
        this.item = new ItemStack( item, 1, 0 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
@@ -76,7 +76,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
 | 
			
		||||
        );
 | 
			
		||||
        Minecraft mc = Minecraft.getMinecraft();
 | 
			
		||||
        return Pair.of(
 | 
			
		||||
            mc.getRenderItem().getItemModelMesher().getItemModel( m_item ),
 | 
			
		||||
            mc.getRenderItem().getItemModelMesher().getItemModel( item ),
 | 
			
		||||
            transform
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -124,7 +124,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
 | 
			
		||||
        if( hit != null )
 | 
			
		||||
        {
 | 
			
		||||
            // Load up the turtle's inventory
 | 
			
		||||
            ItemStack stackCopy = m_item.copy();
 | 
			
		||||
            ItemStack stackCopy = item.copy();
 | 
			
		||||
            turtlePlayer.loadInventory( stackCopy );
 | 
			
		||||
 | 
			
		||||
            Entity hitEntity = hit.getKey();
 | 
			
		||||
@@ -202,7 +202,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
 | 
			
		||||
        IBlockState state = world.getBlockState( blockPosition );
 | 
			
		||||
 | 
			
		||||
        TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction );
 | 
			
		||||
        turtlePlayer.loadInventory( m_item.copy() );
 | 
			
		||||
        turtlePlayer.loadInventory( item.copy() );
 | 
			
		||||
 | 
			
		||||
        if( ComputerCraft.turtlesObeyBlockProtection )
 | 
			
		||||
        {
 | 
			
		||||
@@ -246,7 +246,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
 | 
			
		||||
        boolean canHarvest = state.getBlock().canHarvestBlock( world, blockPosition, turtlePlayer );
 | 
			
		||||
        boolean canBreak = state.getBlock().removedByPlayer( state, world, blockPosition, turtlePlayer, canHarvest );
 | 
			
		||||
        if( canBreak ) state.getBlock().onPlayerDestroy( world, blockPosition, state );
 | 
			
		||||
        if( canHarvest )
 | 
			
		||||
        if( canHarvest && canBreak )
 | 
			
		||||
        {
 | 
			
		||||
            state.getBlock().harvestBlock( world, turtlePlayer, blockPosition, state, tile, turtlePlayer.getHeldItemMainhand() );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ public final class ColourUtils
 | 
			
		||||
        "dyeBlack", "dyeRed", "dyeGreen", "dyeBrown",
 | 
			
		||||
        "dyeBlue", "dyePurple", "dyeCyan", "dyeLightGray",
 | 
			
		||||
        "dyeGray", "dyePink", "dyeLime", "dyeYellow",
 | 
			
		||||
        "dyeLightBlue", "dyeMagenta", "dyeOrange", "dyeWhite"
 | 
			
		||||
        "dyeLightBlue", "dyeMagenta", "dyeOrange", "dyeWhite",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private static int[] ids;
 | 
			
		||||
 
 | 
			
		||||
@@ -72,12 +72,11 @@ public class Palette
 | 
			
		||||
 | 
			
		||||
    public static double[] decodeRGB8( int rgb )
 | 
			
		||||
    {
 | 
			
		||||
        return new double[]
 | 
			
		||||
            {
 | 
			
		||||
                ((rgb >> 16) & 0xFF) / 255.0f,
 | 
			
		||||
                ((rgb >> 8) & 0xFF) / 255.0f,
 | 
			
		||||
                (rgb & 0xFF) / 255.0f
 | 
			
		||||
            };
 | 
			
		||||
        return new double[] {
 | 
			
		||||
            ((rgb >> 16) & 0xFF) / 255.0f,
 | 
			
		||||
            ((rgb >> 8) & 0xFF) / 255.0f,
 | 
			
		||||
            (rgb & 0xFF) / 255.0f,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public NBTTagCompound writeToNBT( NBTTagCompound nbt )
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										195
									
								
								src/main/resources/assets/computercraft/lang/zh_cn.lang
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/main/resources/assets/computercraft/lang/zh_cn.lang
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,195 @@
 | 
			
		||||
itemGroup.computercraft=CC: Tweaked
 | 
			
		||||
 | 
			
		||||
tile.computercraft:computer.name=计算机
 | 
			
		||||
tile.computercraft:advanced_computer.name=高级计算机
 | 
			
		||||
tile.computercraft:drive.name=磁盘驱动器
 | 
			
		||||
tile.computercraft:printer.name=打印机
 | 
			
		||||
tile.computercraft:monitor.name=显示器
 | 
			
		||||
tile.computercraft:advanced_monitor.name=高级显示器
 | 
			
		||||
tile.computercraft:wireless_modem.name=无线调制解调器
 | 
			
		||||
tile.computercraft:wired_modem.name=有线调制解调器
 | 
			
		||||
tile.computercraft:cable.name=网络电缆
 | 
			
		||||
tile.computercraft:command_computer.name=命令电脑
 | 
			
		||||
tile.computercraft:advanced_modem.name=末影调制解调器
 | 
			
		||||
tile.computercraft:speaker.name=扬声器
 | 
			
		||||
 | 
			
		||||
tile.computercraft:turtle.name=海龟
 | 
			
		||||
tile.computercraft:turtle.upgraded.name=%s海龟
 | 
			
		||||
tile.computercraft:turtle.upgraded_twice.name=%s%s海龟
 | 
			
		||||
tile.computercraft:advanced_turtle.name=高级海龟
 | 
			
		||||
tile.computercraft:advanced_turtle.upgraded.name=高级%s海龟
 | 
			
		||||
tile.computercraft:advanced_turtle.upgraded_twice.name=高级%s%s海龟
 | 
			
		||||
 | 
			
		||||
item.computercraft:disk.name=软盘
 | 
			
		||||
item.computercraft:treasure_disk.name=软盘
 | 
			
		||||
item.computercraft:page.name=打印纸
 | 
			
		||||
item.computercraft:pages.name=打印纸
 | 
			
		||||
item.computercraft:book.name=打印书
 | 
			
		||||
 | 
			
		||||
item.computercraft:pocket_computer.name=手提计算机
 | 
			
		||||
item.computercraft:pocket_computer.upgraded.name=%s手提计算机
 | 
			
		||||
item.computercraft:advanced_pocket_computer.name=高级手提计算机
 | 
			
		||||
item.computercraft:advanced_pocket_computer.upgraded.name=高级%s手提计算机
 | 
			
		||||
 | 
			
		||||
upgrade.minecraft:diamond_sword.adjective=战斗
 | 
			
		||||
upgrade.minecraft:diamond_shovel.adjective=挖掘
 | 
			
		||||
upgrade.minecraft:diamond_pickaxe.adjective=采掘
 | 
			
		||||
upgrade.minecraft:diamond_axe.adjective=伐木
 | 
			
		||||
upgrade.minecraft:diamond_hoe.adjective=耕种
 | 
			
		||||
upgrade.computercraft:wireless_modem.adjective=无线
 | 
			
		||||
upgrade.minecraft:crafting_table.adjective=合成
 | 
			
		||||
upgrade.computercraft:advanced_modem.adjective=末影
 | 
			
		||||
upgrade.computercraft:speaker.adjective=喧闹
 | 
			
		||||
 | 
			
		||||
chat.computercraft.wired_modem.peripheral_connected=Peripheral "%s"连接到网络
 | 
			
		||||
chat.computercraft.wired_modem.peripheral_disconnected=Peripheral "%s"与网络断开连接
 | 
			
		||||
 | 
			
		||||
# Command descriptions, usage and any additional messages.
 | 
			
		||||
commands.computercraft.synopsis=各种控制计算机的命令.
 | 
			
		||||
commands.computercraft.desc=/computercraft命令提供各种调试和管理工具,用于控制和与计算机交互.
 | 
			
		||||
 | 
			
		||||
commands.computercraft.help.synopsis=为特定的命令提供帮助
 | 
			
		||||
commands.computercraft.help.desc=
 | 
			
		||||
commands.computercraft.help.usage=[command]
 | 
			
		||||
commands.computercraft.help.no_children=%s没有子命令
 | 
			
		||||
commands.computercraft.help.no_command=没有这样的命令'%s'
 | 
			
		||||
 | 
			
		||||
commands.computercraft.dump.synopsis=显示计算机的状态.
 | 
			
		||||
commands.computercraft.dump.desc=显示所有计算机的状态或某台计算机的特定信息. 你可以指定计算机的实例id (例如. 123), 计算机id (例如. #123)或标签(例如. "@My Computer").
 | 
			
		||||
commands.computercraft.dump.usage=[id]
 | 
			
		||||
commands.computercraft.dump.action=查看有关此计算机的更多信息
 | 
			
		||||
 | 
			
		||||
commands.computercraft.shutdown.synopsis=远程关闭计算机.
 | 
			
		||||
commands.computercraft.shutdown.desc=关闭列出的计算机或全部计算机(如果未指定). 你可以指定计算机的实例id (例如. 123), 计算机id (例如. #123)或标签(例如. "@My Computer").
 | 
			
		||||
commands.computercraft.shutdown.usage=[ids...]
 | 
			
		||||
commands.computercraft.shutdown.done=关闭%s/%s计算机
 | 
			
		||||
 | 
			
		||||
commands.computercraft.turn_on.synopsis=远程打开计算机.
 | 
			
		||||
commands.computercraft.turn_on.desc=打开列出的计算机. 你可以指定计算机的实例id (例如. 123), 计算机id (例如. #123)或标签(例如. "@My Computer").
 | 
			
		||||
commands.computercraft.turn_on.usage=[ids...]
 | 
			
		||||
commands.computercraft.turn_on.done=打开%s/%s计算机
 | 
			
		||||
 | 
			
		||||
commands.computercraft.tp.synopsis=传送到特定的计算机.
 | 
			
		||||
commands.computercraft.tp.desc=传送到计算机的位置. 你可以指定计算机的实例id (例如. 123)或计算机id (例如. #123).
 | 
			
		||||
commands.computercraft.tp.usage=<id>
 | 
			
		||||
commands.computercraft.tp.action=传送到这台电脑
 | 
			
		||||
commands.computercraft.tp.not_entity=无法为非玩家打开终端
 | 
			
		||||
commands.computercraft.tp.not_there=无法在世界上定位电脑
 | 
			
		||||
 | 
			
		||||
commands.computercraft.view.synopsis=查看计算机的终端.
 | 
			
		||||
commands.computercraft.view.desc=打开计算机的终端,允许远程控制计算机. 这不提供对海龟库存的访问. 你可以指定计算机的实例id (例如. 123)或计算机id (例如. #123).
 | 
			
		||||
commands.computercraft.view.usage=<id>
 | 
			
		||||
commands.computercraft.view.action=查看此计算机
 | 
			
		||||
commands.computercraft.view.not_player=无法为非玩家打开终端
 | 
			
		||||
 | 
			
		||||
commands.computercraft.track.synopsis=跟踪计算机的执行时间.
 | 
			
		||||
commands.computercraft.track.desc=跟踪计算机执行的时间以及它们处理的事件数. 这以/forge track类似的方式呈现信息,可用于诊断滞后.
 | 
			
		||||
 | 
			
		||||
commands.computercraft.track.start.synopsis=开始跟踪所有计算机
 | 
			
		||||
commands.computercraft.track.start.desc=开始跟踪所有计算机的执行时间和事件计数. 这将放弃先前运行的结果.
 | 
			
		||||
commands.computercraft.track.start.usage=
 | 
			
		||||
commands.computercraft.track.start.stop=运行%s以停止跟踪并查看结果
 | 
			
		||||
 | 
			
		||||
commands.computercraft.track.stop.synopsis=停止跟踪所有计算机
 | 
			
		||||
commands.computercraft.track.stop.desc=停止跟踪所有计算机的事件和执行时间
 | 
			
		||||
commands.computercraft.track.stop.usage=
 | 
			
		||||
commands.computercraft.track.stop.action=点击停止跟踪
 | 
			
		||||
commands.computercraft.track.stop.not_enabled=目前没有跟踪计算机
 | 
			
		||||
 | 
			
		||||
commands.computercraft.track.dump.synopsis=输出最新的跟踪结果
 | 
			
		||||
commands.computercraft.track.dump.desc=输出计算机跟踪的最新结果.
 | 
			
		||||
commands.computercraft.track.dump.usage=[kind]
 | 
			
		||||
commands.computercraft.track.dump.no_timings=没有时间可用
 | 
			
		||||
commands.computercraft.track.dump.no_field=未知字节'%s'
 | 
			
		||||
commands.computercraft.track.dump.computer=计算器
 | 
			
		||||
 | 
			
		||||
commands.computercraft.reload.synopsis=重新加载ComputerCraft配置文件
 | 
			
		||||
commands.computercraft.reload.desc=重新加载ComputerCraft配置文件
 | 
			
		||||
commands.computercraft.reload.usage=
 | 
			
		||||
commands.computercraft.reload.done=重新加载配置
 | 
			
		||||
 | 
			
		||||
commands.computercraft.queue.synopsis=将computer_command事件发送到命令计算机
 | 
			
		||||
commands.computercraft.queue.desc=发送computer_command事件到命令计算机,并传递其他参数. 这主要是为地图制作者设计的, 作为/trigger更加计算机友好的版本. 任何玩家都可以运行命令, 这很可能是通过文本组件的点击事件完成的.
 | 
			
		||||
commands.computercraft.queue.usage=<id> [args...]
 | 
			
		||||
 | 
			
		||||
commands.computercraft.generic.no_position=<无位置>
 | 
			
		||||
commands.computercraft.generic.position=%s, %s, %s
 | 
			
		||||
commands.computercraft.generic.yes=Y
 | 
			
		||||
commands.computercraft.generic.no=N
 | 
			
		||||
commands.computercraft.generic.exception=未处理的异常(%s)
 | 
			
		||||
commands.computercraft.generic.additional_rows=%d额外的行…
 | 
			
		||||
 | 
			
		||||
commands.computercraft.argument.no_matching=没有计算机匹配'%s'
 | 
			
		||||
commands.computercraft.argument.many_matching=多台计算机匹配'%s' (实例%s)
 | 
			
		||||
commands.computercraft.argument.not_number='%s'不是一个数字
 | 
			
		||||
 | 
			
		||||
# Names for the various tracking fields.
 | 
			
		||||
tracking_field.computercraft.tasks.name=任务
 | 
			
		||||
tracking_field.computercraft.total.name=总计时间
 | 
			
		||||
tracking_field.computercraft.average.name=平均时间
 | 
			
		||||
tracking_field.computercraft.max.name=最大时间
 | 
			
		||||
 | 
			
		||||
tracking_field.computercraft.server_count.name=服务器任务计数
 | 
			
		||||
tracking_field.computercraft.server_time.name=服务器任务时间
 | 
			
		||||
 | 
			
		||||
tracking_field.computercraft.peripheral.name=外围设备呼叫
 | 
			
		||||
tracking_field.computercraft.fs.name=文件系统操作
 | 
			
		||||
tracking_field.computercraft.turtle.name=海龟行动
 | 
			
		||||
 | 
			
		||||
tracking_field.computercraft.http.name=HTTP需求
 | 
			
		||||
tracking_field.computercraft.http_upload.name=HTTP上传
 | 
			
		||||
tracking_field.computercraft.http_download.name=HTTP下载
 | 
			
		||||
 | 
			
		||||
tracking_field.computercraft.websocket_incoming.name=Websocket传入
 | 
			
		||||
tracking_field.computercraft.websocket_outgoing.name=Websocket传出
 | 
			
		||||
 | 
			
		||||
tracking_field.computercraft.coroutines_created.name=协同创建
 | 
			
		||||
tracking_field.computercraft.coroutines_dead.name=协同处理
 | 
			
		||||
 | 
			
		||||
# Misc tooltips
 | 
			
		||||
gui.computercraft.tooltip.copy=复制到剪贴板
 | 
			
		||||
gui.computercraft.tooltip.computer_id=(计算机ID: %s)
 | 
			
		||||
gui.computercraft.tooltip.disk_id=(磁盘ID: %s)
 | 
			
		||||
 | 
			
		||||
# Config options
 | 
			
		||||
gui.computercraft:config.computer_space_limit=计算机空间限制(字节)
 | 
			
		||||
gui.computercraft:config.floppy_space_limit=软盘空间限制(字节)
 | 
			
		||||
gui.computercraft:config.maximum_open_files=每台计算机打开的最大文件数
 | 
			
		||||
gui.computercraft:config.disable_lua51_features=禁用Lua 5.1功能
 | 
			
		||||
gui.computercraft:config.default_computer_settings=默认计算机设置
 | 
			
		||||
gui.computercraft:config.debug_enabled=启用debug库
 | 
			
		||||
gui.computercraft:config.log_computer_errors=记录计算机错误
 | 
			
		||||
 | 
			
		||||
gui.computercraft:config.execution=执行
 | 
			
		||||
gui.computercraft:config.execution.computer_threads=计算机线程数
 | 
			
		||||
gui.computercraft:config.execution.max_main_global_time=服务器全局tick时间限制
 | 
			
		||||
gui.computercraft:config.execution.max_main_computer_time=服务器计算机tick时间限制
 | 
			
		||||
 | 
			
		||||
gui.computercraft:config.http=HTTP
 | 
			
		||||
gui.computercraft:config.http.enabled=启用HTTP API
 | 
			
		||||
gui.computercraft:config.http.websocket_enabled=启用websockets
 | 
			
		||||
gui.computercraft:config.http.whitelist=HTTP白名单
 | 
			
		||||
gui.computercraft:config.http.blacklist=HTTP黑名单
 | 
			
		||||
 | 
			
		||||
gui.computercraft:config.http.timeout=Timeout
 | 
			
		||||
gui.computercraft:config.http.max_requests=最大并发请求数
 | 
			
		||||
gui.computercraft:config.http.max_download=最大响应数据大小
 | 
			
		||||
gui.computercraft:config.http.max_upload=最大请求数据大小
 | 
			
		||||
gui.computercraft:config.http.max_websockets=最大并发websockets数
 | 
			
		||||
gui.computercraft:config.http.max_websocket_message=最大websockets消息大小
 | 
			
		||||
 | 
			
		||||
gui.computercraft:config.peripheral=外围设备
 | 
			
		||||
gui.computercraft:config.peripheral.command_block_enabled=启用命令方块外设
 | 
			
		||||
gui.computercraft:config.peripheral.modem_range=调制解调器范围(默认)
 | 
			
		||||
gui.computercraft:config.peripheral.modem_high_altitude_range=调制解调器范围(高海拔)
 | 
			
		||||
gui.computercraft:config.peripheral.modem_range_during_storm=调制解调器范围(恶劣天气)
 | 
			
		||||
gui.computercraft:config.peripheral.modem_high_altitude_range_during_storm=调制解调器范围(高海拔, 恶劣天气)
 | 
			
		||||
gui.computercraft:config.peripheral.max_notes_per_tick=计算机一次可以播放的最大音符数量
 | 
			
		||||
 | 
			
		||||
gui.computercraft:config.turtle=海龟
 | 
			
		||||
gui.computercraft:config.turtle.need_fuel=启用燃料
 | 
			
		||||
gui.computercraft:config.turtle.normal_fuel_limit=海龟燃料限制
 | 
			
		||||
gui.computercraft:config.turtle.advanced_fuel_limit=高级海龟燃料限制
 | 
			
		||||
gui.computercraft:config.turtle.obey_block_protection=海龟服从方块保护
 | 
			
		||||
gui.computercraft:config.turtle.can_push=海龟可以推动实体
 | 
			
		||||
gui.computercraft:config.turtle.disabled_actions=禁用海龟动作
 | 
			
		||||
@@ -1,5 +1,19 @@
 | 
			
		||||
-- Load in expect from the module path.
 | 
			
		||||
--
 | 
			
		||||
-- Ideally we'd use require, but that is part of the shell, and so is not
 | 
			
		||||
-- available to the BIOS or any APIs. All APIs load this using dofile, but that
 | 
			
		||||
-- has not been defined at this point.
 | 
			
		||||
local expect
 | 
			
		||||
 | 
			
		||||
do
 | 
			
		||||
    local h = fs.open("rom/modules/main/cc/expect.lua", "r")
 | 
			
		||||
    local f, err = loadstring(h.readAll(), "@expect.lua")
 | 
			
		||||
    h.close()
 | 
			
		||||
 | 
			
		||||
    if not f then error(err) end
 | 
			
		||||
    expect = f().expect
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local nativegetfenv = getfenv
 | 
			
		||||
if _VERSION == "Lua 5.1" then
 | 
			
		||||
    -- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
 | 
			
		||||
    local type = type
 | 
			
		||||
@@ -20,18 +34,11 @@ if _VERSION == "Lua 5.1" then
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function load( x, name, mode, env )
 | 
			
		||||
        if type( x ) ~= "string" and type( x ) ~= "function" then
 | 
			
		||||
            error( "bad argument #1 (expected string or function, got " .. type( x ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if name ~= nil and type( name ) ~= "string" then
 | 
			
		||||
            error( "bad argument #2 (expected string, got " .. type( name ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if mode ~= nil and type( mode ) ~= "string" then
 | 
			
		||||
            error( "bad argument #3 (expected string, got " .. type( mode ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if env ~= nil and type( env) ~= "table" then
 | 
			
		||||
            error( "bad argument #4 (expected table, got " .. type( env ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        expect(1, x, "function", "string")
 | 
			
		||||
        expect(2, name, "string", "nil")
 | 
			
		||||
        expect(3, mode, "string", "nil")
 | 
			
		||||
        expect(4, env, "table", "nil")
 | 
			
		||||
 | 
			
		||||
        local ok, p1, p2 = pcall( function()
 | 
			
		||||
            if type(x) == "string" then
 | 
			
		||||
                local result, err = nativeloadstring( x, name )
 | 
			
		||||
@@ -76,10 +83,9 @@ if _VERSION == "Lua 5.1" then
 | 
			
		||||
        math.log10 = nil
 | 
			
		||||
        table.maxn = nil
 | 
			
		||||
    else
 | 
			
		||||
        loadstring = function(string, chunkname) return nativeloadstring(string, prefix( chunkname ))
 | 
			
		||||
        loadstring = function(string, chunkname) return nativeloadstring(string, prefix( chunkname )) end
 | 
			
		||||
 | 
			
		||||
        -- Inject a stub for the old bit library
 | 
			
		||||
        end
 | 
			
		||||
        _G.bit = {
 | 
			
		||||
            bnot = bit32.bnot,
 | 
			
		||||
            band = bit32.band,
 | 
			
		||||
@@ -175,9 +181,7 @@ end
 | 
			
		||||
 | 
			
		||||
-- Install globals
 | 
			
		||||
function sleep( nTime )
 | 
			
		||||
    if nTime ~= nil and type( nTime ) ~= "number" then
 | 
			
		||||
        error( "bad argument #1 (expected number, got " .. type( nTime ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, nTime, "number", "nil")
 | 
			
		||||
    local timer = os.startTimer( nTime or 0 )
 | 
			
		||||
    repeat
 | 
			
		||||
        local sEvent, param = os.pullEvent( "timer" )
 | 
			
		||||
@@ -185,9 +189,7 @@ function sleep( nTime )
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function write( sText )
 | 
			
		||||
    if type( sText ) ~= "string" and type( sText ) ~= "number" then
 | 
			
		||||
        error( "bad argument #1 (expected string or number, got " .. type( sText ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, sText, "string", "number")
 | 
			
		||||
 | 
			
		||||
    local w,h = term.getSize()
 | 
			
		||||
    local x,y = term.getCursorPos()
 | 
			
		||||
@@ -275,18 +277,11 @@ function printError( ... )
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
 | 
			
		||||
    if _sReplaceChar ~= nil and type( _sReplaceChar ) ~= "string" then
 | 
			
		||||
        error( "bad argument #1 (expected string, got " .. type( _sReplaceChar ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if _tHistory ~= nil and type( _tHistory ) ~= "table" then
 | 
			
		||||
        error( "bad argument #2 (expected table, got " .. type( _tHistory ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if _fnComplete ~= nil and type( _fnComplete ) ~= "function" then
 | 
			
		||||
        error( "bad argument #3 (expected function, got " .. type( _fnComplete ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if _sDefault ~= nil and type( _sDefault ) ~= "string" then
 | 
			
		||||
        error( "bad argument #4 (expected string, got " .. type( _sDefault ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, _sReplaceChar, "string", "nil")
 | 
			
		||||
    expect(2, _tHistory, "table", "nil")
 | 
			
		||||
    expect(3, _fnComplete, "function", "nil")
 | 
			
		||||
    expect(4, _sDefault, "string", "nil")
 | 
			
		||||
 | 
			
		||||
    term.setCursorBlink( true )
 | 
			
		||||
 | 
			
		||||
    local sLine
 | 
			
		||||
@@ -544,27 +539,28 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
 | 
			
		||||
    return sLine
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
loadfile = function( _sFile, _tEnv )
 | 
			
		||||
    if type( _sFile ) ~= "string" then
 | 
			
		||||
        error( "bad argument #1 (expected string, got " .. type( _sFile ) .. ")", 2 )
 | 
			
		||||
function loadfile( filename, mode, env )
 | 
			
		||||
    -- Support the previous `loadfile(filename, env)` form instead.
 | 
			
		||||
    if type(mode) == "table" and env == nil then
 | 
			
		||||
        mode, env = nil, mode
 | 
			
		||||
    end
 | 
			
		||||
    if _tEnv ~= nil and type( _tEnv ) ~= "table" then
 | 
			
		||||
        error( "bad argument #2 (expected table, got " .. type( _tEnv ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    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
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
dofile = function( _sFile )
 | 
			
		||||
    if type( _sFile ) ~= "string" then
 | 
			
		||||
        error( "bad argument #1 (expected string, got " .. type( _sFile ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    local fnFile, e = loadfile( _sFile, _G )
 | 
			
		||||
function dofile( _sFile )
 | 
			
		||||
    expect(1, _sFile, "string")
 | 
			
		||||
 | 
			
		||||
    local fnFile, e = loadfile( _sFile, nil, _G )
 | 
			
		||||
    if fnFile then
 | 
			
		||||
        return fnFile()
 | 
			
		||||
    else
 | 
			
		||||
@@ -574,16 +570,13 @@ end
 | 
			
		||||
 | 
			
		||||
-- Install the rest of the OS api
 | 
			
		||||
function os.run( _tEnv, _sPath, ... )
 | 
			
		||||
    if type( _tEnv ) ~= "table" then
 | 
			
		||||
        error( "bad argument #1 (expected table, got " .. type( _tEnv ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if type( _sPath ) ~= "string" then
 | 
			
		||||
        error( "bad argument #2 (expected string, got " .. type( _sPath ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, _tEnv, "table")
 | 
			
		||||
    expect(2, _sPath, "string")
 | 
			
		||||
 | 
			
		||||
    local tArgs = table.pack( ... )
 | 
			
		||||
    local tEnv = _tEnv
 | 
			
		||||
    setmetatable( tEnv, { __index = _G } )
 | 
			
		||||
    local fnFile, err = loadfile( _sPath, tEnv )
 | 
			
		||||
    local fnFile, err = loadfile( _sPath, nil, tEnv )
 | 
			
		||||
    if fnFile then
 | 
			
		||||
        local ok, err = pcall( function()
 | 
			
		||||
            fnFile( table.unpack( tArgs, 1, tArgs.n ) )
 | 
			
		||||
@@ -604,9 +597,7 @@ end
 | 
			
		||||
 | 
			
		||||
local tAPIsLoading = {}
 | 
			
		||||
function os.loadAPI( _sPath )
 | 
			
		||||
    if type( _sPath ) ~= "string" then
 | 
			
		||||
        error( "bad argument #1 (expected string, got " .. type( _sPath ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, _sPath, "string")
 | 
			
		||||
    local sName = fs.getName( _sPath )
 | 
			
		||||
    if sName:sub(-4) == ".lua" then
 | 
			
		||||
        sName = sName:sub(1,-5)
 | 
			
		||||
@@ -619,7 +610,7 @@ function os.loadAPI( _sPath )
 | 
			
		||||
 | 
			
		||||
    local tEnv = {}
 | 
			
		||||
    setmetatable( tEnv, { __index = _G } )
 | 
			
		||||
    local fnAPI, err = loadfile( _sPath, tEnv )
 | 
			
		||||
    local fnAPI, err = loadfile( _sPath, nil, tEnv )
 | 
			
		||||
    if fnAPI then
 | 
			
		||||
        local ok, err = pcall( fnAPI )
 | 
			
		||||
        if not ok then
 | 
			
		||||
@@ -644,9 +635,7 @@ function os.loadAPI( _sPath )
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function os.unloadAPI( _sName )
 | 
			
		||||
    if type( _sName ) ~= "string" then
 | 
			
		||||
        error( "bad argument #1 (expected string, got " .. type( _sName ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, _sName, "string")
 | 
			
		||||
    if _sName ~= "_G" and type(_G[_sName]) == "table" then
 | 
			
		||||
        _G[_sName] = nil
 | 
			
		||||
    end
 | 
			
		||||
@@ -692,9 +681,11 @@ if http then
 | 
			
		||||
 | 
			
		||||
    local function checkOptions( options, body )
 | 
			
		||||
        checkKey( options, "url", "string")
 | 
			
		||||
        if body == false
 | 
			
		||||
        then checkKey( options, "body", "nil" )
 | 
			
		||||
        else checkKey( options, "body", "string", not body ) end
 | 
			
		||||
        if body == false then
 | 
			
		||||
          checkKey( options, "body", "nil" )
 | 
			
		||||
        else
 | 
			
		||||
          checkKey( options, "body", "string", not body )
 | 
			
		||||
        end
 | 
			
		||||
        checkKey( options, "headers", "table", true )
 | 
			
		||||
        checkKey( options, "method", "string", true )
 | 
			
		||||
        checkKey( options, "redirect", "boolean", true )
 | 
			
		||||
@@ -725,15 +716,9 @@ if http then
 | 
			
		||||
            return wrapRequest( _url.url, _url )
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        if type( _url ) ~= "string" then
 | 
			
		||||
            error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if _headers ~= nil and type( _headers ) ~= "table" then
 | 
			
		||||
            error( "bad argument #2 (expected table, got " .. type( _headers ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if _binary ~= nil and type( _binary ) ~= "boolean" then
 | 
			
		||||
            error( "bad argument #3 (expected boolean, got " .. type( _binary ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        expect(1, _url, "string")
 | 
			
		||||
        expect(2, _headers, "table", "nil")
 | 
			
		||||
        expect(3, _binary, "boolean", "nil")
 | 
			
		||||
        return wrapRequest( _url, _url, nil, _headers, _binary )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@@ -743,18 +728,10 @@ if http then
 | 
			
		||||
            return wrapRequest( _url.url, _url )
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        if type( _url ) ~= "string" then
 | 
			
		||||
            error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if type( _post ) ~= "string" then
 | 
			
		||||
            error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if _headers ~= nil and type( _headers ) ~= "table" then
 | 
			
		||||
            error( "bad argument #3 (expected table, got " .. type( _headers ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if _binary ~= nil and type( _binary ) ~= "boolean" then
 | 
			
		||||
            error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        expect(1, _url, "string")
 | 
			
		||||
        expect(2, _post, "string")
 | 
			
		||||
        expect(3, _headers, "table", "nil")
 | 
			
		||||
        expect(4, _binary, "boolean", "nil")
 | 
			
		||||
        return wrapRequest( _url, _url, _post, _headers, _binary )
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@@ -764,19 +741,10 @@ if http then
 | 
			
		||||
            checkOptions( _url )
 | 
			
		||||
            url = _url.url
 | 
			
		||||
        else
 | 
			
		||||
            if type( _url ) ~= "string" then
 | 
			
		||||
                error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
 | 
			
		||||
            end
 | 
			
		||||
            if _post ~= nil and type( _post ) ~= "string" then
 | 
			
		||||
                error( "bad argument #2 (expected string, got " .. type( _post ) .. ")", 2 )
 | 
			
		||||
            end
 | 
			
		||||
            if _headers ~= nil and type( _headers ) ~= "table" then
 | 
			
		||||
                error( "bad argument #3 (expected table, got " .. type( _headers ) .. ")", 2 )
 | 
			
		||||
            end
 | 
			
		||||
            if _binary ~= nil and type( _binary ) ~= "boolean" then
 | 
			
		||||
                error( "bad argument #4 (expected boolean, got " .. type( _binary ) .. ")", 2 )
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            expect(1, _url, "string")
 | 
			
		||||
            expect(2, _post, "string", "nil")
 | 
			
		||||
            expect(3, _headers, "table", "nil")
 | 
			
		||||
            expect(4, _binary, "boolean", "nil")
 | 
			
		||||
            url = _url.url
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
@@ -802,12 +770,9 @@ if http then
 | 
			
		||||
    local nativeWebsocket = http.websocket
 | 
			
		||||
    http.websocketAsync = nativeWebsocket
 | 
			
		||||
    http.websocket = function( _url, _headers )
 | 
			
		||||
        if type( _url ) ~= "string" then
 | 
			
		||||
            error( "bad argument #1 (expected string, got " .. type( _url ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        if _headers ~= nil and type( _headers ) ~= "table" then
 | 
			
		||||
            error( "bad argument #2 (expected table, got " .. type( _headers ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
        expect(1, _url, "string")
 | 
			
		||||
        expect(2, _headers, "table", "nil")
 | 
			
		||||
 | 
			
		||||
        local ok, err = nativeWebsocket( _url, _headers )
 | 
			
		||||
        if not ok then return ok, err end
 | 
			
		||||
 | 
			
		||||
@@ -825,18 +790,11 @@ end
 | 
			
		||||
-- Install the lua part of the FS api
 | 
			
		||||
local tEmpty = {}
 | 
			
		||||
function fs.complete( sPath, sLocation, bIncludeFiles, bIncludeDirs )
 | 
			
		||||
    if type( sPath ) ~= "string" then
 | 
			
		||||
        error( "bad argument #1 (expected string, got " .. type( sPath ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if type( sLocation ) ~= "string" then
 | 
			
		||||
        error( "bad argument #2 (expected string, got " .. type( sLocation ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if bIncludeFiles ~= nil and type( bIncludeFiles ) ~= "boolean" then
 | 
			
		||||
        error( "bad argument #3 (expected boolean, got " .. type( bIncludeFiles ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if bIncludeDirs ~= nil and type( bIncludeDirs ) ~= "boolean" then
 | 
			
		||||
        error( "bad argument #4 (expected boolean, got " .. type( bIncludeDirs ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, sPath, "string")
 | 
			
		||||
    expect(2, sLocation, "string")
 | 
			
		||||
    expect(3, bIncludeFiles, "boolean", "nil")
 | 
			
		||||
    expect(4, bIncludeDirs, "boolean", "nil")
 | 
			
		||||
 | 
			
		||||
    bIncludeFiles = (bIncludeFiles ~= false)
 | 
			
		||||
    bIncludeDirs = (bIncludeDirs ~= false)
 | 
			
		||||
    local sDir = sLocation
 | 
			
		||||
@@ -982,6 +940,8 @@ settings.set( "edit.default_extension", "lua" )
 | 
			
		||||
settings.set( "paint.default_extension", "nfp" )
 | 
			
		||||
settings.set( "lua.autocomplete", true )
 | 
			
		||||
settings.set( "list.show_hidden", false )
 | 
			
		||||
settings.set( "motd.enable", false )
 | 
			
		||||
settings.set( "motd.path", "/rom/motd.txt:/motd.txt" )
 | 
			
		||||
if term.isColour() then
 | 
			
		||||
    settings.set( "bios.use_multishell", true )
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
 | 
			
		||||
 | 
			
		||||
-- Colors
 | 
			
		||||
white = 1
 | 
			
		||||
orange = 2
 | 
			
		||||
@@ -18,49 +20,35 @@ black = 32768
 | 
			
		||||
 | 
			
		||||
function combine( ... )
 | 
			
		||||
    local r = 0
 | 
			
		||||
    for n,c in ipairs( { ... } ) do
 | 
			
		||||
        if type( c ) ~= "number" then
 | 
			
		||||
            error( "bad argument #"..n.." (expected number, got " .. type( c ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
    for i = 1, select('#', ...) do
 | 
			
		||||
        local c = select(i, ...)
 | 
			
		||||
        expect(i, c, "number")
 | 
			
		||||
        r = bit32.bor(r,c)
 | 
			
		||||
    end
 | 
			
		||||
    return r
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function subtract( colors, ... )
 | 
			
		||||
    if type( colors ) ~= "number" then
 | 
			
		||||
        error( "bad argument #1 (expected number, got " .. type( colors ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, colors, "number")
 | 
			
		||||
    local r = colors
 | 
			
		||||
    for n,c in ipairs( { ... } ) do
 | 
			
		||||
        if type( c ) ~= "number" then
 | 
			
		||||
            error( "bad argument #"..tostring( n+1 ).." (expected number, got " .. type( c ) .. ")", 2 )
 | 
			
		||||
        end
 | 
			
		||||
    for i = 1, select('#', ...) do
 | 
			
		||||
        local c = select(i, ...)
 | 
			
		||||
        expect(i + 1, c, "number")
 | 
			
		||||
        r = bit32.band(r, bit32.bnot(c))
 | 
			
		||||
    end
 | 
			
		||||
    return r
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function test( colors, color )
 | 
			
		||||
    if type( colors ) ~= "number" then
 | 
			
		||||
        error( "bad argument #1 (expected number, got " .. type( colors ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if type( color ) ~= "number" then
 | 
			
		||||
        error( "bad argument #2 (expected number, got " .. type( color ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, colors, "number")
 | 
			
		||||
    expect(2, color, "number")
 | 
			
		||||
    return bit32.band(colors, color) == color
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function packRGB( r, g, b )
 | 
			
		||||
    if type( r ) ~= "number" then
 | 
			
		||||
        error( "bad argument #1 (expected number, got " .. type( r ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if type( g ) ~= "number" then
 | 
			
		||||
        error( "bad argument #2 (expected number, got " .. type( g ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    if type( b ) ~= "number" then
 | 
			
		||||
        error( "bad argument #3 (expected number, got " .. type( b ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, r, "number")
 | 
			
		||||
    expect(2, g, "number")
 | 
			
		||||
    expect(3, b, "number")
 | 
			
		||||
    return
 | 
			
		||||
        bit32.band( r * 255, 0xFF ) * 2^16 +
 | 
			
		||||
        bit32.band( g * 255, 0xFF ) * 2^8 +
 | 
			
		||||
@@ -68,9 +56,7 @@ function packRGB( r, g, b )
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function unpackRGB( rgb )
 | 
			
		||||
    if type( rgb ) ~= "number" then
 | 
			
		||||
        error( "bad argument #1 (expected number, got " .. type( rgb ) .. ")", 2 )
 | 
			
		||||
    end
 | 
			
		||||
    expect(1, rgb, "number")
 | 
			
		||||
    return
 | 
			
		||||
        bit32.band( bit32.rshift( rgb, 16 ), 0xFF ) / 255,
 | 
			
		||||
        bit32.band( bit32.rshift( rgb, 8 ), 0xFF ) / 255,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user