mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-16 14:37:39 +00:00
Compare commits
172 Commits
1.80pr1
...
v1.80pr1.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9be61abd6b | ||
![]() |
922f424a78 | ||
![]() |
5c7828dd79 | ||
![]() |
74f5093d2a | ||
![]() |
4651e362c9 | ||
![]() |
a2e2a5cb37 | ||
![]() |
15a3882016 | ||
![]() |
d3ecd5214b | ||
![]() |
0a8a8a742e | ||
![]() |
ecff23d027 | ||
![]() |
20dcb32bae | ||
![]() |
678462d2db | ||
![]() |
61fdfec09b | ||
![]() |
2d3cd5dc80 | ||
![]() |
5208ad0b98 | ||
![]() |
662fb96beb | ||
![]() |
4c14431a3d | ||
![]() |
5ae38a3f18 | ||
![]() |
94e10d1f67 | ||
![]() |
0a50676884 | ||
![]() |
41cce78fcb | ||
![]() |
4c0fa1fabe | ||
![]() |
54e1dafa3f | ||
![]() |
3ac76bc05b | ||
![]() |
83030df3ee | ||
![]() |
07d15caf6f | ||
![]() |
3298efe652 | ||
![]() |
01d9919a3e | ||
![]() |
80b1170b63 | ||
![]() |
2e7302e654 | ||
![]() |
ca7fb8a0b4 | ||
![]() |
c9b0894f26 | ||
![]() |
c3454a195d | ||
![]() |
3b3dd8071b | ||
![]() |
0d28c67534 | ||
![]() |
d0af85754a | ||
![]() |
3e265c27ff | ||
![]() |
8d356f50c4 | ||
![]() |
f30c4f16c0 | ||
![]() |
8bb8caa315 | ||
![]() |
0f17a3d72e | ||
![]() |
7647369e2d | ||
![]() |
4b4208e724 | ||
![]() |
2a16a1df85 | ||
![]() |
25f7c58400 | ||
![]() |
c3db91f11f | ||
![]() |
8c66ce03d4 | ||
![]() |
2be2a0625e | ||
![]() |
c904d5041b | ||
![]() |
632762768e | ||
![]() |
c69ba205f8 | ||
![]() |
019f4dbea9 | ||
![]() |
259ea41ce3 | ||
![]() |
11290f7204 | ||
![]() |
abd06133fb | ||
![]() |
29a3a0c48f | ||
![]() |
2728c63512 | ||
![]() |
f3b11bc1c2 | ||
![]() |
04590befb3 | ||
![]() |
4e9034f910 | ||
![]() |
ba9cfa3764 | ||
![]() |
341e3e2f89 | ||
![]() |
3f70ca5192 | ||
![]() |
f11bfc53ee | ||
![]() |
61e3967b8e | ||
![]() |
add86ea100 | ||
![]() |
dd51c89278 | ||
![]() |
788d783745 | ||
![]() |
35da60543e | ||
![]() |
ce7923d248 | ||
![]() |
55847460c5 | ||
![]() |
893524b0a8 | ||
![]() |
8fb3ae405f | ||
![]() |
aa447ec101 | ||
![]() |
56b1cb4521 | ||
![]() |
90cc24614c | ||
![]() |
d7301ff15e | ||
![]() |
1cf10c5c47 | ||
![]() |
6691ec8e3a | ||
![]() |
a9f77221ff | ||
![]() |
dd3b69a633 | ||
![]() |
d766f8b34e | ||
![]() |
2ae6fb47e7 | ||
![]() |
dd5698241b | ||
![]() |
ed8e9d7817 | ||
![]() |
6c29b44c3c | ||
![]() |
0caa133089 | ||
![]() |
a8b08bd971 | ||
![]() |
c9181a121f | ||
![]() |
30f4e0829f | ||
![]() |
2155fce036 | ||
![]() |
27602ec8fc | ||
![]() |
66f683d9c9 | ||
![]() |
ac08a52323 | ||
![]() |
fe0f998c27 | ||
![]() |
bcf79165f9 | ||
![]() |
9b2a50cdfc | ||
![]() |
c5d99db654 | ||
![]() |
5253ab3e58 | ||
![]() |
11d8253d9c | ||
![]() |
bc2b481918 | ||
![]() |
b26564ccb9 | ||
![]() |
28e3ffe978 | ||
![]() |
540e2e25aa | ||
![]() |
845118e9e2 | ||
![]() |
b2b8753ee7 | ||
![]() |
060fb21bdb | ||
![]() |
ef008709c7 | ||
![]() |
09da119f27 | ||
![]() |
f8487d1e1c | ||
![]() |
b6f773ffce | ||
![]() |
c8673473ef | ||
![]() |
3829815756 | ||
![]() |
7eac8faf0d | ||
![]() |
0bd0f4d313 | ||
![]() |
73873eb8cb | ||
![]() |
0420b6c831 | ||
![]() |
bb741975b7 | ||
![]() |
8bffec6964 | ||
![]() |
9e19dd7070 | ||
![]() |
aba0e3d2d4 | ||
![]() |
d9d025e33b | ||
![]() |
1fe29ab098 | ||
![]() |
53f16782ab | ||
![]() |
bfb4f88304 | ||
![]() |
fb6d65ec23 | ||
![]() |
6b364052c7 | ||
![]() |
7169abcd7b | ||
![]() |
75ccfbdb3d | ||
![]() |
728644c104 | ||
![]() |
999351e667 | ||
![]() |
11e879db41 | ||
![]() |
a4a774fcdf | ||
![]() |
4fb0240a36 | ||
![]() |
80ec54eaf6 | ||
![]() |
9e4ae3a494 | ||
![]() |
19e4c03d3a | ||
![]() |
c6b8cb1fab | ||
![]() |
f20a7afa7f | ||
![]() |
01f5d006fc | ||
![]() |
cd6b076efe | ||
![]() |
f8193a4d23 | ||
![]() |
5be2202b2e | ||
![]() |
fbbfe33e21 | ||
![]() |
7a916ed8c2 | ||
![]() |
60305cd106 | ||
![]() |
b8630f739a | ||
![]() |
1c8480a329 | ||
![]() |
5219648128 | ||
![]() |
1415dd0dae | ||
![]() |
282aa804f8 | ||
![]() |
e959051239 | ||
![]() |
373b7ba293 | ||
![]() |
70c6f3498b | ||
![]() |
afec3743f3 | ||
![]() |
5989d021c7 | ||
![]() |
baa8993999 | ||
![]() |
92f5860de6 | ||
![]() |
12abd4292e | ||
![]() |
4bd5b0d236 | ||
![]() |
0115bc8dca | ||
![]() |
5f323a85a7 | ||
![]() |
85c556d324 | ||
![]() |
0c1114edbc | ||
![]() |
2c264728d9 | ||
![]() |
90c4ebd208 | ||
![]() |
5df97e5133 | ||
![]() |
acb5f65e16 | ||
![]() |
c9e7b45509 | ||
![]() |
6fca136327 | ||
![]() |
084bbe8480 | ||
![]() |
225ec594e7 |
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
# Sadly too many files have whitespace errors, so we leave this as is for
|
||||
# now and just make sure we don't introduce any more.
|
||||
# trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.properties]
|
||||
insert_final_newline = false
|
43
README.md
43
README.md
@@ -1,26 +1,31 @@
|
||||
ComputerCraft
|
||||
=============
|
||||
[](https://travis-ci.org/dan200/ComputerCraft)
|
||||
# 
|
||||
[](https://travis-ci.org/SquidDev-CC/CC-Tweaked)
|
||||
|
||||
ComputerCraft is a Minecraft modification which adds programmable Robots and Computers to the world of Minecraft.
|
||||
If you're not familiar with ComputerCraft, visit the [Website](http://www.computercraft.info/download) or the [Wiki](http://www.computercraft.info/wiki) to find out more.
|
||||
CC: Tweaked is a fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development
|
||||
features of the mod. For a more stable experience, I recommend checking out the
|
||||
[original mod](https://github.com/dan200/ComputerCraft).
|
||||
|
||||
About this Repository
|
||||
=====================
|
||||
## What?
|
||||
CC: Tweaked does not aim to create a competing fork of ComputerCraft, nor am I planning to take it in in a vastly
|
||||
different direction to the original mod. In fact, CC: Tweaked aims to be a nurturing ground for various features, with
|
||||
a pull request against the original mod being the end goal.
|
||||
|
||||
ComputerCraft was originally released in late 2011 by [Daniel Ratcliffe](https://twitter.com/DanTwoHundred). In early 2017, after working on the mod solo for five years, it was decided to release the source code publicly to allow Dan to devote time to other projects. This repository marks the first public release of this source code.
|
||||
CC: Tweaked also includes many pull requests from the community which have not yet been merged, offering a large number
|
||||
of additional bug fixes and features over the original mod.
|
||||
|
||||
The code in this repository will always represent the "bleeding edge" of the ComputerCraft codebase, but stable builds back to 1.79 will be marked on the [Releases](https://github.com/dan200/ComputerCraft/releases) page.
|
||||
## Relation to CCTweaks?
|
||||
This mod has nothing to do with CCTweaks, though there is no denying the name is a throwback to it. However, I do plan
|
||||
to migrate some features of CCTweaks into CC: Tweaked.
|
||||
|
||||
Contributing
|
||||
============
|
||||
## Contributing
|
||||
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you do wish to contribute
|
||||
code, do consider submitting it to the ComputerCraft repository instead.
|
||||
|
||||
While ComputerCraft will no longer be actively developed by Daniel Ratcliffe, you may still contribute pull requests which will be reviewed and incorporated into releases periodically. A pull request is more likely to be accepted if it meets the following criteria:
|
||||
That being said, in order to start helping develop CC: Tweaked, you'll need to follow these steps:
|
||||
|
||||
* It does not add any new dependencies for compiling, running or using the mod.
|
||||
* It does not break compatibility with world saves or programs created with previous versions of the mod.
|
||||
* It does not add unneccessary complexity for users of the mod, and maintains the accessibility for which the mod is known.
|
||||
* It does not add unneccessary complexity or stylistic changes to the code, especially where functionality is not being changed.
|
||||
* It does not create bugs!
|
||||
|
||||
The pull requests most likely to be accepted are those which fix bugs, simplify code, or make the mod compatible with newer versions of Minecraft.
|
||||
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||
- **Setup Forge:** `./gradlew setupDecompWorkspace`
|
||||
- **Test your changes:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
|
||||
|
||||
If you want to run CC: Tweaked in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from
|
||||
`build/libs`.
|
||||
|
82
build.gradle
82
build.gradle
@@ -13,19 +13,17 @@ buildscript {
|
||||
classpath 'org.ajoberstar:gradle-git:1.6.0'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.matthewprenger.cursegradle' version '1.0.9'
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle.forge'
|
||||
apply plugin: 'org.ajoberstar.grgit'
|
||||
|
||||
/*
|
||||
// for people who want stable - not yet functional for MC 1.8.8 - we require the forgegradle 2.1 snapshot
|
||||
plugins {
|
||||
id "net.minecraftforge.gradle.forge" version "2.0.2"
|
||||
}
|
||||
*/
|
||||
|
||||
version = "1.80pr1"
|
||||
group = "dan200.computercraft"
|
||||
archivesBaseName = "ComputerCraft"
|
||||
version = "1.80pr1.4"
|
||||
group = "org.squiddev"
|
||||
archivesBaseName = "cc-tweaked"
|
||||
|
||||
minecraft {
|
||||
version = "1.12-14.21.1.2387"
|
||||
@@ -41,34 +39,48 @@ minecraft {
|
||||
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "JEI"
|
||||
url = "http://dvs1.progwml6.com/files/maven"
|
||||
}
|
||||
maven {
|
||||
name = "squiddev"
|
||||
url = "https://dl.bintray.com/squiddev/maven"
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
shade
|
||||
compile.extendsFrom shade
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// you may put jars on which you depend on in ./libs
|
||||
// or you may define them like so..
|
||||
//compile "some.group:artifact:version:classifier"
|
||||
//compile "some.group:artifact:version"
|
||||
deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api"
|
||||
runtime "mezz.jei:jei_1.12:4.7.5.86"
|
||||
shade 'org.squiddev:Cobalt:0.3.1'
|
||||
|
||||
// real examples
|
||||
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
|
||||
//compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
|
||||
|
||||
// the 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
|
||||
//provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
|
||||
|
||||
// the deobf configurations: 'deobfCompile' and 'deobfProvided' are the same as the normal compile and provided,
|
||||
// except that these dependencies get remapped to your current MCP mappings
|
||||
//deobfCompile 'com.mod-buildcraft:buildcraft:6.0.8:dev'
|
||||
//deobfProvided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
|
||||
|
||||
// for more info...
|
||||
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
|
||||
// http://www.gradle.org/docs/current/userguide/dependency_management.html
|
||||
testCompile 'junit:junit:4.11'
|
||||
}
|
||||
|
||||
javadoc {
|
||||
include "dan200/computercraft/api/**/*.java"
|
||||
}
|
||||
|
||||
jar {
|
||||
dependsOn javadoc
|
||||
|
||||
manifest {
|
||||
attributes('FMLAT': 'computercraft_at.cfg')
|
||||
}
|
||||
|
||||
into("docs", { from (javadoc.destinationDir) })
|
||||
|
||||
into("api", { from (sourceSets.main.allSource) {
|
||||
include "dan200/computercraft/api/**/*.java"
|
||||
}})
|
||||
|
||||
from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
|
||||
import org.ajoberstar.grgit.Grgit
|
||||
@@ -103,9 +115,21 @@ processResources {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
curseforge {
|
||||
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
|
||||
project {
|
||||
id = '282001'
|
||||
releaseType = 'beta'
|
||||
changelog = ''
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint"
|
||||
}
|
||||
}
|
||||
|
||||
runClient.outputs.upToDateWhen { false }
|
||||
runServer.outputs.upToDateWhen { false }
|
||||
|
25
deploy.sh
25
deploy.sh
@@ -18,29 +18,4 @@ OUTPUTJAR=`ls -1 build/libs | grep -v sources | sed s/\-//g`
|
||||
FRIENDLYNAME=`ls -1 build/libs | grep -v sources | sed s/\-/\ /g | sed s/\.jar//g`
|
||||
cp build/libs/$INPUTJAR deploy/$OUTPUTJAR
|
||||
|
||||
echo "Creating API..."
|
||||
mkdir -p deploy/api/src/dan200/computercraft
|
||||
cp -r build/sources/main/java/dan200/computercraft/api deploy/api/src/dan200/computercraft/api
|
||||
|
||||
echo "Creating API Javadocs..."
|
||||
mkdir -p deploy/api/doc
|
||||
cd src/main/java/dan200/computercraft/api
|
||||
find . -type f -name "*.java" | xargs javadoc -d ../../../../../../deploy/api/doc -windowtitle "$FRIENDLYNAME"
|
||||
cd ../../../../../..
|
||||
|
||||
echo "Adding API and Javadocs to deployment..."
|
||||
cd deploy
|
||||
zip -r $OUTPUTJAR api/doc > /dev/null
|
||||
zip -r $OUTPUTJAR api/src/dan200/computercraft > /dev/null
|
||||
cd ..
|
||||
rm -rf deploy/api
|
||||
|
||||
echo "Adding LuaJ to deployment..."
|
||||
mkdir deploy/luaj
|
||||
cd deploy/luaj
|
||||
jar xf ../../libs/luaj-jse-2.0.3.jar
|
||||
zip -r ../$OUTPUTJAR org > /dev/null
|
||||
cd ../..
|
||||
rm -rf deploy/luaj
|
||||
|
||||
echo "Done."
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Mon Sep 14 12:28:28 PDT 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
|
||||
|
110
gradlew
vendored
110
gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS="-Xmx2048m"
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
14
gradlew.bat
vendored
14
gradlew.bat
vendored
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-d64 -Xmx2048m"
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
Binary file not shown.
181
luaj-2.0.3/src/core/org/luaj/vm2/lib/Bit32Lib.java
Normal file
181
luaj-2.0.3/src/core/org/luaj/vm2/lib/Bit32Lib.java
Normal file
@@ -0,0 +1,181 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012 Luaj.org. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
package org.luaj.vm2.lib;
|
||||
|
||||
import org.luaj.vm2.LuaInteger;
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.Varargs;
|
||||
|
||||
/**
|
||||
* Subclass of LibFunction that implements the Lua standard {@code bit32} library.
|
||||
*/
|
||||
public class Bit32Lib extends ZeroArgFunction
|
||||
{
|
||||
public LuaValue call( )
|
||||
{
|
||||
LuaTable t = new LuaTable();
|
||||
bind( t, Bit32LibV.class, new String[] {
|
||||
"band", "bnot", "bor", "btest", "bxor", "extract", "replace"
|
||||
} );
|
||||
bind( t, Bit32Lib2.class, new String[] {
|
||||
"arshift", "lrotate", "lshift", "rrotate", "rshift"
|
||||
} );
|
||||
env.set( "bit32", t );
|
||||
return t;
|
||||
}
|
||||
|
||||
public static final class Bit32LibV extends VarArgFunction
|
||||
{
|
||||
public Varargs invoke( Varargs args )
|
||||
{
|
||||
switch( opcode )
|
||||
{
|
||||
case 0: // band
|
||||
{
|
||||
int result = -1;
|
||||
for( int i = 1; i <= args.narg(); i++ )
|
||||
{
|
||||
result &= args.checkint( i );
|
||||
}
|
||||
return bitsToValue( result );
|
||||
}
|
||||
case 1: // bnot
|
||||
return bitsToValue( ~args.checkint( 1 ) );
|
||||
case 2: // bot
|
||||
{
|
||||
int result = 0;
|
||||
for( int i = 1; i <= args.narg(); i++ )
|
||||
{
|
||||
result |= args.checkint( i );
|
||||
}
|
||||
return bitsToValue( result );
|
||||
}
|
||||
case 3: // btest
|
||||
{
|
||||
int bits = -1;
|
||||
for( int i = 1; i <= args.narg(); i++ )
|
||||
{
|
||||
bits &= args.checkint( i );
|
||||
}
|
||||
return valueOf( bits != 0 );
|
||||
}
|
||||
case 4: // bxor
|
||||
{
|
||||
int result = 0;
|
||||
for( int i = 1; i <= args.narg(); i++ )
|
||||
{
|
||||
result ^= args.checkint( i );
|
||||
}
|
||||
return bitsToValue( result );
|
||||
}
|
||||
case 5: // extract
|
||||
{
|
||||
int field = args.checkint( 2 );
|
||||
int width = args.optint( 3, 1 );
|
||||
|
||||
if( field < 0 ) argerror( 2, "field cannot be negative" );
|
||||
if( width <= 0 ) argerror( 3, "width must be postive" );
|
||||
if( field + width > 32 ) error( "trying to access non-existent bits" );
|
||||
|
||||
return bitsToValue( (args.checkint( 1 ) >>> field) & (-1 >>> (32 - width)) );
|
||||
}
|
||||
case 6: // replace
|
||||
{
|
||||
int n = args.checkint( 1 );
|
||||
int v = args.checkint( 2 );
|
||||
int field = args.checkint( 3 );
|
||||
int width = args.optint( 4, 1 );
|
||||
|
||||
if( field < 0 ) argerror( 3, "field cannot be negative" );
|
||||
if( width <= 0 ) argerror( 4, "width must be postive" );
|
||||
if( field + width > 32 ) error( "trying to access non-existent bits" );
|
||||
|
||||
int mask = (-1 >>> (32 - width)) << field;
|
||||
n = (n & ~mask) | ((v << field) & mask);
|
||||
return bitsToValue( n );
|
||||
}
|
||||
}
|
||||
return NIL;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Bit32Lib2 extends TwoArgFunction
|
||||
{
|
||||
public LuaValue call( LuaValue arg1, LuaValue arg2 )
|
||||
{
|
||||
switch( opcode )
|
||||
{
|
||||
case 0: // arshift
|
||||
{
|
||||
int x = arg1.checkint();
|
||||
int disp = arg2.checkint();
|
||||
return disp >= 0 ? bitsToValue( x >> disp ) : bitsToValue( x << -disp );
|
||||
}
|
||||
case 1: // lrotate
|
||||
return rotate( arg1.checkint(), arg2.checkint() );
|
||||
case 2: // lshift
|
||||
return shift( arg1.checkint(), arg2.checkint() );
|
||||
case 3: // rrotate
|
||||
return rotate( arg1.checkint(), -arg2.checkint() );
|
||||
case 4: // rshift
|
||||
return shift( arg1.checkint(), -arg2.checkint() );
|
||||
}
|
||||
return NIL;
|
||||
}
|
||||
}
|
||||
|
||||
static LuaValue rotate( int x, int disp )
|
||||
{
|
||||
if( disp < 0 )
|
||||
{
|
||||
disp = -disp & 31;
|
||||
return bitsToValue( (x >>> disp) | (x << (32 - disp)) );
|
||||
}
|
||||
else
|
||||
{
|
||||
disp = disp & 31;
|
||||
return bitsToValue( (x << disp) | (x >>> (32 - disp)) );
|
||||
}
|
||||
}
|
||||
|
||||
static LuaValue shift( int x, int disp )
|
||||
{
|
||||
if( disp >= 32 || disp <= -32 )
|
||||
{
|
||||
return ZERO;
|
||||
}
|
||||
else if( disp >= 0 )
|
||||
{
|
||||
return bitsToValue( x << disp );
|
||||
}
|
||||
else
|
||||
{
|
||||
return bitsToValue( x >>> -disp );
|
||||
}
|
||||
}
|
||||
|
||||
private static LuaValue bitsToValue( int x )
|
||||
{
|
||||
return x < 0 ? LuaValue.valueOf( (long) x & 0xFFFFFFFFL ) : LuaInteger.valueOf( x );
|
||||
}
|
||||
}
|
@@ -7,26 +7,37 @@
|
||||
package dan200.computercraft;
|
||||
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Converter;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.media.IMediaProvider;
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.network.wired.IWiredNode;
|
||||
import dan200.computercraft.api.network.wired.IWiredProvider;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||
import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||
import dan200.computercraft.core.apis.AddressPredicate;
|
||||
import dan200.computercraft.core.filesystem.ComboMount;
|
||||
import dan200.computercraft.core.filesystem.FileMount;
|
||||
import dan200.computercraft.core.filesystem.JarMount;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.command.CommandComputer;
|
||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
|
||||
import dan200.computercraft.shared.computer.blocks.BlockComputer;
|
||||
import dan200.computercraft.shared.computer.blocks.TileComputer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
|
||||
import dan200.computercraft.shared.media.items.ItemDiskExpanded;
|
||||
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
|
||||
@@ -36,6 +47,7 @@ import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import dan200.computercraft.shared.network.PacketHandler;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockCable;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockWiredModemFull;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
|
||||
import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem;
|
||||
import dan200.computercraft.shared.peripheral.modem.WirelessNetwork;
|
||||
@@ -49,6 +61,7 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
|
||||
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.*;
|
||||
import dan200.computercraft.shared.wired.WiredNode;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -61,6 +74,7 @@ import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.NonNullList;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.config.ConfigCategory;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
@@ -81,10 +95,7 @@ import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@@ -93,7 +104,7 @@ import java.util.zip.ZipFile;
|
||||
///////////////
|
||||
|
||||
@Mod(
|
||||
modid = ComputerCraft.MOD_ID, name = "ComputerCraft", version = "${version}",
|
||||
modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}",
|
||||
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory"
|
||||
)
|
||||
public class ComputerCraft
|
||||
@@ -109,6 +120,7 @@ public class ComputerCraft
|
||||
// ComputerCraftEdu uses ID 104
|
||||
public static final int printoutGUIID = 105;
|
||||
public static final int pocketComputerGUIID = 106;
|
||||
public static final int viewComputerGUIID = 110;
|
||||
|
||||
// Configuration options
|
||||
private static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
|
||||
@@ -119,12 +131,15 @@ public class ComputerCraft
|
||||
"192.168.0.0/16",
|
||||
"fd00::/8",
|
||||
};
|
||||
|
||||
|
||||
public static boolean http_enable = true;
|
||||
public static boolean http_websocket_enable = true;
|
||||
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
|
||||
public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
|
||||
public static boolean disable_lua51_features = false;
|
||||
public static String default_computer_settings = "";
|
||||
public static boolean debug_enable = false;
|
||||
public static int computer_threads = 1;
|
||||
public static boolean logPeripheralErrors = false;
|
||||
|
||||
public static boolean enableCommandBlock = false;
|
||||
@@ -133,6 +148,7 @@ public class ComputerCraft
|
||||
public static int advancedTurtleFuelLimit = 100000;
|
||||
public static boolean turtlesObeyBlockProtection = true;
|
||||
public static boolean turtlesCanPush = true;
|
||||
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
|
||||
|
||||
public static final int terminalWidth_computer = 51;
|
||||
public static final int terminalHeight_computer = 19;
|
||||
@@ -165,6 +181,7 @@ public class ComputerCraft
|
||||
public static BlockTurtle turtleAdvanced;
|
||||
public static BlockCommandComputer commandComputer;
|
||||
public static BlockAdvancedModem advancedModem;
|
||||
public static BlockWiredModemFull wiredModemFull;
|
||||
}
|
||||
|
||||
public static class Items
|
||||
@@ -200,10 +217,13 @@ public class ComputerCraft
|
||||
public static Configuration config;
|
||||
|
||||
public static Property http_enable;
|
||||
public static Property http_websocket_enable;
|
||||
public static Property http_whitelist;
|
||||
public static Property http_blacklist;
|
||||
public static Property disable_lua51_features;
|
||||
public static Property default_computer_settings;
|
||||
public static Property debug_enable;
|
||||
public static Property computer_threads;
|
||||
public static Property logPeripheralErrors;
|
||||
|
||||
public static Property enableCommandBlock;
|
||||
@@ -212,6 +232,7 @@ public class ComputerCraft
|
||||
public static Property advancedTurtleFuelLimit;
|
||||
public static Property turtlesObeyBlockProtection;
|
||||
public static Property turtlesCanPush;
|
||||
public static Property turtleDisabledActions;
|
||||
|
||||
public static Property modem_range;
|
||||
public static Property modem_highAltitudeRange;
|
||||
@@ -244,6 +265,8 @@ public class ComputerCraft
|
||||
private static List<IMediaProvider> mediaProviders = new ArrayList<>();
|
||||
private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>();
|
||||
private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>();
|
||||
private static final Set<ILuaAPIFactory> apiFactories = new LinkedHashSet<>();
|
||||
private static final Set<IWiredProvider> wiredProviders = new LinkedHashSet<>();
|
||||
|
||||
// Implementation
|
||||
@Mod.Instance( value = ComputerCraft.MOD_ID )
|
||||
@@ -266,11 +289,26 @@ public class ComputerCraft
|
||||
|
||||
// Load config
|
||||
Config.config = new Configuration( event.getSuggestedConfigurationFile() );
|
||||
loadConfig();
|
||||
|
||||
// Setup network
|
||||
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
|
||||
networkEventChannel.register( new PacketHandler() );
|
||||
|
||||
proxy.preInit();
|
||||
turtleProxy.preInit();
|
||||
}
|
||||
|
||||
public static void loadConfig()
|
||||
{
|
||||
Config.config.load();
|
||||
|
||||
Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable );
|
||||
Config.http_enable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for more fine grained control than this)" );
|
||||
|
||||
Config.http_websocket_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_websocket_enable", http_websocket_enable );
|
||||
Config.http_websocket_enable.setComment( "Enable use of http websockets. This requires the \"http_enable\" option to also be true." );
|
||||
|
||||
{
|
||||
ConfigCategory category = Config.config.getCategory( Configuration.CATEGORY_GENERAL );
|
||||
Property currentProperty = category.get( "http_whitelist" );
|
||||
@@ -298,10 +336,20 @@ public class ComputerCraft
|
||||
Config.default_computer_settings = Config.config.get( Configuration.CATEGORY_GENERAL, "default_computer_settings", default_computer_settings );
|
||||
Config.default_computer_settings.setComment( "A comma seperated list of default system settings to set on new computers. Example: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all autocompletion" );
|
||||
|
||||
Config.debug_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "debug_enable", debug_enable );
|
||||
Config.debug_enable.setComment( "Enable Lua's debug library. Whilst this should be safe for general use, it may allow players to interact with other computers. Enable at your own risk." );
|
||||
|
||||
Config.computer_threads = Config.config.get( Configuration.CATEGORY_GENERAL, "computer_threads", computer_threads );
|
||||
Config.computer_threads
|
||||
.setMinValue( 1 )
|
||||
.setRequiresWorldRestart( true )
|
||||
.setComment( "Set the number of threads computers can run on. A higher number means more computers can run at once, but may induce lag.\n" +
|
||||
"Please note that some mods may not work with a thread count higher than 1. Use with caution." );
|
||||
|
||||
Config.logPeripheralErrors = Config.config.get( Configuration.CATEGORY_GENERAL, "logPeripheralErrors", logPeripheralErrors );
|
||||
Config.logPeripheralErrors.setComment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
|
||||
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." );
|
||||
|
||||
|
||||
Config.enableCommandBlock = Config.config.get( Configuration.CATEGORY_GENERAL, "enableCommandBlock", enableCommandBlock );
|
||||
Config.enableCommandBlock.setComment( "Enable Command Block peripheral support" );
|
||||
|
||||
@@ -341,6 +389,9 @@ public class ComputerCraft
|
||||
Config.turtlesCanPush = Config.config.get( Configuration.CATEGORY_GENERAL, "turtlesCanPush", turtlesCanPush );
|
||||
Config.turtlesCanPush.setComment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" );
|
||||
|
||||
Config.turtleDisabledActions = Config.config.get( Configuration.CATEGORY_GENERAL, "turtle_disabled_actions", new String[ 0 ] );
|
||||
Config.turtleDisabledActions.setComment( "A list of turtle actions which are disabled." );
|
||||
|
||||
Config.maxNotesPerTick = Config.config.get( Configuration.CATEGORY_GENERAL, "maxNotesPerTick", maxNotesPerTick );
|
||||
Config.maxNotesPerTick.setComment( "Maximum amount of notes a speaker can play at once" );
|
||||
|
||||
@@ -350,22 +401,18 @@ public class ComputerCraft
|
||||
}
|
||||
|
||||
syncConfig();
|
||||
|
||||
// Setup network
|
||||
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
|
||||
networkEventChannel.register( new PacketHandler() );
|
||||
|
||||
proxy.preInit();
|
||||
turtleProxy.preInit();
|
||||
}
|
||||
|
||||
public static void syncConfig() {
|
||||
|
||||
http_enable = Config.http_enable.getBoolean();
|
||||
http_websocket_enable = Config.http_websocket_enable.getBoolean();
|
||||
http_whitelist = new AddressPredicate( Config.http_whitelist.getStringList() );
|
||||
http_blacklist = new AddressPredicate( Config.http_blacklist.getStringList() );
|
||||
disable_lua51_features = Config.disable_lua51_features.getBoolean();
|
||||
default_computer_settings = Config.default_computer_settings.getString();
|
||||
debug_enable = Config.debug_enable.getBoolean();
|
||||
computer_threads = Config.computer_threads.getInt();
|
||||
logPeripheralErrors = Config.logPeripheralErrors.getBoolean();
|
||||
|
||||
enableCommandBlock = Config.enableCommandBlock.getBoolean();
|
||||
@@ -385,6 +432,20 @@ public class ComputerCraft
|
||||
turtlesObeyBlockProtection = Config.turtlesObeyBlockProtection.getBoolean();
|
||||
turtlesCanPush = Config.turtlesCanPush.getBoolean();
|
||||
|
||||
turtleDisabledActions.clear();
|
||||
Converter<String, String> converter = CaseFormat.LOWER_CAMEL.converterTo( CaseFormat.UPPER_UNDERSCORE );
|
||||
for( String value : Config.turtleDisabledActions.getStringList() )
|
||||
{
|
||||
try
|
||||
{
|
||||
turtleDisabledActions.add( TurtleAction.valueOf( converter.convert( value ) ) );
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Unknown turtle action " + value );
|
||||
}
|
||||
}
|
||||
|
||||
maxNotesPerTick = Math.max(1, Config.maxNotesPerTick.getInt());
|
||||
|
||||
Config.config.save();
|
||||
@@ -400,6 +461,8 @@ public class ComputerCraft
|
||||
@Mod.EventHandler
|
||||
public void onServerStarting( FMLServerStartingEvent event )
|
||||
{
|
||||
event.registerServerCommand( new CommandComputer() );
|
||||
event.registerServerCommand( new CommandComputerCraft() );
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
@@ -496,6 +559,24 @@ public class ComputerCraft
|
||||
player.openGui( ComputerCraft.instance, ComputerCraft.pocketComputerGUIID, player.getEntityWorld(), hand.ordinal(), 0, 0 );
|
||||
}
|
||||
|
||||
public static void openComputerGUI( EntityPlayer player, ServerComputer computer )
|
||||
{
|
||||
ComputerFamily family = computer.getFamily();
|
||||
int width = 0, height = 0;
|
||||
Terminal terminal = computer.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
width = terminal.getWidth();
|
||||
height = terminal.getHeight();
|
||||
}
|
||||
|
||||
// Pack useful terminal information into the various coordinate bits.
|
||||
// These are extracted in ComputerCraftProxyCommon.getClientGuiElement
|
||||
player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(),
|
||||
computer.getInstanceID(), family.ordinal(), (width & 0xFFFF) << 16 | (height & 0xFFFF)
|
||||
);
|
||||
}
|
||||
|
||||
public static File getBaseDir()
|
||||
{
|
||||
return FMLCommonHandler.instance().getMinecraftServerInstance().getFile(".");
|
||||
@@ -533,6 +614,11 @@ public class ComputerCraft
|
||||
networkEventChannel.sendToServer( encode( packet ) );
|
||||
}
|
||||
|
||||
public static void sendToAllAround( ComputerCraftPacket packet, NetworkRegistry.TargetPoint point )
|
||||
{
|
||||
networkEventChannel.sendToAllAround( encode( packet ), point );
|
||||
}
|
||||
|
||||
public static void handlePacket( ComputerCraftPacket packet, EntityPlayer player )
|
||||
{
|
||||
proxy.handlePacket( packet, player );
|
||||
@@ -644,6 +730,24 @@ public class ComputerCraft
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerAPIFactory( ILuaAPIFactory provider )
|
||||
{
|
||||
if( provider != null )
|
||||
{
|
||||
apiFactories.add( provider );
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerWiredProvider( IWiredProvider provider )
|
||||
{
|
||||
if( provider != null ) wiredProviders.add( provider );
|
||||
}
|
||||
|
||||
public static IWiredNode createWiredNodeForElement( IWiredElement element )
|
||||
{
|
||||
return new WiredNode( element );
|
||||
}
|
||||
|
||||
public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side )
|
||||
{
|
||||
// Try the handlers in order:
|
||||
@@ -665,6 +769,24 @@ public class ComputerCraft
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side )
|
||||
{
|
||||
// Try the handlers in order:
|
||||
for( IWiredProvider provider : wiredProviders )
|
||||
{
|
||||
try
|
||||
{
|
||||
IWiredElement element = provider.getElement( world, pos, side );
|
||||
if( element != null ) return element;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
ComputerCraft.log.error( "Wired element provider " + provider + " errored.", e );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )
|
||||
{
|
||||
if( WorldUtil.isBlockInWorld( world, pos ) )
|
||||
@@ -766,11 +888,16 @@ public class ComputerCraft
|
||||
return upgrades;
|
||||
}
|
||||
|
||||
public IPacketNetwork getWirelessNetwork()
|
||||
public static IPacketNetwork getWirelessNetwork()
|
||||
{
|
||||
return WirelessNetwork.getUniversal();
|
||||
}
|
||||
|
||||
public static Iterable<ILuaAPIFactory> getAPIFactories()
|
||||
{
|
||||
return apiFactories;
|
||||
}
|
||||
|
||||
public static int createUniqueNumberedSaveDir( World world, String parentSubPath )
|
||||
{
|
||||
return IDAssigner.getNextIDFromDirectory(new File(getWorldDir(world), parentSubPath));
|
||||
|
@@ -8,9 +8,13 @@ package dan200.computercraft.api;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.media.IMediaProvider;
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.network.wired.IWiredNode;
|
||||
import dan200.computercraft.api.network.wired.IWiredProvider;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||
@@ -20,6 +24,7 @@ import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -311,6 +316,97 @@ public final class ComputerCraftAPI
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void registerAPIFactory( @Nonnull ILuaAPIFactory upgrade )
|
||||
{
|
||||
findCC();
|
||||
if( computerCraft_registerAPIFactory != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
computerCraft_registerAPIFactory.invoke( null, upgrade );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
// It failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a peripheral handler to convert blocks into {@link IPeripheral} implementations.
|
||||
*
|
||||
* @param handler The peripheral provider to register.
|
||||
* @see dan200.computercraft.api.peripheral.IPeripheral
|
||||
* @see dan200.computercraft.api.peripheral.IPeripheralProvider
|
||||
*/
|
||||
public static void registerWiredProvider( @Nonnull IWiredProvider handler )
|
||||
{
|
||||
findCC();
|
||||
if ( computerCraft_registerWiredProvider != null)
|
||||
{
|
||||
try {
|
||||
computerCraft_registerWiredProvider.invoke( null, handler );
|
||||
} catch (Exception e){
|
||||
// It failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new wired node for a given wired element
|
||||
*
|
||||
* @param element The element to construct it for
|
||||
* @return The element's node
|
||||
* @see IWiredElement#getNode()
|
||||
*/
|
||||
@Nonnull
|
||||
public static IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )
|
||||
{
|
||||
findCC();
|
||||
if( computerCraft_createWiredNodeForElement != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
return (IWiredNode) computerCraft_createWiredNodeForElement.invoke( null, element );
|
||||
}
|
||||
catch( ReflectiveOperationException e )
|
||||
{
|
||||
throw new IllegalStateException( "Error creating wired node", e );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException( "ComputerCraft cannot be found" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wired network element for a block in world
|
||||
*
|
||||
* @param world The world the block exists in
|
||||
* @param pos The position the block exists in
|
||||
* @param side The side to extract the network element from
|
||||
* @return The element's node
|
||||
* @see IWiredElement#getNode()
|
||||
*/
|
||||
@Nullable
|
||||
public static IWiredElement getWiredElementAt( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
|
||||
{
|
||||
findCC();
|
||||
if( computerCraft_getWiredElementAt != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
return (IWiredElement) computerCraft_getWiredElementAt.invoke( null, world, pos, side );
|
||||
}
|
||||
catch( ReflectiveOperationException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// The functions below here are private, and are used to interface with the non-API ComputerCraft classes.
|
||||
// Reflection is used here so you can develop your mod without decompiling ComputerCraft and including
|
||||
// it in your solution, and so your mod won't crash if ComputerCraft is installed.
|
||||
@@ -354,6 +450,18 @@ public final class ComputerCraftAPI
|
||||
} );
|
||||
computerCraft_getWirelessNetwork = findCCMethod( "getWirelessNetwork", new Class<?>[] {
|
||||
} );
|
||||
computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] {
|
||||
ILuaAPIFactory.class
|
||||
} );
|
||||
computerCraft_registerWiredProvider = findCCMethod( "registerWiredProvider", new Class<?>[] {
|
||||
IWiredProvider.class
|
||||
} );
|
||||
computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class<?>[] {
|
||||
IWiredElement.class
|
||||
} );
|
||||
computerCraft_getWiredElementAt = findCCMethod( "getWiredElementAt", new Class<?>[]{
|
||||
IBlockAccess.class, BlockPos.class, EnumFacing.class
|
||||
} );
|
||||
} catch( Exception e ) {
|
||||
System.out.println( "ComputerCraftAPI: ComputerCraft not found." );
|
||||
} finally {
|
||||
@@ -390,4 +498,8 @@ public final class ComputerCraftAPI
|
||||
private static Method computerCraft_registerPermissionProvider = null;
|
||||
private static Method computerCraft_registerPocketUpgrade = null;
|
||||
private static Method computerCraft_getWirelessNetwork = null;
|
||||
private static Method computerCraft_registerAPIFactory = null;
|
||||
private static Method computerCraft_registerWiredProvider = null;
|
||||
private static Method computerCraft_createWiredNodeForElement = null;
|
||||
private static Method computerCraft_getWiredElementAt = null;
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
package dan200.computercraft.api.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a mount of the entire computer's file system.
|
||||
*
|
||||
* This exists for use by various APIs - one should not attempt to mount it.
|
||||
*/
|
||||
public interface IFileSystem extends IWritableMount
|
||||
{
|
||||
/**
|
||||
* Combine two paths together, reducing them into a normalised form.
|
||||
*
|
||||
* @param path The main path.
|
||||
* @param child The path to append.
|
||||
* @return The combined, normalised path.
|
||||
*/
|
||||
String combine( String path, String child );
|
||||
|
||||
/**
|
||||
* Copy files from one location to another.
|
||||
*
|
||||
* @param from The location to copy from.
|
||||
* @param to The location to copy to. This should not exist.
|
||||
* @throws IOException If the copy failed.
|
||||
*/
|
||||
void copy( String from, String to ) throws IOException;
|
||||
|
||||
/**
|
||||
* Move files from one location to another.
|
||||
*
|
||||
* @param from The location to move from.
|
||||
* @param to The location to move to. This should not exist.
|
||||
* @throws IOException If the move failed.
|
||||
*/
|
||||
void move( String from, String to ) throws IOException;
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
|
||||
* about a computer.
|
||||
*/
|
||||
public interface IComputerSystem extends IComputerAccess
|
||||
{
|
||||
/**
|
||||
* Get the file system for this computer.
|
||||
*
|
||||
* @return The computer's file system, or {@code null} if it is not initialised.
|
||||
*/
|
||||
@Nullable
|
||||
IFileSystem getFileSystem();
|
||||
|
||||
/**
|
||||
* Get the label for this computer
|
||||
*
|
||||
* @return This computer's label, or {@code null} if it is not set.
|
||||
*/
|
||||
@Nullable
|
||||
String getLabel();
|
||||
}
|
47
src/main/java/dan200/computercraft/api/lua/ILuaAPI.java
Normal file
47
src/main/java/dan200/computercraft/api/lua/ILuaAPI.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2016. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
|
||||
/**
|
||||
* Represents a {@link ILuaObject} which is stored as a global variable on computer startup.
|
||||
*
|
||||
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
|
||||
* to use peripherals to provide functionality to users.
|
||||
*
|
||||
* @see ILuaAPIFactory
|
||||
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
*/
|
||||
public interface ILuaAPI extends ILuaObject
|
||||
{
|
||||
/**
|
||||
* Get the globals this API will be assigned to. This will override any other global, so you should
|
||||
*
|
||||
* @return A list of globals this API will be assigned to.
|
||||
*/
|
||||
String[] getNames();
|
||||
|
||||
/**
|
||||
* Called when the computer is turned on.
|
||||
*
|
||||
* One should only interact with the file system.
|
||||
*/
|
||||
default void startup() { }
|
||||
|
||||
/**
|
||||
* Called every time the computer is ticked. This can be used to process various.
|
||||
*/
|
||||
default void update() { }
|
||||
|
||||
/**
|
||||
* Called when the computer is turned off or unloaded.
|
||||
*
|
||||
* This should reset the state of the object, disposing any remaining file handles, or other resources.
|
||||
*/
|
||||
default void shutdown() { }
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Construct an {@link ILuaAPI} for a specific computer.
|
||||
*
|
||||
* @see ILuaAPI
|
||||
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
*/
|
||||
public interface ILuaAPIFactory
|
||||
{
|
||||
/**
|
||||
* Create a new API instance for a given computer.
|
||||
*
|
||||
* @param computer The computer this API is for.
|
||||
* @return The created API, or {@code null} if one should not be injected.
|
||||
*/
|
||||
@Nullable
|
||||
ILuaAPI create( @Nonnull IComputerSystem computer );
|
||||
}
|
@@ -41,7 +41,8 @@ public interface IPacketNetwork
|
||||
* to all receivers within range (or any interdimensional ones).
|
||||
*
|
||||
* @param packet The packet to send.
|
||||
* @see #transmitInterdimensional(Packet)
|
||||
* @param range The maximum distance this packet will be sent.
|
||||
* @see #transmitInterdimensional(Packet)
|
||||
* @see IPacketReceiver#receiveSameDimension(Packet, double)
|
||||
*/
|
||||
void transmitSameDimension( @Nonnull Packet packet, double range );
|
||||
@@ -51,7 +52,7 @@ public interface IPacketNetwork
|
||||
* to all receivers across all dimensions.
|
||||
*
|
||||
* @param packet The packet to send.
|
||||
* @see #transmitSameDimension(Packet, double)
|
||||
* @see #transmitSameDimension(Packet, double)
|
||||
* @see IPacketReceiver#receiveDifferentDimension(Packet)
|
||||
*/
|
||||
void transmitInterdimensional( @Nonnull Packet packet );
|
||||
|
@@ -0,0 +1,51 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An object which may be part of a wired network.
|
||||
*
|
||||
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts
|
||||
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
|
||||
* for its lifespan.
|
||||
*
|
||||
* Elements are generally tied to a block or tile entity in world. One should either register an {@link IWiredProvider}
|
||||
* or implement {@link IWiredElementTile} on your tile entity.
|
||||
*
|
||||
* @see IWiredProvider
|
||||
* @see ComputerCraftAPI#registerWiredProvider(IWiredProvider)
|
||||
* @see IWiredElementTile
|
||||
*/
|
||||
public interface IWiredElement extends IWiredSender
|
||||
{
|
||||
/**
|
||||
* Fetch the peripherals this network element provides.
|
||||
*
|
||||
* This is only called when initially attaching to a network and after a call to {@link IWiredNode#invalidate()}}, so
|
||||
* one does not <em>need</em> to cache the return value.
|
||||
*
|
||||
* @return The peripherals this node provides.
|
||||
* @see IWiredNode#invalidate()
|
||||
*/
|
||||
@Nonnull
|
||||
default Map<String, IPeripheral> getPeripherals()
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
|
||||
* peripherals change.
|
||||
*
|
||||
* @param change The change which occurred.
|
||||
* @see IWiredNetworkChange
|
||||
*/
|
||||
default void networkChanged( @Nonnull IWiredNetworkChange change )
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import net.minecraft.util.EnumFacing;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link net.minecraft.tileentity.TileEntity} which provides a {@link IWiredElement}. This acts
|
||||
* as a simpler alternative to a full-blown {@link IWiredProvider}.
|
||||
*/
|
||||
public interface IWiredElementTile
|
||||
{
|
||||
/**
|
||||
* Get the wired element of this tile for a given side.
|
||||
*
|
||||
* @param side The side to get the network element from.
|
||||
* @return A network element, or {@code null} if there is no element here.
|
||||
*/
|
||||
@Nullable
|
||||
IWiredElement getWiredElement( @Nonnull EnumFacing side );
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
|
||||
* of peripherals.
|
||||
*
|
||||
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
|
||||
* there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically
|
||||
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
|
||||
* change.
|
||||
*
|
||||
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
|
||||
* it is generally preferred to use the methods provided by {@link IWiredNode}.
|
||||
*
|
||||
* @see IWiredNode#getNetwork()
|
||||
*/
|
||||
public interface IWiredNetwork
|
||||
{
|
||||
/**
|
||||
* Create a connection between two nodes.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param left The first node to connect
|
||||
* @param right The second node to connect
|
||||
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
||||
* @throws IllegalStateException If neither node is on the network.
|
||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||
* @see IWiredNode#connectTo(IWiredNode)
|
||||
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
|
||||
*/
|
||||
boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
|
||||
|
||||
/**
|
||||
* Destroy a connection between this node and another.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param left The first node in the connection.
|
||||
* @param right The second node in the connection.
|
||||
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
||||
* @throws IllegalArgumentException If either node is not on the network.
|
||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||
* @see IWiredNode#disconnectFrom(IWiredNode)
|
||||
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
|
||||
*/
|
||||
boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
|
||||
|
||||
/**
|
||||
* Sever all connections this node has, removing it from this network.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param node The node to remove
|
||||
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
||||
* only element.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see IWiredNode#remove()
|
||||
*/
|
||||
boolean remove( @Nonnull IWiredNode node );
|
||||
|
||||
/**
|
||||
* Mark this node's peripherals as having changed.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param node The node to mark as invalid.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see IWiredElement#getPeripherals()
|
||||
*/
|
||||
void invalidate( @Nonnull IWiredNode node );
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a change to the objects on a wired network.
|
||||
*
|
||||
* @see IWiredElement#networkChanged(IWiredNetworkChange)
|
||||
*/
|
||||
public interface IWiredNetworkChange
|
||||
{
|
||||
/**
|
||||
* A set of peripherals which have been removed. Note that there may be entries with the same name
|
||||
* in the added and removed set, but with a different peripheral.
|
||||
*
|
||||
* @return The set of removed peripherals.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<String, IPeripheral> peripheralsRemoved();
|
||||
|
||||
/**
|
||||
* A set of peripherals which have been added. Note that there may be entries with the same name
|
||||
* in the added and removed set, but with a different peripheral.
|
||||
*
|
||||
* @return The set of added peripherals.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<String, IPeripheral> peripheralsAdded();
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
|
||||
*
|
||||
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
|
||||
* methods may be safely used on any thread.
|
||||
*
|
||||
* When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
|
||||
* element's position. Note that packet senders and receivers can have different locations from their associated
|
||||
* element: the distance between the two will be added to the total packet's distance.
|
||||
*
|
||||
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
|
||||
* be used on the main server thread.
|
||||
*/
|
||||
public interface IWiredNode extends IPacketNetwork
|
||||
{
|
||||
/**
|
||||
* The associated element for this network node.
|
||||
*
|
||||
* @return This node's element.
|
||||
*/
|
||||
@Nonnull
|
||||
IWiredElement getElement();
|
||||
|
||||
/**
|
||||
* The network this node is currently connected to. Note that this may change
|
||||
* after any network operation, so it should not be cached.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @return This node's network.
|
||||
*/
|
||||
@Nonnull
|
||||
IWiredNetwork getNetwork();
|
||||
|
||||
/**
|
||||
* Create a connection from this node to another.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param node The other node to connect to.
|
||||
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
||||
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
|
||||
* @see IWiredNode#disconnectFrom(IWiredNode)
|
||||
*/
|
||||
default boolean connectTo( @Nonnull IWiredNode node )
|
||||
{
|
||||
return getNetwork().connect( this, node );
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a connection between this node and another.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param node The other node to disconnect from.
|
||||
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
||||
* @throws IllegalArgumentException If {@code node} is not on the same network.
|
||||
* @see IWiredNetwork#disconnect(IWiredNode, IWiredNode)
|
||||
* @see IWiredNode#connectTo(IWiredNode)
|
||||
*/
|
||||
default boolean disconnectFrom( @Nonnull IWiredNode node )
|
||||
{
|
||||
return getNetwork().disconnect( this, node );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sever all connections this node has, removing it from this network.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
||||
* only element.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see IWiredNetwork#remove(IWiredNode)
|
||||
*/
|
||||
default boolean remove()
|
||||
{
|
||||
return getNetwork().remove( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this node's peripherals as having changed.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @see IWiredElement#getPeripherals()
|
||||
*/
|
||||
default void invalidate()
|
||||
{
|
||||
getNetwork().invalidate( this );
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Fetch or create an {@link IWiredElement} for a block at a given position.
|
||||
*
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerWiredProvider(IWiredProvider)
|
||||
* @see IWiredElementTile
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IWiredProvider
|
||||
{
|
||||
/**
|
||||
* Extract a wired network element from a block location.
|
||||
*
|
||||
* @param world The world the block is in.
|
||||
* @param pos The position the block is at.
|
||||
* @param side The side to get the network element from.
|
||||
* @return A network element, or {@code null} if there is not an element here you'd like to handle.
|
||||
*/
|
||||
@Nullable
|
||||
IWiredElement getElement( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side );
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.network.IPacketSender;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An object on a {@link IWiredNetwork} capable of sending packets.
|
||||
*
|
||||
* Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to
|
||||
* to send the packet from.
|
||||
*/
|
||||
public interface IWiredSender extends IPacketSender
|
||||
{
|
||||
/**
|
||||
* The node in the network representing this object.
|
||||
*
|
||||
* This should be used as a proxy for the main network. One should send packets
|
||||
* and register receivers through this object.
|
||||
*
|
||||
* @return The node for this element.
|
||||
*/
|
||||
@Nonnull
|
||||
IWiredNode getNode();
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.
|
||||
*/
|
||||
|
||||
@API( owner="ComputerCraft", provides="ComputerCraft|API|Network|Wired", apiVersion="${version}" )
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import net.minecraftforge.fml.common.API;
|
@@ -13,6 +13,8 @@ import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The interface passed to peripherals by computers or turtles, providing methods
|
||||
@@ -154,4 +156,33 @@ public interface IComputerAccess
|
||||
*/
|
||||
@Nonnull
|
||||
String getAttachmentName();
|
||||
|
||||
/**
|
||||
* Get a set of peripherals that this computer access can "see", along with their attachment name.
|
||||
*
|
||||
* This may include other peripherals on the wired network or peripherals on other sides of the computer.
|
||||
*
|
||||
* @return All reachable peripherals
|
||||
* @see #getAttachmentName()
|
||||
* @see #getAvailablePeripheral(String)
|
||||
*/
|
||||
@Nonnull
|
||||
default Map<String, IPeripheral> getAvailablePeripherals()
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reachable peripheral with the given attachement name. This is a equivalent to
|
||||
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant.
|
||||
*
|
||||
* @param name The peripheral's attached name
|
||||
* @return The reachable peripheral, or {@code null} if none can be found.
|
||||
* @see #getAvailablePeripherals()
|
||||
*/
|
||||
@Nullable
|
||||
default IPeripheral getAvailablePeripheral( @Nonnull String name )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -114,6 +114,18 @@ public interface IPeripheral
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object that this peripheral provides methods for. This will generally be the tile entity
|
||||
* or block, but may be an inventory, entity, etc...
|
||||
*
|
||||
* @return The object this peripheral targets
|
||||
*/
|
||||
@Nonnull
|
||||
default Object getTarget()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this peripheral is equivalent to another one.
|
||||
*
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@@ -135,6 +136,14 @@ public interface ITurtleAccess
|
||||
*/
|
||||
int getColour();
|
||||
|
||||
/**
|
||||
* Get the player who owns this turtle, namely whoever placed it.
|
||||
*
|
||||
* @return This turtle's owner.
|
||||
*/
|
||||
@Nonnull
|
||||
GameProfile getOwningPlayer();
|
||||
|
||||
/**
|
||||
* Get the inventory of this turtle
|
||||
*
|
||||
@@ -148,7 +157,7 @@ public interface ITurtleAccess
|
||||
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
|
||||
*
|
||||
* @return This turtle's inventory
|
||||
* @see #getInventory()
|
||||
* @see #getInventory()
|
||||
* @see IItemHandlerModifiable
|
||||
* @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
|
||||
*/
|
||||
|
@@ -8,11 +8,15 @@ package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
|
||||
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
|
||||
import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -100,6 +104,9 @@ 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.
|
||||
*
|
||||
* @param turtle Access to the turtle that the tool resides on.
|
||||
* @param side Which side of the turtle (left or right) the tool resides on.
|
||||
* @param verb Which action (dig or attack) the turtle is being called on to perform.
|
||||
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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;
|
||||
|
||||
/**
|
||||
* A basic action that a turtle may perform, as accessed by the {@code turtle} API.
|
||||
*
|
||||
* @see TurtleActionEvent
|
||||
*/
|
||||
public enum TurtleAction
|
||||
{
|
||||
/**
|
||||
* A turtle moves to a new position.
|
||||
*
|
||||
* @see TurtleBlockEvent.Move
|
||||
*/
|
||||
MOVE,
|
||||
|
||||
/**
|
||||
* A turtle turns in a specific direction.
|
||||
*/
|
||||
TURN,
|
||||
|
||||
/**
|
||||
* A turtle attempts to dig a block.
|
||||
*
|
||||
* @see TurtleBlockEvent.Dig
|
||||
*/
|
||||
DIG,
|
||||
|
||||
/**
|
||||
* A turtle attempts to place a block or item in the world.
|
||||
*
|
||||
* @see TurtleBlockEvent.Place
|
||||
*/
|
||||
PLACE,
|
||||
|
||||
/**
|
||||
* A turtle attempts to attack an entity.
|
||||
*
|
||||
* @see TurtleActionEvent
|
||||
*/
|
||||
ATTACK,
|
||||
|
||||
/**
|
||||
* Drop an item into an inventory/the world.
|
||||
*
|
||||
* @see TurtleInventoryEvent.Drop
|
||||
*/
|
||||
DROP,
|
||||
|
||||
/**
|
||||
* Suck an item from an inventory or the world.
|
||||
*
|
||||
* @see TurtleInventoryEvent.Suck
|
||||
*/
|
||||
SUCK,
|
||||
|
||||
/**
|
||||
* Refuel the turtle's fuel levels.
|
||||
*/
|
||||
REFUEL,
|
||||
|
||||
/**
|
||||
* Equip or unequip an item.
|
||||
*/
|
||||
EQUIP,
|
||||
|
||||
/**
|
||||
* Inspect a block in world
|
||||
*
|
||||
* @see TurtleBlockEvent.Inspect
|
||||
*/
|
||||
INSPECT,
|
||||
|
||||
/**
|
||||
* Gather metdata about an item in the turtle's inventory.
|
||||
*/
|
||||
INSPECT_ITEM,
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An event fired when a turtle is performing a known action.
|
||||
*/
|
||||
@Cancelable
|
||||
public class TurtleActionEvent extends TurtleEvent
|
||||
{
|
||||
private final TurtleAction action;
|
||||
private String failureMessage;
|
||||
|
||||
public TurtleActionEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action )
|
||||
{
|
||||
super( turtle );
|
||||
|
||||
Preconditions.checkNotNull( action, "action cannot be null" );
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public TurtleAction getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cancellation state of this action.
|
||||
*
|
||||
* If {@code cancel} is {@code true}, this action will not be carried out.
|
||||
*
|
||||
* @param cancel The new canceled value.
|
||||
* @see TurtleCommandResult#failure()
|
||||
* @deprecated Use {@link #setCanceled(boolean, String)} instead.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setCanceled( boolean cancel )
|
||||
{
|
||||
setCanceled( cancel, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cancellation state of this action, setting a failure message if required.
|
||||
*
|
||||
* If {@code cancel} is {@code true}, this action will not be carried out.
|
||||
*
|
||||
* @param cancel The new canceled value.
|
||||
* @param failureMessage The message to return to the user explaining the failure.
|
||||
* @see TurtleCommandResult#failure(String)
|
||||
*/
|
||||
public void setCanceled( boolean cancel, @Nullable String failureMessage )
|
||||
{
|
||||
super.setCanceled( cancel );
|
||||
this.failureMessage = cancel ? failureMessage : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message with which this will fail.
|
||||
*
|
||||
* @return The failure message.
|
||||
* @see TurtleCommandResult#failure()
|
||||
* @see #setCanceled(boolean, String)
|
||||
*/
|
||||
@Nullable
|
||||
public String getFailureMessage()
|
||||
{
|
||||
return failureMessage;
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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 com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.turtle.TurtleVerb;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to attack an entity.
|
||||
*
|
||||
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)},
|
||||
* as the base {@code turtle.attack()} command does not fire it.
|
||||
*
|
||||
* Note that such commands should also fire {@link AttackEntityEvent}, so you do not need to listen to both.
|
||||
*
|
||||
* @see TurtleAction#ATTACK
|
||||
*/
|
||||
public class TurtleAttackEvent extends TurtlePlayerEvent
|
||||
{
|
||||
private final Entity target;
|
||||
private final ITurtleUpgrade upgrade;
|
||||
private final TurtleSide side;
|
||||
|
||||
public TurtleAttackEvent( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull Entity target, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
|
||||
{
|
||||
super( turtle, TurtleAction.ATTACK, player );
|
||||
Preconditions.checkNotNull( target, "target cannot be null" );
|
||||
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
|
||||
Preconditions.checkNotNull( side, "side cannot be null" );
|
||||
this.target = target;
|
||||
this.upgrade = upgrade;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity being attacked by this turtle.
|
||||
*
|
||||
* @return The entity being attacked.
|
||||
*/
|
||||
@Nonnull
|
||||
public Entity getTarget()
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upgrade responsible for attacking.
|
||||
*
|
||||
* @return The upgrade responsible for attacking.
|
||||
*/
|
||||
@Nonnull
|
||||
public ITurtleUpgrade getUpgrade()
|
||||
{
|
||||
return upgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the side the attacking upgrade is on.
|
||||
*
|
||||
* @return The upgrade's side.
|
||||
*/
|
||||
@Nonnull
|
||||
public TurtleSide getSide()
|
||||
{
|
||||
return side;
|
||||
}
|
||||
}
|
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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 com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.turtle.TurtleVerb;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A general event for when a turtle interacts with a block or region.
|
||||
*
|
||||
* You should generally listen to one of the sub-events instead, cancelling them where
|
||||
* appropriate.
|
||||
*
|
||||
* Note that you are not guaranteed to receive this event, if it has been cancelled by other
|
||||
* mechanisms, such as block protection systems.
|
||||
*
|
||||
* Be aware that some events (such as {@link TurtleInventoryEvent}) do not necessarily interact
|
||||
* with a block, simply objects within that block space.
|
||||
*/
|
||||
@Cancelable
|
||||
public abstract class TurtleBlockEvent extends TurtlePlayerEvent
|
||||
{
|
||||
private final World world;
|
||||
private final BlockPos pos;
|
||||
|
||||
protected TurtleBlockEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
|
||||
{
|
||||
super( turtle, action, player );
|
||||
|
||||
Preconditions.checkNotNull( world, "world cannot be null" );
|
||||
Preconditions.checkNotNull( pos, "pos cannot be null" );
|
||||
this.world = world;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the world the turtle is interacting in.
|
||||
*
|
||||
* @return The world the turtle is interacting in.
|
||||
*/
|
||||
public World getWorld()
|
||||
{
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position the turtle is interacting with. Note that this is different
|
||||
* to {@link ITurtleAccess#getPosition()}.
|
||||
*
|
||||
* @return The position the turtle is interacting with.
|
||||
*/
|
||||
public BlockPos getPos()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to dig a block.
|
||||
*
|
||||
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)},
|
||||
* as the base {@code turtle.dig()} command does not fire it.
|
||||
*
|
||||
* Note that such commands should also fire {@link BlockEvent.BreakEvent}, so you do not need to listen to both.
|
||||
*
|
||||
* @see TurtleAction#DIG
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Dig extends TurtleBlockEvent
|
||||
{
|
||||
private final IBlockState block;
|
||||
private final ITurtleUpgrade upgrade;
|
||||
private final TurtleSide side;
|
||||
|
||||
public Dig( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState block, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
|
||||
{
|
||||
super( turtle, TurtleAction.DIG, player, world, pos );
|
||||
|
||||
Preconditions.checkNotNull( block, "block cannot be null" );
|
||||
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
|
||||
Preconditions.checkNotNull( side, "side cannot be null" );
|
||||
this.block = block;
|
||||
this.upgrade = upgrade;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block which is about to be broken.
|
||||
*
|
||||
* @return The block which is going to be broken.
|
||||
*/
|
||||
@Nonnull
|
||||
public IBlockState getBlock()
|
||||
{
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upgrade doing the digging
|
||||
*
|
||||
* @return The upgrade doing the digging.
|
||||
*/
|
||||
@Nonnull
|
||||
public ITurtleUpgrade getUpgrade()
|
||||
{
|
||||
return upgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the side the upgrade doing the digging is on.
|
||||
*
|
||||
* @return The upgrade's side.
|
||||
*/
|
||||
@Nonnull
|
||||
public TurtleSide getSide()
|
||||
{
|
||||
return side;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to move into a block.
|
||||
*
|
||||
* @see TurtleAction#MOVE
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Move extends TurtleBlockEvent
|
||||
{
|
||||
public Move( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
|
||||
{
|
||||
super( turtle, TurtleAction.MOVE, player, world, pos );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to place a block in the world.
|
||||
*
|
||||
* @see TurtleAction#PLACE
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Place extends TurtleBlockEvent
|
||||
{
|
||||
private final ItemStack stack;
|
||||
|
||||
public Place( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull ItemStack stack )
|
||||
{
|
||||
super( turtle, TurtleAction.PLACE, player, world, pos );
|
||||
|
||||
Preconditions.checkNotNull( stack, "stack cannot be null" );
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item stack that will be placed. This should not be modified.
|
||||
*
|
||||
* @return The item stack to be placed.
|
||||
*/
|
||||
@Nonnull
|
||||
public ItemStack getStack()
|
||||
{
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle gathers data on a block in world.
|
||||
*
|
||||
* You may prevent blocks being inspected, or add additional information to the result.
|
||||
*
|
||||
* @see TurtleAction#INSPECT
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Inspect extends TurtleBlockEvent
|
||||
{
|
||||
private final IBlockState state;
|
||||
private final Map<String, Object> data;
|
||||
|
||||
public Inspect( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Map<String, Object> data )
|
||||
{
|
||||
super( turtle, TurtleAction.INSPECT, player, world, pos );
|
||||
|
||||
Preconditions.checkNotNull( state, "state cannot be null" );
|
||||
Preconditions.checkNotNull( data, "data cannot be null" );
|
||||
this.data = data;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block state which is being inspected.
|
||||
*
|
||||
* @return The inspected block state.
|
||||
*/
|
||||
@Nonnull
|
||||
public IBlockState getState()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "inspection data" from this block, which will be returned to the user.
|
||||
*
|
||||
* @return This block's inspection data.
|
||||
*/
|
||||
@Nonnull
|
||||
public Map<String, Object> getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new information to the inspection result. Note this will override fields with the same name.
|
||||
*
|
||||
* @param newData The data to add. Note all values should be convertable to Lua (see
|
||||
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
|
||||
*/
|
||||
public void addData( @Nonnull Map<String, ?> newData )
|
||||
{
|
||||
Preconditions.checkNotNull( newData, "newData cannot be null" );
|
||||
data.putAll( newData );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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 com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraftforge.fml.common.eventhandler.Event;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A base class for all events concerning a turtle. This will only ever constructed and fired on the server side,
|
||||
* so sever specific methods on {@link ITurtleAccess} are safe to use.
|
||||
*
|
||||
* You should generally not need to subscribe to this event, preferring one of the more specific classes.
|
||||
*
|
||||
* @see TurtleActionEvent
|
||||
*/
|
||||
public abstract class TurtleEvent extends Event
|
||||
{
|
||||
private final ITurtleAccess turtle;
|
||||
|
||||
protected TurtleEvent( @Nonnull ITurtleAccess turtle )
|
||||
{
|
||||
Preconditions.checkNotNull( turtle, "turtle cannot be null" );
|
||||
this.turtle = turtle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the turtle which is performing this action.
|
||||
*
|
||||
* @return The access for this turtle.
|
||||
*/
|
||||
@Nonnull
|
||||
public ITurtleAccess getTurtle()
|
||||
{
|
||||
return turtle;
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to interact with an inventory.
|
||||
*/
|
||||
@Cancelable
|
||||
public abstract class TurtleInventoryEvent extends TurtleBlockEvent
|
||||
{
|
||||
private final IItemHandler handler;
|
||||
|
||||
protected TurtleInventoryEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
|
||||
{
|
||||
super( turtle, action, player, world, pos );
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inventory being interacted with
|
||||
*
|
||||
* @return The inventory being interacted with, {@code null} if the item will be dropped to/sucked from the world.
|
||||
*/
|
||||
@Nullable
|
||||
public IItemHandler getItemHandler()
|
||||
{
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to suck from an inventory.
|
||||
*
|
||||
* @see TurtleAction#SUCK
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Suck extends TurtleInventoryEvent
|
||||
{
|
||||
public Suck( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
|
||||
{
|
||||
super( turtle, TurtleAction.SUCK, player, world, pos, handler );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to drop an item into an inventory.
|
||||
*
|
||||
* @see TurtleAction#DROP
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Drop extends TurtleInventoryEvent
|
||||
{
|
||||
private final ItemStack stack;
|
||||
|
||||
public Drop( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler, @Nonnull ItemStack stack )
|
||||
{
|
||||
super( turtle, TurtleAction.DROP, player, world, pos, handler );
|
||||
|
||||
Preconditions.checkNotNull( stack, "stack cannot be null" );
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* The item which will be inserted into the inventory/dropped on the ground.
|
||||
*
|
||||
* Note that this is a copy of the original stack, and so should not be modified, as that will have no effect.
|
||||
*
|
||||
* @return The item stack which will be dropped.
|
||||
*/
|
||||
public ItemStack getStack()
|
||||
{
|
||||
return stack.copy();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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 com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An action done by a turtle which is normally done by a player.
|
||||
*
|
||||
* {@link #getPlayer()} may be used to modify the player's attributes or perform permission checks.
|
||||
*/
|
||||
public abstract class TurtlePlayerEvent extends TurtleActionEvent
|
||||
{
|
||||
private final FakePlayer player;
|
||||
|
||||
protected TurtlePlayerEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player )
|
||||
{
|
||||
super( turtle, action );
|
||||
|
||||
Preconditions.checkNotNull( player, "player cannot be null" );
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake player, representing this turtle.
|
||||
*
|
||||
* This may be used for triggering permission checks.
|
||||
*
|
||||
* @return A {@link FakePlayer} representing this turtle.
|
||||
*/
|
||||
@Nonnull
|
||||
public FakePlayer getPlayer()
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.
|
||||
*/
|
||||
|
||||
@API(owner = "ComputerCraft", provides = "ComputerCraft|API|Turtle|Event", apiVersion = "${version}")
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import net.minecraftforge.fml.common.API;
|
@@ -141,10 +141,10 @@ public class FixedWidthFontRenderer
|
||||
}
|
||||
|
||||
// Draw char
|
||||
int index = (int)s.charAt( i );
|
||||
int index = s.charAt( i );
|
||||
if( index < 0 || index > 255 )
|
||||
{
|
||||
index = (int)'?';
|
||||
index = '?';
|
||||
}
|
||||
drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale );
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ public class GuiComputer extends GuiContainer
|
||||
private final int m_termHeight;
|
||||
private WidgetTerminal m_terminal;
|
||||
|
||||
protected GuiComputer( Container container, ComputerFamily family, IComputer computer, int termWidth, int termHeight )
|
||||
public GuiComputer( Container container, ComputerFamily family, IComputer computer, int termWidth, int termHeight )
|
||||
{
|
||||
super( container );
|
||||
m_family = family;
|
||||
|
@@ -18,7 +18,7 @@ public class GuiConfigCC extends GuiConfig
|
||||
{
|
||||
public GuiConfigCC( GuiScreen parentScreen )
|
||||
{
|
||||
super( parentScreen, getConfigElements(), ComputerCraft.MOD_ID, false, false, "ComputerCraft" );
|
||||
super( parentScreen, getConfigElements(), ComputerCraft.MOD_ID, false, false, "CC: Tweaked" );
|
||||
}
|
||||
|
||||
private static List<IConfigElement> getConfigElements()
|
||||
|
@@ -43,4 +43,12 @@ public class GuiDiskDrive extends GuiContainer
|
||||
int i1 = (height - ySize) / 2;
|
||||
drawTexturedModalRect(l, i1, 0, 0, xSize, ySize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen( int mouseX, int mouseY, float partialTicks)
|
||||
{
|
||||
drawDefaultBackground();
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
renderHoveredToolTip(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
@@ -51,4 +51,12 @@ public class GuiPrinter extends GuiContainer
|
||||
drawTexturedModalRect(startX + 34, startY + 21, 176, 0, 25, 45);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen( int mouseX, int mouseY, float partialTicks)
|
||||
{
|
||||
drawDefaultBackground();
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
renderHoveredToolTip(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
@@ -155,4 +155,12 @@ public class GuiTurtle extends GuiContainer
|
||||
|
||||
drawSelectionSlot( advanced );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen( int mouseX, int mouseY, float partialTicks)
|
||||
{
|
||||
drawDefaultBackground();
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
renderHoveredToolTip(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
@@ -175,10 +175,10 @@ public abstract class Widget extends Gui
|
||||
{
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
tessellator.getBuffer().begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
|
||||
tessellator.getBuffer().pos( (double) ( x + 0 ), (double) ( y + h ), (double) this.zLevel ).tex( 0.0, 1.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( (double) ( x + w ), (double) ( y + h ), (double) this.zLevel ).tex( 1.0, 1.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( (double) ( x + w ), (double) ( y + 0 ), (double) this.zLevel ).tex( 1.0, 0.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( (double) ( x + 0 ), (double) ( y + 0 ), (double) this.zLevel ).tex( 0.0, 0.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( x + 0, y + h, this.zLevel ).tex( 0.0, 1.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( x + w, y + h, this.zLevel ).tex( 1.0, 1.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( x + w, y + 0, this.zLevel ).tex( 1.0, 0.0 ).endVertex();
|
||||
tessellator.getBuffer().pos( x + 0, y + 0, this.zLevel ).tex( 0.0, 0.0 ).endVertex();
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.block.model.ModelBakery;
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
|
||||
import net.minecraft.client.renderer.color.IItemColor;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.SimpleReloadableResourceManager;
|
||||
@@ -115,8 +116,23 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
|
||||
MinecraftForge.EVENT_BUS.register( handlers );
|
||||
}
|
||||
|
||||
public class ForgeHandlers
|
||||
public static class ForgeHandlers
|
||||
{
|
||||
private static final String[] TURTLE_UPGRADES = {
|
||||
"turtle_modem_off_left",
|
||||
"turtle_modem_on_left",
|
||||
"turtle_modem_off_right",
|
||||
"turtle_modem_on_right",
|
||||
"turtle_crafting_table_left",
|
||||
"turtle_crafting_table_right",
|
||||
"advanced_turtle_modem_off_left",
|
||||
"advanced_turtle_modem_on_left",
|
||||
"advanced_turtle_modem_off_right",
|
||||
"advanced_turtle_modem_on_right",
|
||||
"turtle_speaker_upgrade_left",
|
||||
"turtle_speaker_upgrade_right",
|
||||
};
|
||||
|
||||
private TurtleSmartItemModel m_turtleSmartItemModel;
|
||||
|
||||
public ForgeHandlers()
|
||||
@@ -142,24 +158,27 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchEvent( TextureStitchEvent.Pre event )
|
||||
{
|
||||
event.getMap().registerSprite( new ResourceLocation( "computercraft", "blocks/crafty_upgrade" ) );
|
||||
// Load all textures for upgrades
|
||||
TextureMap map = event.getMap();
|
||||
for( String upgrade : TURTLE_UPGRADES )
|
||||
{
|
||||
IModel model = ModelLoaderRegistry.getModelOrMissing( new ResourceLocation( "computercraft", "block/" + upgrade ) );
|
||||
for( ResourceLocation texture : model.getTextures() )
|
||||
{
|
||||
map.registerSprite( texture );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onModelBakeEvent( ModelBakeEvent event )
|
||||
{
|
||||
loadModel( event, "turtle_modem_off_left" );
|
||||
loadModel( event, "turtle_modem_on_left" );
|
||||
loadModel( event, "turtle_modem_off_right" );
|
||||
loadModel( event, "turtle_modem_on_right" );
|
||||
loadModel( event, "turtle_crafting_table_left" );
|
||||
loadModel( event, "turtle_crafting_table_right" );
|
||||
loadModel( event, "advanced_turtle_modem_off_left" );
|
||||
loadModel( event, "advanced_turtle_modem_on_left" );
|
||||
loadModel( event, "advanced_turtle_modem_off_right" );
|
||||
loadModel( event, "advanced_turtle_modem_on_right" );
|
||||
loadModel( event, "turtle_speaker_upgrade_left" );
|
||||
loadModel( event, "turtle_speaker_upgrade_right" );
|
||||
// Load all upgrade models
|
||||
for( String upgrade : TURTLE_UPGRADES )
|
||||
{
|
||||
loadModel( event, upgrade );
|
||||
}
|
||||
|
||||
loadSmartModel( event, "turtle_dynamic", m_turtleSmartItemModel );
|
||||
}
|
||||
|
||||
|
@@ -8,17 +8,23 @@ package dan200.computercraft.client.proxy;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.render.ItemPocketRenderer;
|
||||
import dan200.computercraft.client.render.RenderOverlayCable;
|
||||
import dan200.computercraft.client.render.TileEntityCableRenderer;
|
||||
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
|
||||
import dan200.computercraft.shared.command.ContainerViewComputer;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerState;
|
||||
import dan200.computercraft.shared.computer.blocks.TileComputer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.computer.items.ItemComputer;
|
||||
import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
|
||||
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
|
||||
import dan200.computercraft.shared.media.items.ItemPrintout;
|
||||
import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
|
||||
import dan200.computercraft.shared.peripheral.modem.TileCable;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
|
||||
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
|
||||
@@ -51,7 +57,6 @@ import net.minecraftforge.client.event.RenderHandEvent;
|
||||
import net.minecraftforge.client.event.RenderPlayerEvent;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.client.FMLClientHandler;
|
||||
import net.minecraftforge.fml.client.registry.ClientRegistry;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
@@ -110,6 +115,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" );
|
||||
registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" );
|
||||
registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" );
|
||||
registerItemModel( ComputerCraft.Blocks.wiredModemFull, "wired_modem_full" );
|
||||
|
||||
registerItemModel( ComputerCraft.Items.disk, "disk" );
|
||||
registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" );
|
||||
@@ -222,6 +228,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
|
||||
// Setup renderers
|
||||
ClientRegistry.bindTileEntitySpecialRenderer( TileMonitor.class, new TileEntityMonitorRenderer() );
|
||||
ClientRegistry.bindTileEntitySpecialRenderer( TileCable.class, new TileEntityCableRenderer() );
|
||||
}
|
||||
|
||||
private void registerItemModel( Block block, int damage, String name )
|
||||
@@ -314,17 +321,6 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playRecord( SoundEvent record, String recordInfo, World world, BlockPos pos )
|
||||
{
|
||||
Minecraft mc = FMLClientHandler.instance().getClient();
|
||||
world.playRecord( pos, record );
|
||||
if( record != null )
|
||||
{
|
||||
mc.ingameGUI.setRecordPlayingMessage( recordInfo );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDiskDriveGUI( InventoryPlayer inventory, TileDiskDrive drive )
|
||||
{
|
||||
@@ -371,6 +367,13 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getComputerGUI( IComputer computer, int width, int height, ComputerFamily family )
|
||||
{
|
||||
ContainerViewComputer container = new ContainerViewComputer( computer );
|
||||
return new GuiComputer( container, family, computer, width, height );
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getWorldDir( World world )
|
||||
{
|
||||
@@ -383,7 +386,9 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
switch( packet.m_packetType )
|
||||
{
|
||||
case ComputerCraftPacket.ComputerChanged:
|
||||
case ComputerCraftPacket.ComputerTerminalChanged:
|
||||
case ComputerCraftPacket.ComputerDeleted:
|
||||
case ComputerCraftPacket.PlayRecord:
|
||||
{
|
||||
// Packet from Server to Client
|
||||
IThreadListener listener = Minecraft.getMinecraft();
|
||||
@@ -417,6 +422,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
// Packets from Server to Client //
|
||||
///////////////////////////////////
|
||||
case ComputerCraftPacket.ComputerChanged:
|
||||
case ComputerCraftPacket.ComputerTerminalChanged:
|
||||
{
|
||||
int instanceID = packet.m_dataInt[ 0 ];
|
||||
if( !ComputerCraft.clientComputerRegistry.contains( instanceID ) )
|
||||
@@ -435,6 +441,22 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ComputerCraftPacket.PlayRecord:
|
||||
{
|
||||
BlockPos pos = new BlockPos( packet.m_dataInt[ 0 ], packet.m_dataInt[ 1 ], packet.m_dataInt[ 2 ] );
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if( packet.m_dataInt.length > 3 )
|
||||
{
|
||||
SoundEvent sound = SoundEvent.REGISTRY.getObjectById( packet.m_dataInt[ 3 ] );
|
||||
mc.world.playRecord( pos, sound );
|
||||
mc.ingameGUI.setRecordPlayingMessage( packet.m_dataString[ 0 ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
mc.world.playRecord( pos, null );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +464,8 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
{
|
||||
ForgeHandlers handlers = new ForgeHandlers();
|
||||
MinecraftForge.EVENT_BUS.register( handlers );
|
||||
MinecraftForge.EVENT_BUS.register( new RenderOverlayCable() );
|
||||
MinecraftForge.EVENT_BUS.register( new ItemPocketRenderer() );
|
||||
}
|
||||
|
||||
public class ForgeHandlers
|
||||
|
@@ -0,0 +1,264 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
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.pocket.items.ItemPocketComputer;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
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.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumHandSide;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
import net.minecraftforge.client.event.RenderSpecificHandEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
|
||||
|
||||
/**
|
||||
* Emulates map rendering for pocket computers
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class ItemPocketRenderer
|
||||
{
|
||||
@SubscribeEvent
|
||||
public void renderItem( RenderSpecificHandEvent event )
|
||||
{
|
||||
ItemStack stack = event.getItemStack();
|
||||
if( !(stack.getItem() instanceof ItemPocketComputer) ) return;
|
||||
|
||||
event.setCanceled( true );
|
||||
|
||||
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
if( event.getHand() == EnumHand.MAIN_HAND && player.getHeldItemOffhand().isEmpty() )
|
||||
{
|
||||
renderItemFirstCentre(
|
||||
event.getInterpolatedPitch(),
|
||||
event.getEquipProgress(),
|
||||
event.getSwingProgress(),
|
||||
stack
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderItemFirstPersonSide(
|
||||
event.getHand() == EnumHand.MAIN_HAND ? player.getPrimaryHand() : player.getPrimaryHand().opposite(),
|
||||
event.getEquipProgress(),
|
||||
event.getSwingProgress(),
|
||||
stack
|
||||
);
|
||||
}
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* The main rendering method for pocket computers and their associated terminal
|
||||
*
|
||||
* @param stack The stack to render
|
||||
* @see ItemRenderer#renderMapFirstPerson(ItemStack)
|
||||
*/
|
||||
private void renderPocketComputerItem( ItemStack stack )
|
||||
{
|
||||
// Setup various transformations. Note that these are partially adapated from the corresponding method
|
||||
// in ItemRenderer
|
||||
GlStateManager.disableLighting();
|
||||
|
||||
GlStateManager.rotate( 180f, 0f, 1f, 0f );
|
||||
GlStateManager.rotate( 180f, 0f, 0f, 1f );
|
||||
GlStateManager.scale( 0.5, 0.5, 0.5 );
|
||||
|
||||
ItemPocketComputer pocketComputer = ComputerCraft.Items.pocketComputer;
|
||||
ClientComputer computer = pocketComputer.createClientComputer( stack );
|
||||
|
||||
{
|
||||
// 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();
|
||||
|
||||
GlStateManager.scale( 1.0f, -1.0f, 1.0f );
|
||||
|
||||
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 bakedmodel = renderItem.getItemModelWithOverrides( stack, null, null );
|
||||
bakedmodel = ForgeHooksClient.handleCameraTransforms( bakedmodel, ItemCameraTransforms.TransformType.GUI, false );
|
||||
renderItem.renderItem( stack, bakedmodel );
|
||||
|
||||
GlStateManager.disableAlpha();
|
||||
GlStateManager.disableRescaleNormal();
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
// If we've a computer and terminal then attempt to render it.
|
||||
if( computer != null )
|
||||
{
|
||||
Terminal terminal = computer.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
synchronized( terminal )
|
||||
{
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.disableDepth();
|
||||
|
||||
// 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) ComputerCraft.getFixedWidthFontRenderer();
|
||||
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() && ComputerCraft.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlStateManager.enableLighting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a pocket computer to one side of the player.
|
||||
*
|
||||
* @param side The side to render on
|
||||
* @param equipProgress The equip progress of this item
|
||||
* @param swingProgress The swing progress of this item
|
||||
* @param stack The stack to render
|
||||
* @see ItemRenderer#renderMapFirstPersonSide(float, EnumHandSide, float, ItemStack)
|
||||
*/
|
||||
private void renderItemFirstPersonSide( EnumHandSide side, float equipProgress, float swingProgress, ItemStack stack )
|
||||
{
|
||||
Minecraft minecraft = Minecraft.getMinecraft();
|
||||
float offset = side == EnumHandSide.RIGHT ? 1f : -1f;
|
||||
GlStateManager.translate( offset * 0.125f, -0.125f, 0f );
|
||||
|
||||
// If the player is not invisible then render a single arm
|
||||
if( !minecraft.player.isInvisible() )
|
||||
{
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.rotate( offset * 10f, 0f, 0f, 1f );
|
||||
minecraft.getItemRenderer().renderArmFirstPerson( equipProgress, swingProgress, side );
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
// Setup the appropriate transformations. This is just copied from the
|
||||
// corresponding method in ItemRenderer.
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.translate( offset * 0.51f, -0.08f + equipProgress * -1.2f, -0.75f );
|
||||
float f1 = MathHelper.sqrt( swingProgress );
|
||||
float f2 = MathHelper.sin( f1 * (float) Math.PI );
|
||||
float f3 = -0.5f * f2;
|
||||
float f4 = 0.4f * MathHelper.sin( f1 * ((float) Math.PI * 2f) );
|
||||
float f5 = -0.3f * MathHelper.sin( swingProgress * (float) Math.PI );
|
||||
GlStateManager.translate( offset * f3, f4 - 0.3f * f2, f5 );
|
||||
GlStateManager.rotate( f2 * -45f, 1f, 0f, 0f );
|
||||
GlStateManager.rotate( offset * f2 * -30f, 0f, 1f, 0f );
|
||||
|
||||
renderPocketComputerItem( stack );
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an item in the middle of the screen
|
||||
*
|
||||
* @param pitch The pitch of the player
|
||||
* @param equipProgress The equip progress of this item
|
||||
* @param swingProgress The swing progress of this item
|
||||
* @param stack The stack to render
|
||||
* @see ItemRenderer#renderMapFirstPerson(float, float, float)
|
||||
*/
|
||||
private void renderItemFirstCentre( float pitch, float equipProgress, float swingProgress, ItemStack stack )
|
||||
{
|
||||
ItemRenderer itemRenderer = Minecraft.getMinecraft().getItemRenderer();
|
||||
|
||||
// Setup the appropriate transformations. This is just copied from the
|
||||
// corresponding method in ItemRenderer.
|
||||
float swingRt = MathHelper.sqrt( swingProgress );
|
||||
float tX = -0.2f * MathHelper.sin( swingProgress * (float) Math.PI );
|
||||
float tZ = -0.4f * MathHelper.sin( swingRt * (float) Math.PI );
|
||||
GlStateManager.translate( 0f, -tX / 2f, tZ );
|
||||
float pitchAngle = itemRenderer.getMapAngleFromPitch( pitch );
|
||||
GlStateManager.translate( 0f, 0.04f + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f );
|
||||
GlStateManager.rotate( pitchAngle * -85f, 1f, 0f, 0f );
|
||||
itemRenderer.renderArms();
|
||||
float rX = MathHelper.sin( swingRt * (float) Math.PI );
|
||||
GlStateManager.rotate( rX * 20f, 1f, 0f, 0f );
|
||||
GlStateManager.scale( 2f, 2f, 2f );
|
||||
|
||||
renderPocketComputerItem( stack );
|
||||
}
|
||||
}
|
@@ -0,0 +1,264 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
|
||||
import net.minecraftforge.client.model.pipeline.LightUtil;
|
||||
import net.minecraftforge.client.model.pipeline.VertexTransformer;
|
||||
import net.minecraftforge.common.model.TRSRTransformation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.vecmath.Matrix4f;
|
||||
import javax.vecmath.Point3f;
|
||||
import javax.vecmath.Vector3f;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Transforms vertices of a model, remaining aware of winding order, and rearranging
|
||||
* vertices if needed.
|
||||
*/
|
||||
public final class ModelTransformer
|
||||
{
|
||||
private static final Matrix4f identity;
|
||||
|
||||
static
|
||||
{
|
||||
identity = new Matrix4f();
|
||||
identity.setIdentity();
|
||||
}
|
||||
|
||||
private ModelTransformer()
|
||||
{
|
||||
}
|
||||
|
||||
public static void transformQuadsTo( List<BakedQuad> output, List<BakedQuad> input, Matrix4f transform )
|
||||
{
|
||||
if( transform == null || transform.equals( identity ) )
|
||||
{
|
||||
output.addAll( input );
|
||||
}
|
||||
else
|
||||
{
|
||||
Matrix4f normalMatrix = new Matrix4f( transform );
|
||||
normalMatrix.invert();
|
||||
normalMatrix.transpose();
|
||||
|
||||
for( BakedQuad quad : input ) output.add( doTransformQuad( quad, transform, normalMatrix ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static BakedQuad transformQuad( BakedQuad input, Matrix4f transform )
|
||||
{
|
||||
if( transform == null || transform.equals( identity ) ) return input;
|
||||
|
||||
Matrix4f normalMatrix = new Matrix4f( transform );
|
||||
normalMatrix.invert();
|
||||
normalMatrix.transpose();
|
||||
return doTransformQuad( input, transform, normalMatrix );
|
||||
}
|
||||
|
||||
private static BakedQuad doTransformQuad( BakedQuad input, Matrix4f positionMatrix, Matrix4f normalMatrix )
|
||||
{
|
||||
|
||||
BakedQuadBuilder builder = new BakedQuadBuilder( input.getFormat() );
|
||||
NormalAwareTransformer transformer = new NormalAwareTransformer( builder, positionMatrix, normalMatrix );
|
||||
input.pipe( transformer );
|
||||
|
||||
if( transformer.areNormalsInverted() )
|
||||
{
|
||||
builder.swap( 1, 3 );
|
||||
transformer.areNormalsInverted();
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A vertex transformer that tracks whether the normals have been inverted and so the vertices
|
||||
* should be reordered so backface culling works as expected.
|
||||
*/
|
||||
private static class NormalAwareTransformer extends VertexTransformer
|
||||
{
|
||||
private final Matrix4f positionMatrix;
|
||||
private final Matrix4f normalMatrix;
|
||||
|
||||
private int vertexIndex = 0, elementIndex = 0;
|
||||
private final Point3f[] before = new Point3f[ 4 ];
|
||||
private final Point3f[] after = new Point3f[ 4 ];
|
||||
|
||||
public NormalAwareTransformer( IVertexConsumer parent, Matrix4f positionMatrix, Matrix4f normalMatrix )
|
||||
{
|
||||
super( parent );
|
||||
this.positionMatrix = positionMatrix;
|
||||
this.normalMatrix = normalMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQuadOrientation( EnumFacing orientation )
|
||||
{
|
||||
super.setQuadOrientation( orientation == null ? orientation : TRSRTransformation.rotate( positionMatrix, orientation ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put( int element, @Nonnull float... data )
|
||||
{
|
||||
switch( getVertexFormat().getElement( element ).getUsage() )
|
||||
{
|
||||
case POSITION:
|
||||
{
|
||||
Point3f vec = new Point3f( data );
|
||||
Point3f newVec = new Point3f();
|
||||
positionMatrix.transform( vec, newVec );
|
||||
|
||||
float[] newData = new float[ 4 ];
|
||||
newVec.get( newData );
|
||||
super.put( element, newData );
|
||||
|
||||
|
||||
before[ vertexIndex ] = vec;
|
||||
after[ vertexIndex ] = newVec;
|
||||
break;
|
||||
}
|
||||
case NORMAL:
|
||||
{
|
||||
Vector3f vec = new Vector3f( data );
|
||||
normalMatrix.transform( vec );
|
||||
|
||||
float[] newData = new float[ 4 ];
|
||||
vec.get( newData );
|
||||
super.put( element, newData );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
super.put( element, data );
|
||||
break;
|
||||
}
|
||||
|
||||
elementIndex++;
|
||||
if( elementIndex == getVertexFormat().getElementCount() )
|
||||
{
|
||||
vertexIndex++;
|
||||
elementIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean areNormalsInverted()
|
||||
{
|
||||
Vector3f temp1 = new Vector3f(), temp2 = new Vector3f();
|
||||
Vector3f crossBefore = new Vector3f(), crossAfter = new Vector3f();
|
||||
|
||||
// Determine what cross product we expect to have
|
||||
temp1.sub( before[ 1 ], before[ 0 ] );
|
||||
temp2.sub( before[ 1 ], before[ 2 ] );
|
||||
crossBefore.cross( temp1, temp2 );
|
||||
normalMatrix.transform( crossBefore );
|
||||
|
||||
// And determine what cross product we actually have
|
||||
temp1.sub( after[ 1 ], after[ 0 ] );
|
||||
temp2.sub( after[ 1 ], after[ 2 ] );
|
||||
crossAfter.cross( temp1, temp2 );
|
||||
|
||||
// If the angle between expected and actual cross product is greater than
|
||||
// pi/2 radians then we will need to reorder our quads.
|
||||
return Math.abs( crossBefore.angle( crossAfter ) ) >= Math.PI / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A vertex consumer which is capable of building {@link BakedQuad}s.
|
||||
*
|
||||
* Equivalent to {@link net.minecraftforge.client.model.pipeline.UnpackedBakedQuad.Builder} but more memory
|
||||
* efficient.
|
||||
*
|
||||
* This also provides the ability to swap vertices through {@link #swap(int, int)} to allow reordering.
|
||||
*/
|
||||
private static class BakedQuadBuilder implements IVertexConsumer
|
||||
{
|
||||
private final VertexFormat format;
|
||||
|
||||
private final int[] vertexData;
|
||||
private int vertexIndex = 0, elementIndex = 0;
|
||||
|
||||
private EnumFacing orientation;
|
||||
private int quadTint;
|
||||
private boolean diffuse;
|
||||
private TextureAtlasSprite texture;
|
||||
|
||||
private BakedQuadBuilder( VertexFormat format )
|
||||
{
|
||||
this.format = format;
|
||||
this.vertexData = new int[ format.getNextOffset() ];
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public VertexFormat getVertexFormat()
|
||||
{
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQuadTint( int tint )
|
||||
{
|
||||
this.quadTint = tint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQuadOrientation( @Nonnull EnumFacing orientation )
|
||||
{
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplyDiffuseLighting( boolean diffuse )
|
||||
{
|
||||
this.diffuse = diffuse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTexture( @Nonnull TextureAtlasSprite texture )
|
||||
{
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put( int element, @Nonnull float... data )
|
||||
{
|
||||
LightUtil.pack( data, vertexData, format, vertexIndex, element );
|
||||
|
||||
elementIndex++;
|
||||
if( elementIndex == getVertexFormat().getElementCount() )
|
||||
{
|
||||
vertexIndex++;
|
||||
elementIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void swap( int a, int b )
|
||||
{
|
||||
int length = vertexData.length / 4;
|
||||
for( int i = 0; i < length; i++ )
|
||||
{
|
||||
int temp = vertexData[ a * length + i ];
|
||||
vertexData[ a * length + i ] = vertexData[ b * length + i ];
|
||||
vertexData[ b * length + i ] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public BakedQuad build()
|
||||
{
|
||||
if( elementIndex != 0 || vertexIndex != 4 )
|
||||
{
|
||||
throw new IllegalStateException( "Got an unexpected number of elements/vertices" );
|
||||
}
|
||||
if( texture == null )
|
||||
{
|
||||
throw new IllegalStateException( "Texture has not been set" );
|
||||
}
|
||||
|
||||
return new BakedQuad( vertexData, quadTint, orientation, texture, diffuse, format );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,213 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.peripheral.PeripheralType;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockCable;
|
||||
import dan200.computercraft.shared.peripheral.modem.TileCable;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
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.eventhandler.SubscribeEvent;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class RenderOverlayCable
|
||||
{
|
||||
private static final float EXPAND = 0.002f;
|
||||
private static final double MIN = TileCable.MIN - EXPAND;
|
||||
private static final double MAX = TileCable.MAX + EXPAND;
|
||||
|
||||
@SubscribeEvent
|
||||
public void drawHighlight( DrawBlockHighlightEvent event )
|
||||
{
|
||||
if( event.getTarget().typeOfHit != RayTraceResult.Type.BLOCK ) return;
|
||||
|
||||
BlockPos pos = event.getTarget().getBlockPos();
|
||||
World world = event.getPlayer().getEntityWorld();
|
||||
|
||||
IBlockState state = world.getBlockState( pos );
|
||||
if( state.getBlock() != ComputerCraft.Blocks.cable ) return;
|
||||
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
if( tile == null || !(tile instanceof TileCable) ) return;
|
||||
|
||||
event.setCanceled( true );
|
||||
TileCable cable = (TileCable) tile;
|
||||
|
||||
PeripheralType type = cable.getPeripheralType();
|
||||
|
||||
GlStateManager.enableBlend();
|
||||
GlStateManager.tryBlendFuncSeparate( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0 );
|
||||
GlStateManager.color( 0.0f, 0.0f, 0.0f, 0.4f );
|
||||
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() );
|
||||
}
|
||||
|
||||
if( type != PeripheralType.Cable && WorldUtil.isVecInsideInclusive( cable.getModemBounds(), event.getTarget().hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )
|
||||
{
|
||||
RenderGlobal.drawSelectionBoundingBox( cable.getModemBounds(), 0, 0, 0, 0.4f );
|
||||
}
|
||||
else
|
||||
{
|
||||
int flags = 0;
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
|
||||
for( EnumFacing facing : EnumFacing.VALUES )
|
||||
{
|
||||
if( BlockCable.doesConnectVisually( state, world, pos, facing ) )
|
||||
{
|
||||
flags |= 1 << facing.ordinal();
|
||||
|
||||
|
||||
switch( facing.getAxis() )
|
||||
{
|
||||
case X:
|
||||
{
|
||||
double offset = facing == EnumFacing.WEST ? -EXPAND : 1 + EXPAND;
|
||||
double centre = facing == EnumFacing.WEST ? MIN : MAX;
|
||||
|
||||
buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( offset, MIN, MIN ).endVertex();
|
||||
buffer.pos( offset, MAX, MIN ).endVertex();
|
||||
buffer.pos( offset, MAX, MAX ).endVertex();
|
||||
buffer.pos( offset, MIN, MAX ).endVertex();
|
||||
buffer.pos( offset, MIN, MIN ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( offset, MIN, MIN ).endVertex();
|
||||
buffer.pos( centre, MIN, MIN ).endVertex();
|
||||
buffer.pos( offset, MAX, MIN ).endVertex();
|
||||
buffer.pos( centre, MAX, MIN ).endVertex();
|
||||
buffer.pos( offset, MAX, MAX ).endVertex();
|
||||
buffer.pos( centre, MAX, MAX ).endVertex();
|
||||
buffer.pos( offset, MIN, MAX ).endVertex();
|
||||
buffer.pos( centre, MIN, MAX ).endVertex();
|
||||
tessellator.draw();
|
||||
break;
|
||||
}
|
||||
case Y:
|
||||
{
|
||||
double offset = facing == EnumFacing.DOWN ? -EXPAND : 1 + EXPAND;
|
||||
double centre = facing == EnumFacing.DOWN ? MIN : MAX;
|
||||
|
||||
buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( MIN, offset, MIN ).endVertex();
|
||||
buffer.pos( MAX, offset, MIN ).endVertex();
|
||||
buffer.pos( MAX, offset, MAX ).endVertex();
|
||||
buffer.pos( MIN, offset, MAX ).endVertex();
|
||||
buffer.pos( MIN, offset, MIN ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( MIN, offset, MIN ).endVertex();
|
||||
buffer.pos( MIN, centre, MIN ).endVertex();
|
||||
buffer.pos( MAX, offset, MIN ).endVertex();
|
||||
buffer.pos( MAX, centre, MIN ).endVertex();
|
||||
buffer.pos( MAX, offset, MAX ).endVertex();
|
||||
buffer.pos( MAX, centre, MAX ).endVertex();
|
||||
buffer.pos( MIN, offset, MAX ).endVertex();
|
||||
buffer.pos( MIN, centre, MAX ).endVertex();
|
||||
tessellator.draw();
|
||||
break;
|
||||
}
|
||||
case Z:
|
||||
{
|
||||
double offset = facing == EnumFacing.NORTH ? -EXPAND : 1 + EXPAND;
|
||||
double centre = facing == EnumFacing.NORTH ? MIN : MAX;
|
||||
|
||||
buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( MIN, MIN, offset ).endVertex();
|
||||
buffer.pos( MAX, MIN, offset ).endVertex();
|
||||
buffer.pos( MAX, MAX, offset ).endVertex();
|
||||
buffer.pos( MIN, MAX, offset ).endVertex();
|
||||
buffer.pos( MIN, MIN, offset ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( MIN, MIN, offset ).endVertex();
|
||||
buffer.pos( MIN, MIN, centre ).endVertex();
|
||||
buffer.pos( MAX, MIN, offset ).endVertex();
|
||||
buffer.pos( MAX, MIN, centre ).endVertex();
|
||||
buffer.pos( MAX, MAX, offset ).endVertex();
|
||||
buffer.pos( MAX, MAX, centre ).endVertex();
|
||||
buffer.pos( MIN, MAX, offset ).endVertex();
|
||||
buffer.pos( MIN, MAX, centre ).endVertex();
|
||||
tessellator.draw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
|
||||
|
||||
draw( buffer, flags, EnumFacing.WEST, EnumFacing.DOWN, EnumFacing.Axis.Z );
|
||||
draw( buffer, flags, EnumFacing.WEST, EnumFacing.UP, EnumFacing.Axis.Z );
|
||||
draw( buffer, flags, EnumFacing.EAST, EnumFacing.DOWN, EnumFacing.Axis.Z );
|
||||
draw( buffer, flags, EnumFacing.EAST, EnumFacing.UP, EnumFacing.Axis.Z );
|
||||
|
||||
draw( buffer, flags, EnumFacing.WEST, EnumFacing.NORTH, EnumFacing.Axis.Y );
|
||||
draw( buffer, flags, EnumFacing.WEST, EnumFacing.SOUTH, EnumFacing.Axis.Y );
|
||||
draw( buffer, flags, EnumFacing.EAST, EnumFacing.NORTH, EnumFacing.Axis.Y );
|
||||
draw( buffer, flags, EnumFacing.EAST, EnumFacing.SOUTH, EnumFacing.Axis.Y );
|
||||
|
||||
draw( buffer, flags, EnumFacing.DOWN, EnumFacing.NORTH, EnumFacing.Axis.X );
|
||||
draw( buffer, flags, EnumFacing.DOWN, EnumFacing.SOUTH, EnumFacing.Axis.X );
|
||||
draw( buffer, flags, EnumFacing.UP, EnumFacing.NORTH, EnumFacing.Axis.X );
|
||||
draw( buffer, flags, EnumFacing.UP, EnumFacing.SOUTH, EnumFacing.Axis.X );
|
||||
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
GlStateManager.depthMask( true );
|
||||
GlStateManager.enableTexture2D();
|
||||
GlStateManager.disableBlend();
|
||||
}
|
||||
|
||||
private static void draw( BufferBuilder buffer, int flags, EnumFacing a, EnumFacing b, EnumFacing.Axis other )
|
||||
{
|
||||
if( ((flags >> a.ordinal()) & 1) != ((flags >> b.ordinal()) & 1) ) return;
|
||||
|
||||
double offA = a.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ? MIN : MAX;
|
||||
double offB = b.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ? MIN : MAX;
|
||||
switch( other )
|
||||
{
|
||||
case X:
|
||||
buffer.pos( MIN, offA, offB ).endVertex();
|
||||
buffer.pos( MAX, offA, offB ).endVertex();
|
||||
break;
|
||||
case Y:
|
||||
buffer.pos( offA, MIN, offB ).endVertex();
|
||||
buffer.pos( offA, MAX, offB ).endVertex();
|
||||
break;
|
||||
case Z:
|
||||
buffer.pos( offA, offB, MIN ).endVertex();
|
||||
buffer.pos( offA, offB, MAX ).endVertex();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.peripheral.PeripheralType;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockCable;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockCableModemVariant;
|
||||
import dan200.computercraft.shared.peripheral.modem.TileCable;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.util.BlockRenderLayer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
import net.minecraftforge.client.MinecraftForgeClient;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Render breaking animation only over part of a {@link TileCable}.
|
||||
*/
|
||||
public class TileEntityCableRenderer extends TileEntitySpecialRenderer<TileCable>
|
||||
{
|
||||
@Override
|
||||
public void render( @Nonnull TileCable te, double x, double y, double z, float partialTicks, int destroyStage, float alpha )
|
||||
{
|
||||
if( destroyStage < 0 ) return;
|
||||
|
||||
BlockPos pos = te.getPos();
|
||||
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
RayTraceResult hit = mc.objectMouseOver;
|
||||
if( hit == null || !hit.getBlockPos().equals( pos ) ) return;
|
||||
|
||||
if( MinecraftForgeClient.getRenderPass() != 0 ) return;
|
||||
|
||||
World world = te.getWorld();
|
||||
IBlockState state = world.getBlockState( pos );
|
||||
Block block = state.getBlock();
|
||||
if( block != ComputerCraft.Blocks.cable ) return;
|
||||
|
||||
state = state.getActualState( world, pos );
|
||||
if( te.getPeripheralType() != PeripheralType.Cable && WorldUtil.isVecInsideInclusive( te.getModemBounds(), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )
|
||||
{
|
||||
state = block.getDefaultState().withProperty( BlockCable.Properties.MODEM, state.getValue( BlockCable.Properties.MODEM ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
state = state.withProperty( BlockCable.Properties.MODEM, BlockCableModemVariant.None );
|
||||
}
|
||||
|
||||
IBakedModel model = mc.getBlockRendererDispatcher().getModelForState( state );
|
||||
if( model == null ) return;
|
||||
|
||||
preRenderDamagedBlocks();
|
||||
|
||||
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
|
||||
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.BLOCK );
|
||||
buffer.setTranslation( x - pos.getX(), y - pos.getY(), z - pos.getZ() );
|
||||
buffer.noColor();
|
||||
|
||||
ForgeHooksClient.setRenderLayer( block.getBlockLayer() );
|
||||
|
||||
// See BlockRendererDispatcher#renderBlockDamage
|
||||
TextureAtlasSprite breakingTexture = mc.getTextureMapBlocks().getAtlasSprite( "minecraft:blocks/destroy_stage_" + destroyStage );
|
||||
Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelRenderer().renderModel(
|
||||
world,
|
||||
ForgeHooksClient.getDamageModel( model, breakingTexture, state, world, pos ),
|
||||
state, pos, buffer, true
|
||||
);
|
||||
|
||||
ForgeHooksClient.setRenderLayer( BlockRenderLayer.SOLID );
|
||||
|
||||
buffer.setTranslation( 0, 0, 0 );
|
||||
Tessellator.getInstance().draw();
|
||||
|
||||
postRenderDamagedBlocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RenderGlobal#preRenderDamagedBlocks()
|
||||
*/
|
||||
private void preRenderDamagedBlocks()
|
||||
{
|
||||
GlStateManager.disableLighting();
|
||||
|
||||
GlStateManager.enableBlend();
|
||||
GlStateManager.tryBlendFuncSeparate( GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
|
||||
GlStateManager.enableBlend();
|
||||
GlStateManager.color( 1.0F, 1.0F, 1.0F, 0.5F );
|
||||
GlStateManager.doPolygonOffset( -3.0F, -3.0F );
|
||||
GlStateManager.enablePolygonOffset();
|
||||
GlStateManager.alphaFunc( 516, 0.1F );
|
||||
GlStateManager.enableAlpha();
|
||||
GlStateManager.pushMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RenderGlobal#postRenderDamagedBlocks()
|
||||
*/
|
||||
private void postRenderDamagedBlocks()
|
||||
{
|
||||
GlStateManager.disableAlpha();
|
||||
GlStateManager.doPolygonOffset( 0.0F, 0.0F );
|
||||
GlStateManager.disablePolygonOffset();
|
||||
GlStateManager.disablePolygonOffset();
|
||||
GlStateManager.depthMask( true );
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.common.ClientTerminal;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
@@ -43,24 +43,22 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
private void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
|
||||
{
|
||||
// Render from the origin monitor
|
||||
TileMonitor origin = monitor.getOrigin();
|
||||
if( origin == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
ClientMonitor originTerminal = monitor.getClientMonitor();
|
||||
|
||||
if( originTerminal == null ) return;
|
||||
TileMonitor origin = originTerminal.getOrigin();
|
||||
|
||||
// Ensure each monitor is rendered only once
|
||||
long renderFrame = ComputerCraft.getRenderFrame();
|
||||
if( origin.m_lastRenderFrame == renderFrame )
|
||||
if( originTerminal.lastRenderFrame == renderFrame )
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin.m_lastRenderFrame = renderFrame;
|
||||
originTerminal.lastRenderFrame = renderFrame;
|
||||
}
|
||||
|
||||
boolean redraw = origin.pollChanged();
|
||||
BlockPos monitorPos = monitor.getPos();
|
||||
BlockPos originPos = origin.getPos();
|
||||
posX += originPos.getX() - monitorPos.getX();
|
||||
@@ -82,11 +80,11 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.rotate( pitch, 1.0f, 0.0f, 0.0f );
|
||||
GlStateManager.translate(
|
||||
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
|
||||
((double)origin.getHeight() - 0.5) - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN),
|
||||
(origin.getHeight() - 0.5) - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN),
|
||||
0.5
|
||||
);
|
||||
double xSize = (double)origin.getWidth() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER );
|
||||
double ySize = (double)origin.getHeight() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER );
|
||||
double xSize = origin.getWidth() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER );
|
||||
double ySize = origin.getHeight() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER );
|
||||
|
||||
// Get renderers
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
@@ -94,9 +92,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
BufferBuilder renderer = tessellator.getBuffer();
|
||||
|
||||
// Get terminal
|
||||
ClientTerminal clientTerminal = (ClientTerminal)origin.getTerminal();
|
||||
Terminal terminal = (clientTerminal != null) ? clientTerminal.getTerminal() : null;
|
||||
redraw = redraw || (clientTerminal != null && clientTerminal.hasTerminalChanged());
|
||||
boolean redraw = originTerminal.pollTerminalChanged();
|
||||
|
||||
// Draw the contents
|
||||
GlStateManager.depthMask( false );
|
||||
@@ -104,30 +100,31 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
mc.entityRenderer.disableLightmap();
|
||||
try
|
||||
{
|
||||
Terminal terminal = originTerminal.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
Palette palette = terminal.getPalette();
|
||||
|
||||
// Allocate display lists
|
||||
if( origin.m_renderDisplayList < 0 )
|
||||
if( originTerminal.renderDisplayList < 0 )
|
||||
{
|
||||
origin.m_renderDisplayList = GlStateManager.glGenLists( 3 );
|
||||
originTerminal.renderDisplayList = GlStateManager.glGenLists( 3 );
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
// Draw a terminal
|
||||
boolean greyscale = !clientTerminal.isColour();
|
||||
boolean greyscale = !originTerminal.isColour();
|
||||
int width = terminal.getWidth();
|
||||
int height = terminal.getHeight();
|
||||
int cursorX = terminal.getCursorX();
|
||||
int cursorY = terminal.getCursorY();
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer)ComputerCraft.getFixedWidthFontRenderer();
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
try
|
||||
{
|
||||
double xScale = xSize / (double) ( width * FixedWidthFontRenderer.FONT_WIDTH );
|
||||
double yScale = ySize / (double) ( height * FixedWidthFontRenderer.FONT_HEIGHT );
|
||||
double xScale = xSize / ( width * FixedWidthFontRenderer.FONT_WIDTH );
|
||||
double yScale = ySize / ( height * FixedWidthFontRenderer.FONT_HEIGHT );
|
||||
GlStateManager.scale( xScale, -yScale, 1.0 );
|
||||
|
||||
// Draw background
|
||||
@@ -135,12 +132,12 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( redraw )
|
||||
{
|
||||
// Build background display list
|
||||
GlStateManager.glNewList( origin.m_renderDisplayList, GL11.GL_COMPILE );
|
||||
GlStateManager.glNewList( originTerminal.renderDisplayList, GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
|
||||
double marginYSize = TileMonitor.RENDER_MARGIN / yScale;
|
||||
double marginSquash = marginYSize / (double) FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
double marginSquash = marginYSize / FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
|
||||
// Top and bottom margins
|
||||
GlStateManager.pushMatrix();
|
||||
@@ -149,7 +146,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.scale( 1.0, marginSquash, 1.0 );
|
||||
GlStateManager.translate( 0.0, -marginYSize / marginSquash, 0.0 );
|
||||
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette );
|
||||
GlStateManager.translate( 0.0, ( marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT ) / marginSquash, 0.0 );
|
||||
GlStateManager.translate( 0.0, (marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT) / marginSquash, 0.0 );
|
||||
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
|
||||
}
|
||||
finally
|
||||
@@ -174,7 +171,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.glEndList();
|
||||
}
|
||||
}
|
||||
GlStateManager.callList( origin.m_renderDisplayList );
|
||||
GlStateManager.callList( originTerminal.renderDisplayList );
|
||||
GlStateManager.resetColor();
|
||||
|
||||
// Draw text
|
||||
@@ -182,7 +179,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( redraw )
|
||||
{
|
||||
// Build text display list
|
||||
GlStateManager.glNewList( origin.m_renderDisplayList + 1, GL11.GL_COMPILE );
|
||||
GlStateManager.glNewList( originTerminal.renderDisplayList + 1, GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
// Lines
|
||||
@@ -202,7 +199,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.glEndList();
|
||||
}
|
||||
}
|
||||
GlStateManager.callList( origin.m_renderDisplayList + 1 );
|
||||
GlStateManager.callList( originTerminal.renderDisplayList + 1 );
|
||||
GlStateManager.resetColor();
|
||||
|
||||
// Draw cursor
|
||||
@@ -210,7 +207,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( redraw )
|
||||
{
|
||||
// Build cursor display list
|
||||
GlStateManager.glNewList( origin.m_renderDisplayList + 2, GL11.GL_COMPILE );
|
||||
GlStateManager.glNewList( originTerminal.renderDisplayList + 2, GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
// Cursor
|
||||
@@ -236,7 +233,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
}
|
||||
if( ComputerCraft.getGlobalCursorBlink() )
|
||||
{
|
||||
GlStateManager.callList( origin.m_renderDisplayList + 2 );
|
||||
GlStateManager.callList( originTerminal.renderDisplayList + 2 );
|
||||
GlStateManager.resetColor();
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ package dan200.computercraft.client.render;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
@@ -116,47 +115,34 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
|
||||
// Setup the transform
|
||||
Vec3d offset;
|
||||
float yaw;
|
||||
if( turtle != null )
|
||||
{
|
||||
offset = turtle.getRenderOffset( f );
|
||||
yaw = turtle.getRenderYaw( f );
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = new Vec3d( 0.0, 0.0, 0.0 );
|
||||
yaw = 0.0f;
|
||||
}
|
||||
offset = turtle.getRenderOffset( f );
|
||||
yaw = turtle.getRenderYaw( f );
|
||||
GlStateManager.translate( posX + offset.x, posY + offset.y, posZ + offset.z );
|
||||
|
||||
// Render the label
|
||||
IComputer computer = (turtle != null) ? turtle.getComputer() : null;
|
||||
String label = (computer != null) ? computer.getLabel() : null;
|
||||
String label = turtle.createProxy().getLabel();
|
||||
if( label != null )
|
||||
{
|
||||
renderLabel( turtle.getAccess().getPosition(), label );
|
||||
}
|
||||
|
||||
// Render the turtle
|
||||
GlStateManager.translate( 0.5f, 0.0f, 0.5f );
|
||||
GlStateManager.translate( 0.5f, 0.5f, 0.5f );
|
||||
GlStateManager.rotate( 180.0f - yaw, 0.0f, 1.0f, 0.0f );
|
||||
GlStateManager.translate( -0.5f, 0.0f, -0.5f );
|
||||
|
||||
if( label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )) )
|
||||
{
|
||||
// Flip the model and swap the cull face as winding order will have changed.
|
||||
GlStateManager.scale( 1.0f, -1.0f, 1.0f );
|
||||
GlStateManager.cullFace( GlStateManager.CullFace.FRONT );
|
||||
}
|
||||
GlStateManager.translate( -0.5f, -0.5f, -0.5f );
|
||||
// Render the turtle
|
||||
int colour;
|
||||
ComputerFamily family;
|
||||
ResourceLocation overlay;
|
||||
if( turtle != null )
|
||||
{
|
||||
colour = turtle.getColour();
|
||||
family = turtle.getFamily();
|
||||
overlay = turtle.getOverlay();
|
||||
}
|
||||
else
|
||||
{
|
||||
colour = -1;
|
||||
family = ComputerFamily.Normal;
|
||||
overlay = null;
|
||||
}
|
||||
colour = turtle.getColour();
|
||||
family = turtle.getFamily();
|
||||
overlay = turtle.getOverlay();
|
||||
|
||||
renderModel( state, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } );
|
||||
|
||||
@@ -183,15 +169,13 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
|
||||
}
|
||||
|
||||
// Render the upgrades
|
||||
if( turtle != null )
|
||||
{
|
||||
renderUpgrade( state, turtle, TurtleSide.Left, f );
|
||||
renderUpgrade( state, turtle, TurtleSide.Right, f );
|
||||
}
|
||||
renderUpgrade( state, turtle, TurtleSide.Left, f );
|
||||
renderUpgrade( state, turtle, TurtleSide.Right, f );
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.popMatrix();
|
||||
GlStateManager.cullFace( GlStateManager.CullFace.BACK );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,10 +294,10 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder renderer = tessellator.getBuffer();
|
||||
renderer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR );
|
||||
renderer.pos( (double) ( -xOffset - 1 ), (double) ( -1 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( (double) ( -xOffset - 1 ), (double) ( 8 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( (double) ( xOffset + 1 ), (double) ( 8 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( (double) ( xOffset + 1 ), (double) ( -1 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( -xOffset - 1, -1 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( -xOffset - 1, 8 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( xOffset + 1, 8 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
renderer.pos( xOffset + 1, -1 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
|
||||
tessellator.draw();
|
||||
}
|
||||
finally
|
||||
|
@@ -6,13 +6,10 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
|
||||
import net.minecraft.client.renderer.block.model.ItemOverrideList;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormatElement;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.vecmath.Matrix4f;
|
||||
import javax.vecmath.Point3f;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -20,16 +17,17 @@ import java.util.Map;
|
||||
|
||||
public class TurtleMultiModel implements IBakedModel
|
||||
{
|
||||
private IBakedModel m_baseModel;
|
||||
private IBakedModel m_overlayModel;
|
||||
private IBakedModel m_leftUpgradeModel;
|
||||
private Matrix4f m_leftUpgradeTransform;
|
||||
private IBakedModel m_rightUpgradeModel;
|
||||
private Matrix4f m_rightUpgradeTransform;
|
||||
private final IBakedModel m_baseModel;
|
||||
private final IBakedModel m_overlayModel;
|
||||
private final Matrix4f m_generalTransform;
|
||||
private final IBakedModel m_leftUpgradeModel;
|
||||
private final Matrix4f m_leftUpgradeTransform;
|
||||
private final IBakedModel m_rightUpgradeModel;
|
||||
private final Matrix4f m_rightUpgradeTransform;
|
||||
private List<BakedQuad> m_generalQuads;
|
||||
private Map<EnumFacing, List<BakedQuad>> m_faceQuads;
|
||||
|
||||
public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform )
|
||||
public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, Matrix4f generalTransform, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform )
|
||||
{
|
||||
// Get the models
|
||||
m_baseModel = baseModel;
|
||||
@@ -38,6 +36,7 @@ public class TurtleMultiModel implements IBakedModel
|
||||
m_leftUpgradeTransform = leftUpgradeTransform;
|
||||
m_rightUpgradeModel = rightUpgradeModel;
|
||||
m_rightUpgradeTransform = rightUpgradeTransform;
|
||||
m_generalTransform = generalTransform;
|
||||
m_generalQuads = null;
|
||||
m_faceQuads = new HashMap<>();
|
||||
}
|
||||
@@ -48,51 +47,52 @@ public class TurtleMultiModel implements IBakedModel
|
||||
{
|
||||
if( side != null )
|
||||
{
|
||||
if( !m_faceQuads.containsKey( side ) )
|
||||
{
|
||||
ArrayList<BakedQuad> quads = new ArrayList<>();
|
||||
if( m_overlayModel != null )
|
||||
{
|
||||
quads.addAll( m_overlayModel.getQuads( state, side, rand ) );
|
||||
}
|
||||
if( m_leftUpgradeModel != null )
|
||||
{
|
||||
quads.addAll( transformQuads( m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ) );
|
||||
}
|
||||
if( m_rightUpgradeModel != null )
|
||||
{
|
||||
quads.addAll( transformQuads( m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ) );
|
||||
}
|
||||
quads.trimToSize();
|
||||
m_faceQuads.put( side, quads );
|
||||
}
|
||||
if( !m_faceQuads.containsKey( side ) ) m_faceQuads.put( side, buildQuads( state, side, rand ) );
|
||||
return m_faceQuads.get( side );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_generalQuads == null )
|
||||
{
|
||||
ArrayList<BakedQuad> quads = new ArrayList<>();
|
||||
quads.addAll( m_baseModel.getQuads( state, side, rand ) );
|
||||
if( m_overlayModel != null )
|
||||
{
|
||||
quads.addAll( m_overlayModel.getQuads( state, side, rand ) );
|
||||
}
|
||||
if( m_leftUpgradeModel != null )
|
||||
{
|
||||
quads.addAll( transformQuads( m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ) );
|
||||
}
|
||||
if( m_rightUpgradeModel != null )
|
||||
{
|
||||
quads.addAll( transformQuads( m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ) );
|
||||
}
|
||||
quads.trimToSize();
|
||||
m_generalQuads = quads;
|
||||
}
|
||||
if( m_generalQuads == null ) m_generalQuads = buildQuads( state, side, rand );
|
||||
return m_generalQuads;
|
||||
}
|
||||
}
|
||||
|
||||
private List<BakedQuad> buildQuads( IBlockState state, EnumFacing side, long rand )
|
||||
{
|
||||
ArrayList<BakedQuad> quads = new ArrayList<>();
|
||||
ModelTransformer.transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand ), m_generalTransform );
|
||||
if( m_overlayModel != null )
|
||||
{
|
||||
ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform );
|
||||
}
|
||||
if( m_overlayModel != null )
|
||||
{
|
||||
ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform );
|
||||
}
|
||||
if( m_leftUpgradeModel != null )
|
||||
{
|
||||
Matrix4f upgradeTransform = m_generalTransform;
|
||||
if( m_leftUpgradeTransform != null )
|
||||
{
|
||||
upgradeTransform = new Matrix4f( m_generalTransform );
|
||||
upgradeTransform.mul( m_leftUpgradeTransform );
|
||||
}
|
||||
ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), upgradeTransform );
|
||||
}
|
||||
if( m_rightUpgradeModel != null )
|
||||
{
|
||||
Matrix4f upgradeTransform = m_generalTransform;
|
||||
if( m_rightUpgradeTransform != null )
|
||||
{
|
||||
upgradeTransform = new Matrix4f( m_generalTransform );
|
||||
upgradeTransform.mul( m_rightUpgradeTransform );
|
||||
}
|
||||
ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), upgradeTransform );
|
||||
}
|
||||
quads.trimToSize();
|
||||
return quads;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAmbientOcclusion()
|
||||
{
|
||||
@@ -132,63 +132,4 @@ public class TurtleMultiModel implements IBakedModel
|
||||
{
|
||||
return ItemOverrideList.NONE;
|
||||
}
|
||||
|
||||
private List<BakedQuad> transformQuads( List<BakedQuad> input, Matrix4f transform )
|
||||
{
|
||||
if( transform == null || input.size() == 0 )
|
||||
{
|
||||
return input;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<BakedQuad> output = new ArrayList<>( input.size() );
|
||||
for( BakedQuad quad : input )
|
||||
{
|
||||
output.add( transformQuad( quad, transform ) );
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private BakedQuad transformQuad( BakedQuad quad, Matrix4f transform )
|
||||
{
|
||||
int[] vertexData = quad.getVertexData().clone();
|
||||
int offset = 0;
|
||||
BakedQuad copy = new BakedQuad( vertexData, -1, quad.getFace(), quad.getSprite(), quad.shouldApplyDiffuseLighting(), quad.getFormat() );
|
||||
VertexFormat format = copy.getFormat();
|
||||
for( int i=0; i<format.getElementCount(); ++i ) // For each vertex element
|
||||
{
|
||||
VertexFormatElement element = format.getElement( i );
|
||||
if( element.isPositionElement() &&
|
||||
element.getType() == VertexFormatElement.EnumType.FLOAT &&
|
||||
element.getElementCount() == 3 ) // When we find a position element
|
||||
{
|
||||
for( int j=0; j<4; ++j ) // For each corner of the quad
|
||||
{
|
||||
int start = offset + j * format.getNextOffset();
|
||||
if( (start % 4) == 0 )
|
||||
{
|
||||
start = start / 4;
|
||||
|
||||
// Extract the position
|
||||
Point3f pos = new Point3f(
|
||||
Float.intBitsToFloat( vertexData[ start ] ),
|
||||
Float.intBitsToFloat( vertexData[ start + 1 ] ),
|
||||
Float.intBitsToFloat( vertexData[ start + 2 ] )
|
||||
);
|
||||
|
||||
// Transform the position
|
||||
transform.transform( pos );
|
||||
|
||||
// Insert the position
|
||||
vertexData[ start ] = Float.floatToRawIntBits( pos.x );
|
||||
vertexData[ start + 1 ] = Float.floatToRawIntBits( pos.y );
|
||||
vertexData[ start + 2 ] = Float.floatToRawIntBits( pos.z );
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += element.getSize();
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
@@ -35,6 +35,19 @@ import java.util.List;
|
||||
|
||||
public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReloadListener
|
||||
{
|
||||
private static final Matrix4f s_identity, s_flip;
|
||||
|
||||
static
|
||||
{
|
||||
s_identity = new Matrix4f();
|
||||
s_identity.setIdentity();
|
||||
|
||||
s_flip = new Matrix4f();
|
||||
s_flip.setIdentity();
|
||||
s_flip.m11 = -1; // Flip on the y axis
|
||||
s_flip.m13 = 1; // Models go from (0,0,0) to (1,1,1), so push back up.
|
||||
}
|
||||
|
||||
private static class TurtleModelCombination
|
||||
{
|
||||
public final ComputerFamily m_family;
|
||||
@@ -43,8 +56,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
public final ITurtleUpgrade m_rightUpgrade;
|
||||
public final ResourceLocation m_overlay;
|
||||
public final boolean m_christmas;
|
||||
public final boolean m_flip;
|
||||
|
||||
public TurtleModelCombination( ComputerFamily family, boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, ResourceLocation overlay, boolean christmas )
|
||||
public TurtleModelCombination( ComputerFamily family, boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, ResourceLocation overlay, boolean christmas, boolean flip )
|
||||
{
|
||||
m_family = family;
|
||||
m_colour = colour;
|
||||
@@ -52,22 +66,26 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
m_rightUpgrade = rightUpgrade;
|
||||
m_overlay = overlay;
|
||||
m_christmas = christmas;
|
||||
m_flip = flip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object other )
|
||||
{
|
||||
if( other == this ) {
|
||||
if( other == this )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if( other instanceof TurtleModelCombination ) {
|
||||
TurtleModelCombination otherCombo = (TurtleModelCombination)other;
|
||||
if( other instanceof TurtleModelCombination )
|
||||
{
|
||||
TurtleModelCombination otherCombo = (TurtleModelCombination) other;
|
||||
if( otherCombo.m_family == m_family &&
|
||||
otherCombo.m_colour == m_colour &&
|
||||
otherCombo.m_leftUpgrade == m_leftUpgrade &&
|
||||
otherCombo.m_rightUpgrade == m_rightUpgrade &&
|
||||
Objects.equal( otherCombo.m_overlay, m_overlay ) &&
|
||||
otherCombo.m_christmas == m_christmas )
|
||||
otherCombo.m_christmas == m_christmas &&
|
||||
otherCombo.m_flip == m_flip )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -86,10 +104,11 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
result = prime * result + (m_rightUpgrade != null ? m_rightUpgrade.hashCode() : 0);
|
||||
result = prime * result + (m_overlay != null ? m_overlay.hashCode() : 0);
|
||||
result = prime * result + (m_christmas ? 1 : 0);
|
||||
result = prime * result + (m_flip ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HashMap<TurtleModelCombination, IBakedModel> m_cachedModels;
|
||||
private ItemOverrideList m_overrides;
|
||||
private final TurtleModelCombination m_defaultCombination;
|
||||
@@ -97,12 +116,12 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
public TurtleSmartItemModel()
|
||||
{
|
||||
m_cachedModels = new HashMap<>();
|
||||
m_defaultCombination = new TurtleModelCombination( ComputerFamily.Normal, false, null, null, null, false );
|
||||
m_defaultCombination = new TurtleModelCombination( ComputerFamily.Normal, false, null, null, null, false, false );
|
||||
m_overrides = new ItemOverrideList( new ArrayList<>() )
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public IBakedModel handleItemState( @Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity)
|
||||
public IBakedModel handleItemState( @Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity )
|
||||
{
|
||||
ItemTurtleBase turtle = (ItemTurtleBase) stack.getItem();
|
||||
ComputerFamily family = turtle.getFamily( stack );
|
||||
@@ -111,7 +130,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.Right );
|
||||
ResourceLocation overlay = turtle.getOverlay( stack );
|
||||
boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.Christmas;
|
||||
TurtleModelCombination combo = new TurtleModelCombination( family, colour != -1, leftUpgrade, rightUpgrade, overlay, christmas );
|
||||
String label = turtle.getLabel( stack );
|
||||
boolean flip = label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" ));
|
||||
TurtleModelCombination combo = new TurtleModelCombination( family, colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip );
|
||||
if( m_cachedModels.containsKey( combo ) )
|
||||
{
|
||||
return m_cachedModels.get( combo );
|
||||
@@ -146,28 +167,25 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
ModelResourceLocation baseModelLocation = TileEntityTurtleRenderer.getTurtleModel( combo.m_family, combo.m_colour );
|
||||
ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_family, combo.m_overlay, combo.m_christmas );
|
||||
IBakedModel baseModel = modelManager.getModel( baseModelLocation );
|
||||
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( baseModelLocation ) : null;
|
||||
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( overlayModelLocation ) : null;
|
||||
Matrix4f transform = combo.m_flip ? s_flip : s_identity;
|
||||
Pair<IBakedModel, Matrix4f> leftModel = (combo.m_leftUpgrade != null) ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
|
||||
Pair<IBakedModel, Matrix4f> rightModel = (combo.m_rightUpgrade != null) ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
|
||||
if( leftModel != null && rightModel != null )
|
||||
{
|
||||
return new TurtleMultiModel( baseModel, overlayModel, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() );
|
||||
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() );
|
||||
}
|
||||
else if( leftModel != null )
|
||||
{
|
||||
return new TurtleMultiModel( baseModel, overlayModel, leftModel.getLeft(), leftModel.getRight(), null, null );
|
||||
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), null, null );
|
||||
}
|
||||
else if( rightModel != null )
|
||||
{
|
||||
return new TurtleMultiModel( baseModel, overlayModel, null, null, rightModel.getLeft(), rightModel.getRight() );
|
||||
}
|
||||
else if( overlayModel != null )
|
||||
{
|
||||
return new TurtleMultiModel( baseModel, overlayModel, null, null, null, null );
|
||||
return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, rightModel.getLeft(), rightModel.getRight() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return baseModel;
|
||||
return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, null, null );
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -45,8 +45,8 @@ public class AddressPredicate
|
||||
|
||||
public AddressPredicate( String... filters )
|
||||
{
|
||||
List<Pattern> wildcards = this.wildcards = new ArrayList<Pattern>();
|
||||
List<HostRange> ranges = this.ranges = new ArrayList<HostRange>();
|
||||
List<Pattern> wildcards = this.wildcards = new ArrayList<>();
|
||||
List<HostRange> ranges = this.ranges = new ArrayList<>();
|
||||
|
||||
for( String filter : filters )
|
||||
{
|
||||
|
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
|
||||
|
||||
// Contributed by Nia
|
||||
// Based on LuaBit (http://luaforge.net/projects/bit)
|
||||
|
||||
public class BitAPI implements ILuaAPI
|
||||
{
|
||||
private static final int BNOT = 0;
|
||||
private static final int BAND = 1;
|
||||
private static final int BOR = 2;
|
||||
private static final int BXOR = 3;
|
||||
private static final int BRSHIFT = 4;
|
||||
private static final int BLSHIFT = 5;
|
||||
private static final int BLOGIC_RSHIFT = 6;
|
||||
|
||||
public BitAPI( IAPIEnvironment _environment )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
return new String[] {
|
||||
"bit"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup( )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames() {
|
||||
return new String[] {
|
||||
"bnot", "band", "bor", "bxor",
|
||||
"brshift", "blshift", "blogic_rshift"
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
int ret = 0;
|
||||
switch(method) {
|
||||
case BNOT:
|
||||
ret = ~getInt( args, 0 );
|
||||
break;
|
||||
case BAND:
|
||||
ret = getInt( args, 0 ) & getInt( args, 1 );
|
||||
break;
|
||||
case BOR:
|
||||
ret = getInt( args, 0 ) | getInt( args, 1 );
|
||||
break;
|
||||
case BXOR:
|
||||
ret = getInt( args, 0 ) ^ getInt( args, 1 );
|
||||
break;
|
||||
case BRSHIFT:
|
||||
ret = getInt( args, 0 ) >> getInt( args, 1 );
|
||||
break;
|
||||
case BLSHIFT:
|
||||
ret = getInt( args, 0 ) << getInt( args, 1 );
|
||||
break;
|
||||
case BLOGIC_RSHIFT:
|
||||
ret = getInt( args, 0 ) >>> getInt( args, 1 );
|
||||
break;
|
||||
}
|
||||
|
||||
return new Object[]{ ret&0xFFFFFFFFL };
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
@@ -100,21 +101,6 @@ public class BufferAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
|
155
src/main/java/dan200/computercraft/core/apis/ComputerAccess.java
Normal file
155
src/main/java/dan200/computercraft/core/apis/ComputerAccess.java
Normal file
@@ -0,0 +1,155 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class ComputerAccess implements IComputerAccess
|
||||
{
|
||||
private final IAPIEnvironment m_environment;
|
||||
private final Set<String> m_mounts = new HashSet<>();
|
||||
|
||||
protected ComputerAccess( IAPIEnvironment m_environment )
|
||||
{
|
||||
this.m_environment = m_environment;
|
||||
}
|
||||
|
||||
public void unmountAll()
|
||||
{
|
||||
FileSystem fileSystem = m_environment.getFileSystem();
|
||||
for( String m_mount : m_mounts )
|
||||
{
|
||||
fileSystem.unmount( m_mount );
|
||||
}
|
||||
m_mounts.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mount( @Nonnull String desiredLoc, @Nonnull IMount mount )
|
||||
{
|
||||
return mount( desiredLoc, mount, getAttachmentName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
|
||||
{
|
||||
// Mount the location
|
||||
String location;
|
||||
FileSystem fileSystem = m_environment.getFileSystem();
|
||||
if( fileSystem == null )
|
||||
{
|
||||
throw new IllegalStateException( "File system has not been created" );
|
||||
}
|
||||
|
||||
synchronized( fileSystem )
|
||||
{
|
||||
location = findFreeLocation( desiredLoc );
|
||||
if( location != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
fileSystem.mount( driveName, location, mount );
|
||||
}
|
||||
catch( FileSystemException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
if( location != null )
|
||||
{
|
||||
m_mounts.add( location );
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount )
|
||||
{
|
||||
return mountWritable( desiredLoc, mount, getAttachmentName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
|
||||
{
|
||||
// Mount the location
|
||||
String location;
|
||||
FileSystem fileSystem = m_environment.getFileSystem();
|
||||
if( fileSystem == null )
|
||||
{
|
||||
throw new IllegalStateException( "File system has not been created" );
|
||||
}
|
||||
|
||||
synchronized( fileSystem )
|
||||
{
|
||||
location = findFreeLocation( desiredLoc );
|
||||
if( location != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
fileSystem.mountWritable( driveName, location, mount );
|
||||
}
|
||||
catch( FileSystemException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
if( location != null )
|
||||
{
|
||||
m_mounts.add( location );
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount( String location )
|
||||
{
|
||||
if( location != null )
|
||||
{
|
||||
if( !m_mounts.contains( location ) )
|
||||
{
|
||||
throw new RuntimeException( "You didn't mount this location" );
|
||||
}
|
||||
|
||||
m_environment.getFileSystem().unmount( location );
|
||||
m_mounts.remove( location );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID()
|
||||
{
|
||||
return m_environment.getComputerID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent( @Nonnull final String event, final Object[] arguments )
|
||||
{
|
||||
m_environment.queueEvent( event, arguments );
|
||||
}
|
||||
|
||||
private String findFreeLocation( String desiredLoc )
|
||||
{
|
||||
try
|
||||
{
|
||||
FileSystem fileSystem = m_environment.getFileSystem();
|
||||
if( !fileSystem.exists( desiredLoc ) )
|
||||
{
|
||||
return desiredLoc;
|
||||
}
|
||||
|
||||
// We used to check foo2,foo3,foo4,etc here
|
||||
// but the disk drive does this itself now
|
||||
return null;
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
@@ -48,11 +49,6 @@ public class FSAPI implements ILuaAPI
|
||||
m_fileSystem = m_env.getFileSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
|
@@ -6,29 +6,35 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.http.HTTPCheck;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequest;
|
||||
import dan200.computercraft.core.apis.http.HTTPTask;
|
||||
import dan200.computercraft.core.apis.http.*;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
|
||||
public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
private final IAPIEnvironment m_apiEnvironment;
|
||||
private final List<HTTPTask> m_httpTasks;
|
||||
|
||||
private final List<Future<?>> m_httpTasks;
|
||||
private final Set<Closeable> m_closeables;
|
||||
|
||||
public HTTPAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_apiEnvironment = environment;
|
||||
m_httpTasks = new ArrayList<>();
|
||||
m_closeables = new HashSet<>();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
@@ -38,49 +44,55 @@ public class HTTPAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup( )
|
||||
public void update()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
// Wait for all of our http requests
|
||||
// Wait for all of our http requests
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
Iterator<HTTPTask> it = m_httpTasks.iterator();
|
||||
Iterator<Future<?>> it = m_httpTasks.iterator();
|
||||
while( it.hasNext() )
|
||||
{
|
||||
final HTTPTask h = it.next();
|
||||
if( h.isFinished() )
|
||||
{
|
||||
h.whenFinished( m_apiEnvironment );
|
||||
it.remove();
|
||||
}
|
||||
final Future<?> h = it.next();
|
||||
if( h.isDone() ) it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
for( HTTPTask r : m_httpTasks )
|
||||
for( Future<?> r : m_httpTasks )
|
||||
{
|
||||
r.cancel();
|
||||
r.cancel( false );
|
||||
}
|
||||
m_httpTasks.clear();
|
||||
}
|
||||
synchronized( m_closeables )
|
||||
{
|
||||
for( Closeable x : m_closeables )
|
||||
{
|
||||
try
|
||||
{
|
||||
x.close();
|
||||
}
|
||||
catch( IOException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
m_closeables.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
return new String[] {
|
||||
"request",
|
||||
"checkURL"
|
||||
"checkURL",
|
||||
"websocket",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -113,7 +125,7 @@ public class HTTPAPI implements ILuaAPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get binary
|
||||
boolean binary = false;
|
||||
if( args.length >= 4 )
|
||||
@@ -125,10 +137,10 @@ public class HTTPAPI implements ILuaAPI
|
||||
try
|
||||
{
|
||||
URL url = HTTPRequest.checkURL( urlString );
|
||||
HTTPRequest request = new HTTPRequest( urlString, url, postString, headers, binary );
|
||||
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary );
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
m_httpTasks.add( HTTPTask.submit( request ) );
|
||||
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
|
||||
}
|
||||
return new Object[] { true };
|
||||
}
|
||||
@@ -147,9 +159,47 @@ public class HTTPAPI implements ILuaAPI
|
||||
try
|
||||
{
|
||||
URL url = HTTPRequest.checkURL( urlString );
|
||||
HTTPCheck check = new HTTPCheck( urlString, url );
|
||||
synchronized( m_httpTasks ) {
|
||||
m_httpTasks.add( HTTPTask.submit( check ) );
|
||||
HTTPCheck check = new HTTPCheck( m_apiEnvironment, urlString, url );
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
|
||||
}
|
||||
return new Object[] { true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
case 2: // websocket
|
||||
{
|
||||
String address = getString( args, 0 );
|
||||
Map<Object, Object> headerTbl = optTable( args, 1, Collections.emptyMap() );
|
||||
|
||||
HashMap<String, String> headers = new HashMap<String, String>( headerTbl.size() );
|
||||
for( Object key : headerTbl.keySet() )
|
||||
{
|
||||
Object value = headerTbl.get( key );
|
||||
if( key instanceof String && value instanceof String )
|
||||
{
|
||||
headers.put( (String) key, (String) value );
|
||||
}
|
||||
}
|
||||
|
||||
if( !ComputerCraft.http_websocket_enable )
|
||||
{
|
||||
throw new LuaException( "Websocket connections are disabled" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
URI uri = WebsocketConnector.checkURI( address );
|
||||
int port = WebsocketConnector.getPort( uri );
|
||||
|
||||
Future<?> connector = WebsocketConnector.createConnector( m_apiEnvironment, this, uri, address, port, headers );
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
m_httpTasks.add( connector );
|
||||
}
|
||||
return new Object[] { true };
|
||||
}
|
||||
@@ -164,4 +214,20 @@ public class HTTPAPI implements ILuaAPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addCloseable( Closeable closeable )
|
||||
{
|
||||
synchronized( m_closeables )
|
||||
{
|
||||
m_closeables.add( closeable );
|
||||
}
|
||||
}
|
||||
|
||||
public void removeCloseable( Closeable closeable )
|
||||
{
|
||||
synchronized( m_closeables )
|
||||
{
|
||||
m_closeables.remove( closeable );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HTTPRequest
|
||||
{
|
||||
public static URL checkURL( String urlString ) throws HTTPRequestException
|
||||
{
|
||||
URL url;
|
||||
try
|
||||
{
|
||||
url = new URL( urlString );
|
||||
}
|
||||
catch( MalformedURLException e )
|
||||
{
|
||||
throw new HTTPRequestException( "URL malformed" );
|
||||
}
|
||||
|
||||
// Validate the URL
|
||||
String protocol = url.getProtocol().toLowerCase();
|
||||
if( !protocol.equals("http") && !protocol.equals("https") )
|
||||
{
|
||||
throw new HTTPRequestException( "URL not http" );
|
||||
}
|
||||
|
||||
// Compare the URL to the whitelist
|
||||
if( !ComputerCraft.http_whitelist.matches( url.getHost() ) || ComputerCraft.http_blacklist.matches( url.getHost() ) )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public HTTPRequest( String url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
|
||||
{
|
||||
// Parse the URL
|
||||
m_urlString = url;
|
||||
m_url = checkURL( m_urlString );
|
||||
m_binary = binary;
|
||||
|
||||
// Start the thread
|
||||
m_cancelled = false;
|
||||
m_complete = false;
|
||||
m_success = false;
|
||||
m_result = null;
|
||||
m_responseCode = -1;
|
||||
|
||||
Thread thread = new Thread( () ->
|
||||
{
|
||||
try
|
||||
{
|
||||
// Connect to the URL
|
||||
HttpURLConnection connection = (HttpURLConnection)m_url.openConnection();
|
||||
|
||||
if( postText != null )
|
||||
{
|
||||
connection.setRequestMethod( "POST" );
|
||||
connection.setDoOutput( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.setRequestMethod( "GET" );
|
||||
}
|
||||
|
||||
// Set headers
|
||||
connection.setRequestProperty( "accept-charset", "UTF-8" );
|
||||
if( postText != null )
|
||||
{
|
||||
connection.setRequestProperty( "content-type", "application/x-www-form-urlencoded; charset=utf-8" );
|
||||
connection.setRequestProperty( "content-encoding", "UTF-8" );
|
||||
}
|
||||
if( headers != null )
|
||||
{
|
||||
for( Map.Entry<String, String> header : headers.entrySet() )
|
||||
{
|
||||
connection.setRequestProperty( header.getKey(), header.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
// Send POST text
|
||||
if( postText != null )
|
||||
{
|
||||
OutputStream os = connection.getOutputStream();
|
||||
OutputStreamWriter osw;
|
||||
try
|
||||
{
|
||||
osw = new OutputStreamWriter( os, "UTF-8" );
|
||||
}
|
||||
catch( UnsupportedEncodingException e )
|
||||
{
|
||||
osw = new OutputStreamWriter( os );
|
||||
}
|
||||
BufferedWriter writer = new BufferedWriter( osw );
|
||||
writer.write( postText, 0, postText.length() );
|
||||
writer.close();
|
||||
}
|
||||
|
||||
// Read response
|
||||
InputStream is;
|
||||
int code = connection.getResponseCode();
|
||||
boolean responseSuccess;
|
||||
if (code >= 200 && code < 400) {
|
||||
is = connection.getInputStream();
|
||||
responseSuccess = true;
|
||||
} else {
|
||||
is = connection.getErrorStream();
|
||||
responseSuccess = false;
|
||||
}
|
||||
|
||||
byte[] result = ByteStreams.toByteArray( is );
|
||||
is.close();
|
||||
|
||||
synchronized( m_lock )
|
||||
{
|
||||
if( m_cancelled )
|
||||
{
|
||||
// We cancelled
|
||||
m_complete = true;
|
||||
m_success = false;
|
||||
m_result = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We completed
|
||||
m_complete = true;
|
||||
m_success = responseSuccess;
|
||||
m_result = result;
|
||||
m_responseCode = connection.getResponseCode();
|
||||
m_encoding = connection.getContentEncoding();
|
||||
|
||||
Joiner joiner = Joiner.on( ',' );
|
||||
Map<String, String> headers1 = m_responseHeaders = new HashMap<>();
|
||||
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
|
||||
headers1.put(header.getKey(), joiner.join( header.getValue() ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connection.disconnect(); // disconnect
|
||||
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
synchronized( m_lock )
|
||||
{
|
||||
// There was an error
|
||||
m_complete = true;
|
||||
m_success = false;
|
||||
m_result = null;
|
||||
}
|
||||
}
|
||||
} );
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public String getURL() {
|
||||
return m_urlString;
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
synchronized(m_lock) {
|
||||
m_cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isComplete()
|
||||
{
|
||||
synchronized(m_lock) {
|
||||
return m_complete;
|
||||
}
|
||||
}
|
||||
|
||||
public int getResponseCode() {
|
||||
synchronized(m_lock) {
|
||||
return m_responseCode;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getResponseHeaders() {
|
||||
synchronized (m_lock) {
|
||||
return m_responseHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasSuccessful()
|
||||
{
|
||||
synchronized(m_lock) {
|
||||
return m_success;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBinary()
|
||||
{
|
||||
return m_binary;
|
||||
}
|
||||
|
||||
public InputStream getContents()
|
||||
{
|
||||
byte[] result;
|
||||
synchronized(m_lock) {
|
||||
result = m_result;
|
||||
}
|
||||
|
||||
if( result != null ) {
|
||||
return new ByteArrayInputStream( result );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getEncoding() {
|
||||
return m_encoding;
|
||||
}
|
||||
|
||||
private final Object m_lock = new Object();
|
||||
private final URL m_url;
|
||||
private final String m_urlString;
|
||||
|
||||
private boolean m_complete;
|
||||
private boolean m_cancelled;
|
||||
private boolean m_success;
|
||||
private String m_encoding;
|
||||
private byte[] m_result;
|
||||
private boolean m_binary;
|
||||
private int m_responseCode;
|
||||
private Map<String, String> m_responseHeaders;
|
||||
}
|
@@ -1,17 +1,16 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
|
||||
public interface ILuaAPI extends ILuaObject
|
||||
/**
|
||||
* This exists purely to ensure binary compatibility.
|
||||
*
|
||||
* @see dan200.computercraft.api.lua.ILuaAPI
|
||||
*/
|
||||
public interface ILuaAPI extends dan200.computercraft.api.lua.ILuaAPI
|
||||
{
|
||||
String[] getNames();
|
||||
void advance( double v );
|
||||
|
||||
void startup(); // LT
|
||||
void advance( double _dt ); // MT
|
||||
void shutdown(); // LT
|
||||
default void update()
|
||||
{
|
||||
advance( 0.05 );
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
@@ -52,8 +53,8 @@ public class OSAPI implements ILuaAPI
|
||||
@Override
|
||||
public int compareTo( @Nonnull Alarm o )
|
||||
{
|
||||
double t = (double)m_day * 24.0 + m_time;
|
||||
double ot = (double)m_day * 24.0 + m_time;
|
||||
double t = m_day * 24.0 + m_time;
|
||||
double ot = m_day * 24.0 + m_time;
|
||||
if( t < ot ) {
|
||||
return -1;
|
||||
} else if( t > ot ) {
|
||||
@@ -102,7 +103,7 @@ public class OSAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double dt )
|
||||
public void update()
|
||||
{
|
||||
synchronized( m_timers )
|
||||
{
|
||||
@@ -135,13 +136,13 @@ public class OSAPI implements ILuaAPI
|
||||
|
||||
if( time > previousTime || day > previousDay )
|
||||
{
|
||||
double now = (double)m_day * 24.0 + m_time;
|
||||
double now = m_day * 24.0 + m_time;
|
||||
Iterator<Map.Entry<Integer, Alarm>> it = m_alarms.entrySet().iterator();
|
||||
while( it.hasNext() )
|
||||
{
|
||||
Map.Entry<Integer, Alarm> entry = it.next();
|
||||
Alarm alarm = entry.getValue();
|
||||
double t = (double)alarm.m_day * 24.0 + alarm.m_time;
|
||||
double t = alarm.m_day * 24.0 + alarm.m_time;
|
||||
if( now >= t )
|
||||
{
|
||||
queueLuaEvent( "alarm", new Object[]{ entry.getKey() } );
|
||||
@@ -196,8 +197,8 @@ public class OSAPI implements ILuaAPI
|
||||
private float getTimeForCalendar(Calendar c)
|
||||
{
|
||||
float time = c.get(Calendar.HOUR_OF_DAY);
|
||||
time += (float)c.get(Calendar.MINUTE) / 60.0f;
|
||||
time += (float)c.get(Calendar.SECOND) / (60.0f * 60.0f);
|
||||
time += c.get(Calendar.MINUTE) / 60.0f;
|
||||
time += c.get(Calendar.SECOND) / (60.0f * 60.0f);
|
||||
return time;
|
||||
}
|
||||
|
||||
@@ -296,7 +297,7 @@ public class OSAPI implements ILuaAPI
|
||||
// clock
|
||||
synchronized( m_timers )
|
||||
{
|
||||
return new Object[] { (double)m_clock * 0.05 };
|
||||
return new Object[] { m_clock * 0.05 };
|
||||
}
|
||||
}
|
||||
case 11:
|
||||
|
@@ -8,24 +8,26 @@ package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerThread;
|
||||
import dan200.computercraft.core.computer.ITask;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
|
||||
|
||||
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
|
||||
{
|
||||
private class PeripheralWrapper implements IComputerAccess
|
||||
private class PeripheralWrapper extends ComputerAccess
|
||||
{
|
||||
private final String m_side;
|
||||
private final IPeripheral m_peripheral;
|
||||
@@ -34,74 +36,71 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
private String[] m_methods;
|
||||
private Map<String, Integer> m_methodMap;
|
||||
private boolean m_attached;
|
||||
|
||||
private Set<String> m_mounts;
|
||||
|
||||
|
||||
public PeripheralWrapper( IPeripheral peripheral, String side )
|
||||
{
|
||||
super(m_environment);
|
||||
m_side = side;
|
||||
m_peripheral = peripheral;
|
||||
m_attached = false;
|
||||
|
||||
|
||||
m_type = peripheral.getType();
|
||||
m_methods = peripheral.getMethodNames();
|
||||
assert( m_type != null );
|
||||
assert( m_methods != null );
|
||||
|
||||
|
||||
m_methodMap = new HashMap<>();
|
||||
for(int i=0; i<m_methods.length; ++i ) {
|
||||
if( m_methods[i] != null ) {
|
||||
m_methodMap.put( m_methods[i], i );
|
||||
}
|
||||
}
|
||||
|
||||
m_mounts = new HashSet<>();
|
||||
}
|
||||
|
||||
|
||||
public IPeripheral getPeripheral()
|
||||
{
|
||||
return m_peripheral;
|
||||
}
|
||||
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
|
||||
public String[] getMethods()
|
||||
{
|
||||
return m_methods;
|
||||
}
|
||||
|
||||
|
||||
public synchronized boolean isAttached()
|
||||
{
|
||||
return m_attached;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void attach()
|
||||
{
|
||||
m_attached = true;
|
||||
m_peripheral.attach( this );
|
||||
}
|
||||
|
||||
public synchronized void detach()
|
||||
|
||||
public void detach()
|
||||
{
|
||||
// Call detach
|
||||
m_peripheral.detach( this );
|
||||
m_attached = false;
|
||||
|
||||
// Unmount everything the detach function forgot to do
|
||||
for( String m_mount : m_mounts )
|
||||
|
||||
synchronized( this )
|
||||
{
|
||||
m_fileSystem.unmount( m_mount );
|
||||
// Unmount everything the detach function forgot to do
|
||||
unmountAll();
|
||||
}
|
||||
m_mounts.clear();
|
||||
|
||||
m_attached = false;
|
||||
}
|
||||
|
||||
|
||||
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
int method = -1;
|
||||
synchronized( this )
|
||||
synchronized( this )
|
||||
{
|
||||
if( m_methodMap.containsKey( methodName ) )
|
||||
{
|
||||
@@ -119,13 +118,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
|
||||
// IComputerAccess implementation
|
||||
|
||||
@Override
|
||||
public String mount( @Nonnull String desiredLoc, @Nonnull IMount mount )
|
||||
{
|
||||
return mount( desiredLoc, mount, m_side );
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
|
||||
{
|
||||
@@ -133,32 +125,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
// Mount the location
|
||||
String location;
|
||||
synchronized( m_fileSystem )
|
||||
{
|
||||
location = findFreeLocation( desiredLoc );
|
||||
if( location != null )
|
||||
{
|
||||
try {
|
||||
m_fileSystem.mount( driveName, location, mount );
|
||||
} catch( FileSystemException e ) {
|
||||
// fail and return null
|
||||
}
|
||||
}
|
||||
}
|
||||
if( location != null )
|
||||
{
|
||||
m_mounts.add( location );
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount )
|
||||
{
|
||||
return mountWritable( desiredLoc, mount, m_side );
|
||||
return super.mount( desiredLoc, mount, driveName );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,77 +136,94 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
// Mount the location
|
||||
String location;
|
||||
synchronized( m_fileSystem )
|
||||
{
|
||||
location = findFreeLocation( desiredLoc );
|
||||
if( location != null )
|
||||
{
|
||||
try {
|
||||
m_fileSystem.mountWritable( driveName, location, mount );
|
||||
} catch( FileSystemException e ) {
|
||||
// fail and return null
|
||||
}
|
||||
}
|
||||
}
|
||||
if( location != null )
|
||||
{
|
||||
m_mounts.add( location );
|
||||
}
|
||||
return location;
|
||||
|
||||
return super.mountWritable( desiredLoc, mount, driveName );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void unmount( String location )
|
||||
{
|
||||
if( !m_attached ) {
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
if( location != null )
|
||||
if( !m_attached )
|
||||
{
|
||||
if( !m_mounts.contains( location ) ) {
|
||||
throw new RuntimeException( "You didn't mount this location" );
|
||||
}
|
||||
|
||||
m_fileSystem.unmount( location );
|
||||
m_mounts.remove( location );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getID()
|
||||
{
|
||||
if( !m_attached ) {
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
return m_environment.getComputerID();
|
||||
|
||||
super.unmount( location );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void queueEvent( @Nonnull final String event, final Object[] arguments )
|
||||
public int getID()
|
||||
{
|
||||
if( !m_attached ) {
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
m_environment.queueEvent( event, arguments );
|
||||
}
|
||||
return super.getID();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void queueEvent( @Nonnull final String event, final Object[] arguments )
|
||||
{
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
super.queueEvent( event, arguments );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public synchronized String getAttachmentName()
|
||||
public String getAttachmentName()
|
||||
{
|
||||
if( !m_attached ) {
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
return m_side;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Map<String, IPeripheral> getAvailablePeripherals()
|
||||
{
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
Map<String, IPeripheral> peripherals = new HashMap<>();
|
||||
for( PeripheralWrapper wrapper : m_peripherals )
|
||||
{
|
||||
if( wrapper != null && wrapper.isAttached() )
|
||||
{
|
||||
peripherals.put( wrapper.getAttachmentName(), wrapper.getPeripheral() );
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap( peripherals );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IPeripheral getAvailablePeripheral( @Nonnull String name )
|
||||
{
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
for( PeripheralWrapper wrapper : m_peripherals )
|
||||
{
|
||||
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
|
||||
{
|
||||
return wrapper.getPeripheral();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final IAPIEnvironment m_environment;
|
||||
private FileSystem m_fileSystem;
|
||||
private final PeripheralWrapper[] m_peripherals;
|
||||
private boolean m_running;
|
||||
|
||||
@@ -246,16 +231,16 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
{
|
||||
m_environment = _environment;
|
||||
m_environment.setPeripheralChangeListener( this );
|
||||
|
||||
|
||||
m_peripherals = new PeripheralWrapper[6];
|
||||
for(int i=0; i<6; ++i)
|
||||
{
|
||||
m_peripherals[i] = null;
|
||||
}
|
||||
|
||||
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
|
||||
// IPeripheralChangeListener
|
||||
|
||||
@Override
|
||||
@@ -282,11 +267,11 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
|
||||
// Queue a detachment event
|
||||
m_environment.queueEvent( "peripheral_detach", new Object[] { Computer.s_sideNames[side] } );
|
||||
}
|
||||
|
||||
|
||||
// Assign the new peripheral
|
||||
if( newPeripheral != null )
|
||||
{
|
||||
@@ -296,7 +281,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
{
|
||||
m_peripherals[side] = null;
|
||||
}
|
||||
|
||||
|
||||
if( m_peripherals[side] != null )
|
||||
{
|
||||
// Queue an attachment
|
||||
@@ -318,7 +303,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
}
|
||||
}, null );
|
||||
|
||||
|
||||
// Queue an attachment event
|
||||
m_environment.queueEvent( "peripheral", new Object[] { Computer.s_sideNames[side] } );
|
||||
}
|
||||
@@ -326,7 +311,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
|
||||
// ILuaAPI implementation
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
@@ -340,7 +325,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
m_fileSystem = m_environment.getFileSystem();
|
||||
m_running = true;
|
||||
for( int i=0; i<6; ++i )
|
||||
{
|
||||
@@ -352,12 +336,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
@@ -372,7 +351,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
wrapper.detach();
|
||||
}
|
||||
}
|
||||
m_fileSystem = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,8 +442,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
// call
|
||||
int side = parseSide( args );
|
||||
String methodName = getString( args, 1 );
|
||||
Object[] methodArgs = trimArray( args, 2 );
|
||||
|
||||
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
|
||||
|
||||
if( side >= 0 )
|
||||
{
|
||||
PeripheralWrapper p;
|
||||
@@ -486,13 +464,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Privates
|
||||
|
||||
private Object[] trimArray( Object[] array, int skip )
|
||||
{
|
||||
return Arrays.copyOfRange( array, skip, array.length );
|
||||
}
|
||||
// Privates
|
||||
|
||||
private int parseSide( Object[] args ) throws LuaException
|
||||
{
|
||||
@@ -506,25 +479,4 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String findFreeLocation( String desiredLoc )
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized( m_fileSystem )
|
||||
{
|
||||
if( !m_fileSystem.exists( desiredLoc ) )
|
||||
{
|
||||
return desiredLoc;
|
||||
}
|
||||
// We used to check foo2,foo3,foo4,etc here
|
||||
// but the disk drive does this itself now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
@@ -33,21 +34,6 @@ public class RedstoneAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup( )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.computer.IComputerEnvironment;
|
||||
@@ -36,21 +37,6 @@ public class TermAPI implements ILuaAPI
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup( )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance( double _dt )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
{
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
|
@@ -6,6 +6,8 @@ import dan200.computercraft.api.lua.LuaException;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
|
||||
public class EncodedInputHandle extends HandleGeneric
|
||||
{
|
||||
private final BufferedReader m_reader;
|
||||
@@ -49,6 +51,7 @@ public class EncodedInputHandle extends HandleGeneric
|
||||
"readLine",
|
||||
"readAll",
|
||||
"close",
|
||||
"read",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,6 +105,26 @@ public class EncodedInputHandle extends HandleGeneric
|
||||
// close
|
||||
close();
|
||||
return null;
|
||||
case 3:
|
||||
// read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int count = optInt( args, 0, 1 );
|
||||
if( count <= 0 || count >= 1024 * 16 )
|
||||
{
|
||||
throw new LuaException( "Count out of range" );
|
||||
}
|
||||
char[] bytes = new char[ count ];
|
||||
count = m_reader.read( bytes );
|
||||
if( count < 0 ) return null;
|
||||
String str = new String( bytes, 0, count );
|
||||
return new Object[] { str };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@@ -1,18 +1,18 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import dan200.computercraft.core.apis.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class HTTPCheck implements HTTPTask.IHTTPTask
|
||||
public class HTTPCheck implements Runnable
|
||||
{
|
||||
private final IAPIEnvironment environment;
|
||||
private final String urlString;
|
||||
private final URL url;
|
||||
private String error;
|
||||
|
||||
public HTTPCheck( String urlString, URL url )
|
||||
public HTTPCheck( IAPIEnvironment environment, String urlString, URL url )
|
||||
{
|
||||
this.environment = environment;
|
||||
this.urlString = urlString;
|
||||
this.url = url;
|
||||
}
|
||||
@@ -22,24 +22,12 @@ public class HTTPCheck implements HTTPTask.IHTTPTask
|
||||
{
|
||||
try
|
||||
{
|
||||
HTTPRequest.checkHost( url );
|
||||
HTTPRequest.checkHost( url.getHost() );
|
||||
environment.queueEvent( "http_check", new Object[] { urlString, true } );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
error = e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void whenFinished( IAPIEnvironment environment )
|
||||
{
|
||||
if( error == null )
|
||||
{
|
||||
environment.queueEvent( "http_check", new Object[] { urlString, true } );
|
||||
}
|
||||
else
|
||||
{
|
||||
environment.queueEvent( "http_check", new Object[] { urlString, false, error } );
|
||||
environment.queueEvent( "http_check", new Object[] { urlString, false, e.getMessage() } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Just a shared object for executing simple HTTP related tasks.
|
||||
*/
|
||||
public final class HTTPExecutor
|
||||
{
|
||||
public static final ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator( new ThreadPoolExecutor(
|
||||
4, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon( true )
|
||||
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
|
||||
.setNameFormat( "ComputerCraft-HTTP-%d" )
|
||||
.build()
|
||||
) );
|
||||
|
||||
public static final EventLoopGroup LOOP_GROUP = new NioEventLoopGroup( 4, new ThreadFactoryBuilder()
|
||||
.setDaemon( true )
|
||||
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
|
||||
.setNameFormat( "ComputerCraft-Netty-%d" )
|
||||
.build()
|
||||
);
|
||||
|
||||
private HTTPExecutor()
|
||||
{
|
||||
}
|
||||
}
|
@@ -12,7 +12,6 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
@@ -25,7 +24,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HTTPRequest implements HTTPTask.IHTTPTask
|
||||
public class HTTPRequest implements Runnable
|
||||
{
|
||||
public static URL checkURL( String urlString ) throws HTTPRequestException
|
||||
{
|
||||
@@ -55,11 +54,11 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
|
||||
return url;
|
||||
}
|
||||
|
||||
public static InetAddress checkHost( URL url ) throws HTTPRequestException
|
||||
public static InetAddress checkHost( String host ) throws HTTPRequestException
|
||||
{
|
||||
try
|
||||
{
|
||||
InetAddress resolved = InetAddress.getByName( url.getHost() );
|
||||
InetAddress resolved = InetAddress.getByName( host );
|
||||
if( !ComputerCraft.http_whitelist.matches( resolved ) || ComputerCraft.http_blacklist.matches( resolved ) )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
@@ -73,22 +72,16 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
|
||||
}
|
||||
}
|
||||
|
||||
private final IAPIEnvironment m_environment;
|
||||
private final URL m_url;
|
||||
private final String m_urlString;
|
||||
private final String m_postText;
|
||||
private final Map<String, String> m_headers;
|
||||
|
||||
private boolean m_success = false;
|
||||
private String m_encoding;
|
||||
private byte[] m_result;
|
||||
private boolean m_binary;
|
||||
private int m_responseCode = -1;
|
||||
private Map<String, String> m_responseHeaders;
|
||||
private String m_errorMessage;
|
||||
|
||||
public HTTPRequest( String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
|
||||
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
|
||||
{
|
||||
// Parse the URL
|
||||
m_environment = environment;
|
||||
m_urlString = urlString;
|
||||
m_url = url;
|
||||
m_binary = binary;
|
||||
@@ -96,28 +89,19 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
|
||||
m_headers = headers;
|
||||
}
|
||||
|
||||
public InputStream getContents()
|
||||
{
|
||||
byte[] result = m_result;
|
||||
if( result != null )
|
||||
{
|
||||
return new ByteArrayInputStream( result );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// First verify the address is allowed.
|
||||
try
|
||||
{
|
||||
checkHost( m_url );
|
||||
checkHost( m_url.getHost() );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
m_success = false;
|
||||
m_errorMessage = e.getMessage();
|
||||
// Queue the failure event if not.
|
||||
String error = e.getMessage();
|
||||
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, error == null ? "Could not connect" : error, null } );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -186,58 +170,36 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
|
||||
byte[] result = ByteStreams.toByteArray( is );
|
||||
is.close();
|
||||
|
||||
// We completed
|
||||
m_success = responseSuccess;
|
||||
m_result = result;
|
||||
m_responseCode = connection.getResponseCode();
|
||||
m_encoding = connection.getContentEncoding();
|
||||
|
||||
// We've got some sort of response, so let's build a resulting object.
|
||||
Joiner joiner = Joiner.on( ',' );
|
||||
Map<String, String> headers = m_responseHeaders = new HashMap<String, String>();
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
for( Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet() )
|
||||
{
|
||||
headers.put( header.getKey(), joiner.join( header.getValue() ) );
|
||||
}
|
||||
|
||||
InputStream contents = new ByteArrayInputStream( result );
|
||||
ILuaObject stream = wrapStream(
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
|
||||
connection.getResponseCode(), headers
|
||||
);
|
||||
|
||||
connection.disconnect(); // disconnect
|
||||
|
||||
// Queue the appropriate event.
|
||||
if( responseSuccess )
|
||||
{
|
||||
m_environment.queueEvent( "http_success", new Object[] { m_urlString, stream } );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", stream } );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
// There was an error
|
||||
m_success = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void whenFinished( IAPIEnvironment environment )
|
||||
{
|
||||
final String url = m_urlString;
|
||||
if( m_success )
|
||||
{
|
||||
// Queue the "http_success" event
|
||||
InputStream contents = getContents();
|
||||
Object result = wrapStream(
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, m_encoding ),
|
||||
m_responseCode, m_responseHeaders
|
||||
);
|
||||
environment.queueEvent( "http_success", new Object[] { url, result } );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue the "http_failure" event
|
||||
String error = "Could not connect";
|
||||
if( m_errorMessage != null ) error = m_errorMessage;
|
||||
|
||||
InputStream contents = getContents();
|
||||
Object result = null;
|
||||
if( contents != null )
|
||||
{
|
||||
result = wrapStream(
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, m_encoding ),
|
||||
m_responseCode, m_responseHeaders
|
||||
);
|
||||
}
|
||||
environment.queueEvent( "http_failure", new Object[] { url, error, result } );
|
||||
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", null } );
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
public class HTTPRequestException extends Exception
|
||||
{
|
@@ -1,61 +0,0 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* A task which executes asynchronously on a new thread.
|
||||
*
|
||||
* This functions very similarly to a {@link Future}, but with an additional
|
||||
* method which is called on the main thread when the task is completed.
|
||||
*/
|
||||
public class HTTPTask
|
||||
{
|
||||
public interface IHTTPTask extends Runnable
|
||||
{
|
||||
void whenFinished( IAPIEnvironment environment );
|
||||
}
|
||||
|
||||
private static final ExecutorService httpThreads = new ThreadPoolExecutor(
|
||||
4, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setDaemon( true )
|
||||
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
|
||||
.setNameFormat( "ComputerCraft-HTTP-%d" )
|
||||
.build()
|
||||
);
|
||||
|
||||
private final Future<?> future;
|
||||
private final IHTTPTask task;
|
||||
|
||||
private HTTPTask( Future<?> future, IHTTPTask task )
|
||||
{
|
||||
this.future = future;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public static HTTPTask submit( IHTTPTask task )
|
||||
{
|
||||
Future<?> future = httpThreads.submit( task );
|
||||
return new HTTPTask( future, task );
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
future.cancel( false );
|
||||
}
|
||||
|
||||
public boolean isFinished()
|
||||
{
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
public void whenFinished( IAPIEnvironment environment )
|
||||
{
|
||||
task.whenFinished( environment );
|
||||
}
|
||||
}
|
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.HTTPAPI;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.websocketx.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WebsocketConnection extends SimpleChannelInboundHandler<Object> implements ILuaObject, Closeable
|
||||
{
|
||||
public static final String SUCCESS_EVENT = "websocket_success";
|
||||
public static final String FAILURE_EVENT = "websocket_failure";
|
||||
public static final String CLOSE_EVENT = "websocket_closed";
|
||||
public static final String MESSAGE_EVENT = "websocket_message";
|
||||
|
||||
private final String url;
|
||||
private final IAPIEnvironment computer;
|
||||
private final HTTPAPI api;
|
||||
private boolean open = true;
|
||||
|
||||
private Channel channel;
|
||||
private final WebSocketClientHandshaker handshaker;
|
||||
|
||||
public WebsocketConnection( IAPIEnvironment computer, HTTPAPI api, WebSocketClientHandshaker handshaker, String url )
|
||||
{
|
||||
this.computer = computer;
|
||||
this.api = api;
|
||||
this.handshaker = handshaker;
|
||||
this.url = url;
|
||||
|
||||
api.addCloseable( this );
|
||||
}
|
||||
|
||||
private void close( boolean remove )
|
||||
{
|
||||
open = false;
|
||||
if( remove ) api.removeCloseable( this );
|
||||
|
||||
if( channel != null )
|
||||
{
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
close( false );
|
||||
}
|
||||
|
||||
private void onClosed()
|
||||
{
|
||||
close( true );
|
||||
computer.queueEvent( CLOSE_EVENT, new Object[] { url } );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded( ChannelHandlerContext ctx ) throws Exception
|
||||
{
|
||||
channel = ctx.channel();
|
||||
super.handlerAdded( ctx );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive( ChannelHandlerContext ctx ) throws Exception
|
||||
{
|
||||
handshaker.handshake( ctx.channel() );
|
||||
super.channelActive( ctx );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive( ChannelHandlerContext ctx ) throws Exception
|
||||
{
|
||||
onClosed();
|
||||
super.channelInactive( ctx );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0( ChannelHandlerContext ctx, Object msg ) throws Exception
|
||||
{
|
||||
Channel ch = ctx.channel();
|
||||
if( !handshaker.isHandshakeComplete() )
|
||||
{
|
||||
handshaker.finishHandshake( ch, (FullHttpResponse) msg );
|
||||
computer.queueEvent( SUCCESS_EVENT, new Object[] { url, this } );
|
||||
return;
|
||||
}
|
||||
|
||||
if( msg instanceof FullHttpResponse )
|
||||
{
|
||||
FullHttpResponse response = (FullHttpResponse) msg;
|
||||
throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString( CharsetUtil.UTF_8 ) + ')' );
|
||||
}
|
||||
|
||||
WebSocketFrame frame = (WebSocketFrame) msg;
|
||||
if( frame instanceof TextWebSocketFrame )
|
||||
{
|
||||
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, ((TextWebSocketFrame) frame).text() } );
|
||||
}
|
||||
else if( frame instanceof BinaryWebSocketFrame )
|
||||
{
|
||||
ByteBuf data = frame.content();
|
||||
byte[] converted = new byte[ data.readableBytes() ];
|
||||
data.readBytes( converted );
|
||||
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, data } );
|
||||
}
|
||||
else if( frame instanceof CloseWebSocketFrame )
|
||||
{
|
||||
ch.close();
|
||||
onClosed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
|
||||
{
|
||||
ctx.close();
|
||||
computer.queueEvent( FAILURE_EVENT, new Object[] {
|
||||
url,
|
||||
cause instanceof WebSocketHandshakeException ? cause.getMessage() : "Could not connect"
|
||||
} );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] { "receive", "send", "close" };
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
while( true )
|
||||
{
|
||||
checkOpen();
|
||||
Object[] event = context.pullEvent( MESSAGE_EVENT );
|
||||
if( event.length >= 3 && Objects.equal( event[ 1 ], url ) )
|
||||
{
|
||||
return new Object[] { event[ 2 ] };
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
checkOpen();
|
||||
String text = arguments.length > 0 && arguments[ 0 ] != null ? arguments[ 0 ].toString() : "";
|
||||
channel.writeAndFlush( new TextWebSocketFrame( text ) );
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
close( true );
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOpen() throws LuaException
|
||||
{
|
||||
if( !open ) throw new LuaException( "attempt to use a closed file" );
|
||||
}
|
||||
}
|
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.core.apis.HTTPAPI;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/*
|
||||
* Provides functionality to verify and connect to a remote websocket.
|
||||
*/
|
||||
public final class WebsocketConnector
|
||||
{
|
||||
private static final Object lock = new Object();
|
||||
private static TrustManagerFactory trustManager;
|
||||
|
||||
private WebsocketConnector()
|
||||
{
|
||||
}
|
||||
|
||||
private static TrustManagerFactory getTrustManager()
|
||||
{
|
||||
if( trustManager != null ) return trustManager;
|
||||
synchronized( lock )
|
||||
{
|
||||
if( trustManager != null ) return trustManager;
|
||||
|
||||
TrustManagerFactory tmf = null;
|
||||
try
|
||||
{
|
||||
tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
|
||||
tmf.init( (KeyStore) null );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
ComputerCraft.log.error( "Cannot setup trust manager", e );
|
||||
}
|
||||
|
||||
return trustManager = tmf;
|
||||
}
|
||||
}
|
||||
|
||||
public static URI checkURI( String address ) throws HTTPRequestException
|
||||
{
|
||||
URI uri = null;
|
||||
try
|
||||
{
|
||||
uri = new URI( address );
|
||||
}
|
||||
catch( URISyntaxException ignored )
|
||||
{
|
||||
}
|
||||
|
||||
if( uri == null || uri.getHost() == null )
|
||||
{
|
||||
try
|
||||
{
|
||||
uri = new URI( "ws://" + address );
|
||||
}
|
||||
catch( URISyntaxException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if( uri == null || uri.getHost() == null ) throw new HTTPRequestException( "URL malformed" );
|
||||
|
||||
String scheme = uri.getScheme();
|
||||
if( scheme == null )
|
||||
{
|
||||
try
|
||||
{
|
||||
uri = new URI( "ws://" + uri.toString() );
|
||||
}
|
||||
catch( URISyntaxException e )
|
||||
{
|
||||
throw new HTTPRequestException( "URL malformed" );
|
||||
}
|
||||
}
|
||||
else if( !scheme.equalsIgnoreCase( "wss" ) && !scheme.equalsIgnoreCase( "ws" ) )
|
||||
{
|
||||
throw new HTTPRequestException( "Invalid scheme '" + scheme + "'" );
|
||||
}
|
||||
|
||||
if( !ComputerCraft.http_whitelist.matches( uri.getHost() ) || ComputerCraft.http_blacklist.matches( uri.getHost() ) )
|
||||
{
|
||||
throw new HTTPRequestException( "Domain not permitted" );
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
public static int getPort( URI uri ) throws HTTPRequestException
|
||||
{
|
||||
int port = uri.getPort();
|
||||
if( port >= 0 ) return port;
|
||||
|
||||
String scheme = uri.getScheme();
|
||||
if( scheme.equalsIgnoreCase( "ws" ) )
|
||||
{
|
||||
return 80;
|
||||
}
|
||||
else if( scheme.equalsIgnoreCase( "wss" ) )
|
||||
{
|
||||
return 443;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HTTPRequestException( "Invalid scheme '" + scheme + "'" );
|
||||
}
|
||||
}
|
||||
|
||||
public static Future<?> createConnector( final IAPIEnvironment environment, final HTTPAPI api, final URI uri, final String address, final int port, final Map<String, String> headers )
|
||||
{
|
||||
return HTTPExecutor.EXECUTOR.submit( () -> {
|
||||
InetAddress resolved;
|
||||
try
|
||||
{
|
||||
resolved = HTTPRequest.checkHost( uri.getHost() );
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
environment.queueEvent( WebsocketConnection.FAILURE_EVENT, new Object[] { address, e.getMessage() } );
|
||||
return;
|
||||
}
|
||||
|
||||
InetSocketAddress socketAddress = new InetSocketAddress( resolved, uri.getPort() == -1 ? port : uri.getPort() );
|
||||
|
||||
final SslContext ssl;
|
||||
if( uri.getScheme().equalsIgnoreCase( "wss" ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
ssl = SslContextBuilder.forClient().trustManager( getTrustManager() ).build();
|
||||
}
|
||||
catch( SSLException e )
|
||||
{
|
||||
environment.queueEvent( WebsocketConnection.FAILURE_EVENT, new Object[] { address, "Cannot create secure socket" } );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl = null;
|
||||
}
|
||||
|
||||
HttpHeaders httpHeaders = new DefaultHttpHeaders();
|
||||
for( Map.Entry<String, String> header : headers.entrySet() )
|
||||
{
|
||||
httpHeaders.add( header.getKey(), header.getValue() );
|
||||
}
|
||||
|
||||
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, false, httpHeaders );
|
||||
final WebsocketConnection connection = new WebsocketConnection( environment, api, handshaker, address );
|
||||
|
||||
new Bootstrap()
|
||||
.group( HTTPExecutor.LOOP_GROUP )
|
||||
.channel( NioSocketChannel.class )
|
||||
.handler( new ChannelInitializer<SocketChannel>()
|
||||
{
|
||||
@Override
|
||||
protected void initChannel( SocketChannel ch ) throws Exception
|
||||
{
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
if( ssl != null ) p.addLast( ssl.newHandler( ch.alloc(), uri.getHost(), port ) );
|
||||
p.addLast( new HttpClientCodec(), new HttpObjectAggregator( 8192 ), connection );
|
||||
}
|
||||
} )
|
||||
.remoteAddress( socketAddress )
|
||||
.connect();
|
||||
} );
|
||||
}
|
||||
}
|
@@ -8,16 +8,21 @@ package dan200.computercraft.core.computer;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.apis.*;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.lua.LuaJLuaMachine;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -173,7 +178,91 @@ public class Computer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ComputerSystem extends ComputerAccess implements IComputerSystem
|
||||
{
|
||||
private final IAPIEnvironment m_environment;
|
||||
|
||||
private ComputerSystem( IAPIEnvironment m_environment )
|
||||
{
|
||||
super( m_environment );
|
||||
this.m_environment = m_environment;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getAttachmentName()
|
||||
{
|
||||
return "computer";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IFileSystem getFileSystem()
|
||||
{
|
||||
FileSystem fs = m_environment.getFileSystem();
|
||||
return fs == null ? null : fs.getMountWrapper();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLabel()
|
||||
{
|
||||
return m_environment.getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
private static class APIWrapper implements ILuaAPI
|
||||
{
|
||||
private final ILuaAPI delegate;
|
||||
private final ComputerSystem system;
|
||||
|
||||
private APIWrapper( ILuaAPI delegate, ComputerSystem system )
|
||||
{
|
||||
this.delegate = delegate;
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
return delegate.getNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup()
|
||||
{
|
||||
delegate.startup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update()
|
||||
{
|
||||
delegate.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
delegate.shutdown();
|
||||
system.unmountAll();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return delegate.getMethodNames();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
return delegate.callMethod( context, method, arguments );
|
||||
}
|
||||
}
|
||||
|
||||
private static IMount s_romMount = null;
|
||||
|
||||
private int m_id;
|
||||
@@ -371,7 +460,7 @@ public class Computer
|
||||
{
|
||||
for(ILuaAPI api : m_apis)
|
||||
{
|
||||
api.advance( _dt );
|
||||
api.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -420,14 +509,13 @@ public class Computer
|
||||
}
|
||||
}
|
||||
|
||||
public boolean pollChanged()
|
||||
public boolean pollAndResetChanged()
|
||||
{
|
||||
return m_externalOutputChanged;
|
||||
}
|
||||
|
||||
public void clearChanged()
|
||||
{
|
||||
m_externalOutputChanged = false;
|
||||
synchronized(this) {
|
||||
boolean changed = m_externalOutputChanged;
|
||||
m_externalOutputChanged = false;
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBlinking()
|
||||
@@ -452,7 +540,7 @@ public class Computer
|
||||
private boolean initFileSystem()
|
||||
{
|
||||
// Create the file system
|
||||
int id = assignID();
|
||||
assignID();
|
||||
try
|
||||
{
|
||||
m_fileSystem = new FileSystem( "hdd", getRootMount() );
|
||||
@@ -578,6 +666,11 @@ public class Computer
|
||||
{
|
||||
m_apis.add( api );
|
||||
}
|
||||
|
||||
public void addAPI( dan200.computercraft.core.apis.ILuaAPI api )
|
||||
{
|
||||
addAPI( (ILuaAPI) api );
|
||||
}
|
||||
|
||||
public void setPeripheral( int side, IPeripheral peripheral )
|
||||
{
|
||||
@@ -611,18 +704,27 @@ public class Computer
|
||||
m_apis.add( new FSAPI( m_apiEnvironment ) );
|
||||
m_apis.add( new PeripheralAPI( m_apiEnvironment ) );
|
||||
m_apis.add( new OSAPI( m_apiEnvironment ) );
|
||||
m_apis.add( new BitAPI( m_apiEnvironment ) );
|
||||
//m_apis.add( new BufferAPI( m_apiEnvironment ) );
|
||||
if( ComputerCraft.http_enable )
|
||||
{
|
||||
m_apis.add( new HTTPAPI( m_apiEnvironment ) );
|
||||
}
|
||||
|
||||
for( ILuaAPIFactory factory : ComputerCraft.getAPIFactories() )
|
||||
{
|
||||
ComputerSystem system = new ComputerSystem( m_apiEnvironment );
|
||||
ILuaAPI api = factory.create( system );
|
||||
if( api != null )
|
||||
{
|
||||
m_apis.add( api );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initLua()
|
||||
{
|
||||
// Create the lua machine
|
||||
ILuaMachine machine = new LuaJLuaMachine( this );
|
||||
ILuaMachine machine = new CobaltLuaMachine( this );
|
||||
|
||||
// Add the APIs
|
||||
for(ILuaAPI api : m_apis)
|
||||
@@ -688,6 +790,7 @@ public class Computer
|
||||
return;
|
||||
}
|
||||
m_state = State.Starting;
|
||||
m_externalOutputChanged = true;
|
||||
m_ticksSinceStart = 0;
|
||||
}
|
||||
|
||||
@@ -747,6 +850,7 @@ public class Computer
|
||||
|
||||
// Start a new state
|
||||
m_state = State.Running;
|
||||
m_externalOutputChanged = true;
|
||||
synchronized( m_machine )
|
||||
{
|
||||
m_machine.handleEvent( null, null );
|
||||
@@ -765,6 +869,7 @@ public class Computer
|
||||
return;
|
||||
}
|
||||
m_state = State.Stopping;
|
||||
m_externalOutputChanged = true;
|
||||
}
|
||||
|
||||
// Turn the computercraft off
|
||||
@@ -789,7 +894,7 @@ public class Computer
|
||||
// Shutdown our APIs
|
||||
synchronized( m_apis )
|
||||
{
|
||||
for(ILuaAPI api : m_apis)
|
||||
for( ILuaAPI api : m_apis )
|
||||
{
|
||||
api.shutdown();
|
||||
}
|
||||
@@ -828,6 +933,7 @@ public class Computer
|
||||
}
|
||||
|
||||
m_state = State.Off;
|
||||
m_externalOutputChanged = true;
|
||||
if( reboot )
|
||||
{
|
||||
m_startRequested = true;
|
||||
|
@@ -8,223 +8,328 @@ package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ComputerThread
|
||||
{
|
||||
private static final Object m_lock;
|
||||
|
||||
private static Thread m_thread;
|
||||
private static final WeakHashMap <Object, LinkedBlockingQueue<ITask>> m_computerTasks;
|
||||
private static final ArrayList <LinkedBlockingQueue<ITask>> m_computerTasksActive;
|
||||
private static final ArrayList <LinkedBlockingQueue<ITask>> m_computerTasksPending;
|
||||
private static final Object m_defaultQueue;
|
||||
private static final Object m_monitor;
|
||||
private static final int QUEUE_LIMIT = 256;
|
||||
|
||||
private static boolean m_running;
|
||||
private static boolean m_stopped;
|
||||
|
||||
static
|
||||
{
|
||||
m_lock = new Object();
|
||||
m_thread = null;
|
||||
m_computerTasks = new WeakHashMap<>();
|
||||
m_computerTasksPending = new ArrayList<>();
|
||||
m_computerTasksActive = new ArrayList<>();
|
||||
m_defaultQueue = new Object();
|
||||
m_monitor = new Object();
|
||||
m_running = false;
|
||||
m_stopped = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock used for modifications to the object
|
||||
*/
|
||||
private static final Object s_stateLock = new Object();
|
||||
|
||||
/**
|
||||
* Lock for various task operations
|
||||
*/
|
||||
private static final Object s_taskLock = new Object();
|
||||
|
||||
/**
|
||||
* Map of objects to task list
|
||||
*/
|
||||
private static final WeakHashMap<Object, BlockingQueue<ITask>> s_computerTaskQueues = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Active queues to execute
|
||||
*/
|
||||
private static final BlockingQueue<BlockingQueue<ITask>> s_computerTasksActive = new LinkedBlockingQueue<>();
|
||||
private static final Set<BlockingQueue<ITask>> s_computerTasksActiveSet = new HashSet<>();
|
||||
|
||||
/**
|
||||
* The default object for items which don't have an owner
|
||||
*/
|
||||
private static final Object s_defaultOwner = new Object();
|
||||
|
||||
/**
|
||||
* Whether the thread is stopped or should be stopped
|
||||
*/
|
||||
private static boolean s_stopped = false;
|
||||
|
||||
/**
|
||||
* The thread tasks execute on
|
||||
*/
|
||||
private static Thread[] s_threads = null;
|
||||
|
||||
private static final AtomicInteger s_ManagerCounter = new AtomicInteger( 1 );
|
||||
private static final AtomicInteger s_DelegateCounter = new AtomicInteger( 1 );
|
||||
|
||||
/**
|
||||
* Start the computer thread
|
||||
*/
|
||||
public static void start()
|
||||
{
|
||||
synchronized( m_lock )
|
||||
synchronized( s_stateLock )
|
||||
{
|
||||
if( m_running )
|
||||
s_stopped = false;
|
||||
if( s_threads == null || s_threads.length != ComputerCraft.computer_threads )
|
||||
{
|
||||
m_stopped = false;
|
||||
return;
|
||||
s_threads = new Thread[ ComputerCraft.computer_threads ];
|
||||
}
|
||||
|
||||
m_thread = new Thread( () ->
|
||||
|
||||
SecurityManager manager = System.getSecurityManager();
|
||||
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
|
||||
for( int i = 0; i < s_threads.length; i++ )
|
||||
{
|
||||
Thread thread = s_threads[ i ];
|
||||
if( thread == null || !thread.isAlive() )
|
||||
{
|
||||
thread = s_threads[ i ] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
|
||||
thread.setDaemon( true );
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to stop the computer thread
|
||||
*/
|
||||
public static void stop()
|
||||
{
|
||||
synchronized( s_stateLock )
|
||||
{
|
||||
if( s_threads != null )
|
||||
{
|
||||
s_stopped = true;
|
||||
for( Thread thread : s_threads )
|
||||
{
|
||||
if( thread != null && thread.isAlive() )
|
||||
{
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized( s_taskLock )
|
||||
{
|
||||
s_computerTaskQueues.clear();
|
||||
s_computerTasksActive.clear();
|
||||
s_computerTasksActiveSet.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a task to execute on the thread
|
||||
*
|
||||
* @param task The task to execute
|
||||
* @param computer The computer to execute it on, use {@code null} to execute on the default object.
|
||||
*/
|
||||
public static void queueTask( ITask task, Computer computer )
|
||||
{
|
||||
Object queueObject = computer == null ? s_defaultOwner : computer;
|
||||
|
||||
BlockingQueue<ITask> queue;
|
||||
synchronized( s_computerTaskQueues )
|
||||
{
|
||||
queue = s_computerTaskQueues.get( queueObject );
|
||||
if( queue == null )
|
||||
{
|
||||
s_computerTaskQueues.put( queueObject, queue = new LinkedBlockingQueue<>( QUEUE_LIMIT ) );
|
||||
}
|
||||
}
|
||||
|
||||
synchronized( s_taskLock )
|
||||
{
|
||||
if( queue.offer( task ) && !s_computerTasksActiveSet.contains( queue ) )
|
||||
{
|
||||
s_computerTasksActive.add( queue );
|
||||
s_computerTasksActiveSet.add( queue );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for pulling and managing computer tasks. This pulls a task from {@link #s_computerTasksActive},
|
||||
* creates a new thread using {@link TaskRunner} or reuses a previous one and uses that to execute the task.
|
||||
*
|
||||
* If the task times out, then it will attempt to interrupt the {@link TaskRunner} instance.
|
||||
*/
|
||||
private static final class TaskExecutor implements Runnable
|
||||
{
|
||||
private TaskRunner runner;
|
||||
private Thread thread;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
while( true )
|
||||
{
|
||||
synchronized( m_computerTasksPending )
|
||||
{
|
||||
if (!m_computerTasksPending.isEmpty())
|
||||
{
|
||||
Iterator<LinkedBlockingQueue<ITask>> it = m_computerTasksPending.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
LinkedBlockingQueue<ITask> queue = it.next();
|
||||
|
||||
if (!m_computerTasksActive.contains(queue))
|
||||
{
|
||||
m_computerTasksActive.add(queue);
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
/*
|
||||
m_computerTasksActive.addAll(m_computerTasksPending); // put any that have been added since
|
||||
m_computerTasksPending.clear();
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<LinkedBlockingQueue<ITask>> it = m_computerTasksActive.iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
LinkedBlockingQueue<ITask> queue = it.next();
|
||||
|
||||
if (queue == null || queue.isEmpty()) // we don't need the blocking part of the queue. Null check to ensure it exists due to a weird NPE I got
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
synchronized( m_lock )
|
||||
{
|
||||
if( m_stopped )
|
||||
{
|
||||
m_running = false;
|
||||
m_thread = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
final ITask task = queue.take();
|
||||
// Wait for an active queue to execute
|
||||
BlockingQueue<ITask> queue = s_computerTasksActive.take();
|
||||
|
||||
// Create the task
|
||||
Thread worker = new Thread( () ->
|
||||
{
|
||||
try {
|
||||
task.execute();
|
||||
} catch( Throwable e ) {
|
||||
ComputerCraft.log.error( "Error running task", e );
|
||||
}
|
||||
} );
|
||||
|
||||
// Run the task
|
||||
worker.setDaemon(true);
|
||||
worker.start();
|
||||
worker.join( 7000 );
|
||||
|
||||
if( worker.isAlive() )
|
||||
{
|
||||
// Task ran for too long
|
||||
// Initiate escape plan
|
||||
Computer computer = task.getOwner();
|
||||
if( computer != null )
|
||||
{
|
||||
// Step 1: Soft abort
|
||||
computer.abort( false );
|
||||
worker.join( 1500 );
|
||||
|
||||
if( worker.isAlive() )
|
||||
{
|
||||
// Step 2: Hard abort
|
||||
computer.abort( true );
|
||||
worker.join( 1500 );
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: abandon
|
||||
if( worker.isAlive() )
|
||||
{
|
||||
// ComputerCraft.log.warn( "Failed to abort Computer " + computer.getID() + ". Dangling lua thread could cause errors." );
|
||||
worker.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
synchronized (queue)
|
||||
{
|
||||
if (queue.isEmpty())
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (m_computerTasksActive.isEmpty() && m_computerTasksPending.isEmpty())
|
||||
// If threads should be stopped then return
|
||||
synchronized( s_stateLock )
|
||||
{
|
||||
synchronized (m_monitor)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_monitor.wait();
|
||||
}
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
}
|
||||
}
|
||||
if( s_stopped ) return;
|
||||
}
|
||||
|
||||
execute( queue );
|
||||
}
|
||||
}, "Computer Dispatch Thread" );
|
||||
|
||||
m_thread.setDaemon(true);
|
||||
m_thread.start();
|
||||
m_running = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop()
|
||||
{
|
||||
synchronized( m_lock )
|
||||
{
|
||||
if( m_running )
|
||||
}
|
||||
catch( InterruptedException ignored )
|
||||
{
|
||||
m_stopped = true;
|
||||
m_thread.interrupt();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void queueTask( ITask _task, Computer computer )
|
||||
{
|
||||
Object queueObject = computer;
|
||||
|
||||
if (queueObject == null)
|
||||
{
|
||||
queueObject = m_defaultQueue;
|
||||
}
|
||||
|
||||
LinkedBlockingQueue<ITask> queue = m_computerTasks.get(queueObject);
|
||||
|
||||
if (queue == null)
|
||||
private void execute( BlockingQueue<ITask> queue ) throws InterruptedException
|
||||
{
|
||||
m_computerTasks.put(queueObject, queue = new LinkedBlockingQueue<>( 256 ));
|
||||
}
|
||||
|
||||
synchronized ( m_computerTasksPending )
|
||||
{
|
||||
if( queue.offer( _task ) )
|
||||
ITask task = queue.remove();
|
||||
|
||||
if( thread == null || !thread.isAlive() )
|
||||
{
|
||||
if( !m_computerTasksPending.contains( queue ) )
|
||||
runner = new TaskRunner();
|
||||
|
||||
SecurityManager manager = System.getSecurityManager();
|
||||
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
|
||||
Thread thread = this.thread = new Thread( group, runner, "ComputerCraft-Computer-Runner" + s_DelegateCounter.getAndIncrement() );
|
||||
thread.setDaemon( true );
|
||||
thread.start();
|
||||
}
|
||||
|
||||
long start = System.nanoTime();
|
||||
|
||||
// Execute the task
|
||||
runner.submit( task );
|
||||
|
||||
try
|
||||
{
|
||||
// If we timed out rather than exiting:
|
||||
boolean done = runner.await( 7000 );
|
||||
if( !done )
|
||||
{
|
||||
m_computerTasksPending.add( queue );
|
||||
// Attempt to soft then hard abort
|
||||
Computer computer = task.getOwner();
|
||||
if( computer != null )
|
||||
{
|
||||
computer.abort( false );
|
||||
|
||||
done = runner.await( 1500 );
|
||||
if( !done )
|
||||
{
|
||||
computer.abort( true );
|
||||
done = runner.await( 1500 );
|
||||
}
|
||||
}
|
||||
|
||||
// Interrupt the thread
|
||||
if( !done )
|
||||
{
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
runner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
//System.out.println( "Event queue overflow" );
|
||||
long stop = System.nanoTime();
|
||||
Computer computer = task.getOwner();
|
||||
if( computer != null ) ComputerTimeTracker.addTiming( computer, stop - start );
|
||||
|
||||
// Re-add it back onto the queue or remove it
|
||||
synchronized( s_taskLock )
|
||||
{
|
||||
if( queue.isEmpty() )
|
||||
{
|
||||
s_computerTasksActiveSet.remove( queue );
|
||||
}
|
||||
else
|
||||
{
|
||||
s_computerTasksActive.add( queue );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (m_monitor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for the actual running of tasks. It waitin for the {@link TaskRunner#input} semaphore to be
|
||||
* triggered, consumes a task and then triggers {@link TaskRunner#finished}.
|
||||
*/
|
||||
private static final class TaskRunner implements Runnable
|
||||
{
|
||||
private final Semaphore input = new Semaphore();
|
||||
private final Semaphore finished = new Semaphore();
|
||||
private ITask task;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
m_monitor.notify();
|
||||
try
|
||||
{
|
||||
while( true )
|
||||
{
|
||||
input.await();
|
||||
try
|
||||
{
|
||||
task.execute();
|
||||
}
|
||||
catch( RuntimeException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Error running task.", e );
|
||||
}
|
||||
task = null;
|
||||
finished.signal();
|
||||
}
|
||||
}
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Error running task.", e );
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void submit( ITask task )
|
||||
{
|
||||
this.task = task;
|
||||
input.signal();
|
||||
}
|
||||
|
||||
boolean await( long timeout ) throws InterruptedException
|
||||
{
|
||||
return finished.await( timeout );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to allow awaiting/providing a signal.
|
||||
*
|
||||
* Java does provide similar classes, but I only needed something simple.
|
||||
*/
|
||||
private static final class Semaphore
|
||||
{
|
||||
private volatile boolean state = false;
|
||||
|
||||
synchronized void signal()
|
||||
{
|
||||
state = true;
|
||||
notify();
|
||||
}
|
||||
|
||||
synchronized void await() throws InterruptedException
|
||||
{
|
||||
while( !state ) wait();
|
||||
state = false;
|
||||
}
|
||||
|
||||
synchronized boolean await( long timeout ) throws InterruptedException
|
||||
{
|
||||
if( !state )
|
||||
{
|
||||
wait( timeout );
|
||||
if( !state ) return false;
|
||||
}
|
||||
state = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,116 @@
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tracks timing information about computers, including how long they ran for
|
||||
* and the number of events they handled.
|
||||
*
|
||||
* Note that this <em>will</em> track computers which have been deleted (hence
|
||||
* the presence of {@link #timingLookup} and {@link #timings}
|
||||
*/
|
||||
public class ComputerTimeTracker
|
||||
{
|
||||
public static class Timings
|
||||
{
|
||||
private final WeakReference<Computer> computer;
|
||||
private final int computerId;
|
||||
|
||||
private int tasks;
|
||||
|
||||
private long totalTime;
|
||||
private long maxTime;
|
||||
|
||||
public Timings( @Nonnull Computer computer )
|
||||
{
|
||||
this.computer = new WeakReference<>( computer );
|
||||
this.computerId = computer.getID();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Computer getComputer()
|
||||
{
|
||||
return computer.get();
|
||||
}
|
||||
|
||||
public int getComputerId()
|
||||
{
|
||||
return computerId;
|
||||
}
|
||||
|
||||
public int getTasks()
|
||||
{
|
||||
return tasks;
|
||||
}
|
||||
|
||||
public long getTotalTime()
|
||||
{
|
||||
return totalTime;
|
||||
}
|
||||
|
||||
public long getMaxTime()
|
||||
{
|
||||
return maxTime;
|
||||
}
|
||||
|
||||
public double getAverage()
|
||||
{
|
||||
return totalTime / (double) tasks;
|
||||
}
|
||||
|
||||
void update( long time )
|
||||
{
|
||||
tasks++;
|
||||
totalTime += time;
|
||||
if( time > maxTime ) maxTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tracking;
|
||||
private static final List<Timings> timings = new ArrayList<>();
|
||||
private static final Map<Computer, Timings> timingLookup = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
public synchronized static void start()
|
||||
{
|
||||
tracking = true;
|
||||
|
||||
timings.clear();
|
||||
timingLookup.clear();
|
||||
}
|
||||
|
||||
public synchronized static boolean stop()
|
||||
{
|
||||
if( !tracking ) return false;
|
||||
|
||||
tracking = false;
|
||||
timingLookup.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static synchronized List<Timings> getTimings()
|
||||
{
|
||||
return new ArrayList<>( timings );
|
||||
}
|
||||
|
||||
public static synchronized void addTiming( Computer computer, long time )
|
||||
{
|
||||
if( !tracking ) return;
|
||||
|
||||
Timings timings = ComputerTimeTracker.timingLookup.get( computer );
|
||||
if( timings == null )
|
||||
{
|
||||
timings = new Timings( computer );
|
||||
timingLookup.put( computer, timings );
|
||||
ComputerTimeTracker.timings.add( timings );
|
||||
}
|
||||
|
||||
timings.update( time );
|
||||
}
|
||||
}
|
@@ -9,7 +9,6 @@ package dan200.computercraft.core.filesystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
@@ -22,31 +21,31 @@ public class EmptyMount implements IMount
|
||||
// IMount implementation
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
return path.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
return path.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
public long getSize( @Nonnull String path )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
public InputStream openForRead( @Nonnull String path )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@@ -95,7 +95,7 @@ public class FileMount implements IWritableMount
|
||||
// IMount implementation
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
if( !created() )
|
||||
{
|
||||
@@ -109,7 +109,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
if( !created() )
|
||||
{
|
||||
@@ -339,7 +339,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRemainingSpace() throws IOException
|
||||
public long getRemainingSpace()
|
||||
{
|
||||
return Math.max( m_capacity - m_usedSpace, 0 );
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
@@ -65,7 +66,7 @@ public class FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReadOnly( String path ) throws FileSystemException
|
||||
public boolean isReadOnly( String path )
|
||||
{
|
||||
return (m_writableMount == null);
|
||||
}
|
||||
@@ -290,6 +291,7 @@ public class FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
private final FileSystemMount m_wrapper = new FileSystemMount( this );
|
||||
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
|
||||
private final Set<Closeable> m_openFiles = Collections.newSetFromMap( new WeakHashMap<Closeable, Boolean>() );
|
||||
|
||||
@@ -347,7 +349,7 @@ public class FileSystem
|
||||
mount( new MountWrapper( label, location, mount ) );
|
||||
}
|
||||
|
||||
private synchronized void mount( MountWrapper wrapper ) throws FileSystemException
|
||||
private synchronized void mount( MountWrapper wrapper )
|
||||
{
|
||||
String location = wrapper.getLocation();
|
||||
if( m_mounts.containsKey( location ) )
|
||||
@@ -734,6 +736,11 @@ public class FileSystem
|
||||
return match;
|
||||
}
|
||||
|
||||
public IFileSystem getMountWrapper()
|
||||
{
|
||||
return m_wrapper;
|
||||
}
|
||||
|
||||
private static String sanitizePath( String path )
|
||||
{
|
||||
return sanitizePath( path, false );
|
||||
|
@@ -0,0 +1,185 @@
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class FileSystemMount implements IFileSystem
|
||||
{
|
||||
private final FileSystem m_filesystem;
|
||||
|
||||
public FileSystemMount( FileSystem m_filesystem )
|
||||
{
|
||||
this.m_filesystem = m_filesystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.makeDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.delete( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream openForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForWrite( path, false );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream openForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForWrite( path, true );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRemainingSpace() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.getFreeSpace( "/" );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
Collections.addAll( contents, m_filesystem.list( path ) );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.getSize( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForRead( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String combine( String path, String child )
|
||||
{
|
||||
return m_filesystem.combine( path, child );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy( String from, String to ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.copy( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move( String from, String to ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.move( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
@@ -176,14 +176,14 @@ public class JarMount implements IMount
|
||||
// IMount implementation
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
return file != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
if( file != null )
|
||||
|
@@ -11,109 +11,135 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.ILuaTask;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ITask;
|
||||
import dan200.computercraft.core.computer.MainThread;
|
||||
|
||||
import org.luaj.vm2.*;
|
||||
import org.luaj.vm2.lib.OneArgFunction;
|
||||
import org.luaj.vm2.lib.VarArgFunction;
|
||||
import org.luaj.vm2.lib.ZeroArgFunction;
|
||||
import org.luaj.vm2.lib.jse.JsePlatform;
|
||||
import org.squiddev.cobalt.*;
|
||||
import org.squiddev.cobalt.compiler.CompileException;
|
||||
import org.squiddev.cobalt.compiler.LoadState;
|
||||
import org.squiddev.cobalt.debug.DebugFrame;
|
||||
import org.squiddev.cobalt.debug.DebugHandler;
|
||||
import org.squiddev.cobalt.debug.DebugState;
|
||||
import org.squiddev.cobalt.function.LibFunction;
|
||||
import org.squiddev.cobalt.function.LuaFunction;
|
||||
import org.squiddev.cobalt.function.VarArgFunction;
|
||||
import org.squiddev.cobalt.lib.*;
|
||||
import org.squiddev.cobalt.lib.platform.AbstractResourceManipulator;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LuaJLuaMachine implements ILuaMachine
|
||||
{
|
||||
private Computer m_computer;
|
||||
import static org.squiddev.cobalt.Constants.NONE;
|
||||
import static org.squiddev.cobalt.ValueFactory.valueOf;
|
||||
import static org.squiddev.cobalt.ValueFactory.varargsOf;
|
||||
|
||||
private LuaValue m_globals;
|
||||
private LuaValue m_loadString;
|
||||
private LuaValue m_assert;
|
||||
private LuaValue m_coroutine_create;
|
||||
private LuaValue m_coroutine_resume;
|
||||
private LuaValue m_coroutine_yield;
|
||||
|
||||
private LuaValue m_mainRoutine;
|
||||
public class CobaltLuaMachine implements ILuaMachine
|
||||
{
|
||||
private final Computer m_computer;
|
||||
|
||||
private final LuaState m_state;
|
||||
private final LuaTable m_globals;
|
||||
|
||||
private LuaThread m_mainRoutine;
|
||||
private String m_eventFilter;
|
||||
private String m_softAbortMessage;
|
||||
private String m_hardAbortMessage;
|
||||
|
||||
private Map<Object, LuaValue> m_valuesInProgress;
|
||||
private Map<LuaValue, Object> m_objectsInProgress;
|
||||
|
||||
public LuaJLuaMachine( Computer computer )
|
||||
public CobaltLuaMachine( Computer computer )
|
||||
{
|
||||
m_computer = computer;
|
||||
|
||||
// Create an environment to run in
|
||||
m_globals = JsePlatform.debugGlobals();
|
||||
m_loadString = m_globals.get("loadstring");
|
||||
m_assert = m_globals.get("assert");
|
||||
|
||||
LuaValue coroutine = m_globals.get("coroutine");
|
||||
final LuaValue native_coroutine_create = coroutine.get("create");
|
||||
|
||||
LuaValue debug = m_globals.get("debug");
|
||||
final LuaValue debug_sethook = debug.get("sethook");
|
||||
|
||||
coroutine.set("create", new OneArgFunction() {
|
||||
final LuaState state = this.m_state = new LuaState( new AbstractResourceManipulator()
|
||||
{
|
||||
@Override
|
||||
public LuaValue call( LuaValue value )
|
||||
public InputStream findResource( String filename )
|
||||
{
|
||||
final LuaThread thread = native_coroutine_create.call( value ).checkthread();
|
||||
debug_sethook.invoke( new LuaValue[] {
|
||||
thread,
|
||||
new ZeroArgFunction() {
|
||||
@Override
|
||||
public LuaValue call() {
|
||||
String hardAbortMessage = m_hardAbortMessage;
|
||||
if( hardAbortMessage != null )
|
||||
{
|
||||
LuaThread.yield(LuaValue.NIL);
|
||||
}
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
},
|
||||
LuaValue.NIL,
|
||||
LuaValue.valueOf(100000)
|
||||
} );
|
||||
return thread;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
m_coroutine_create = coroutine.get("create");
|
||||
m_coroutine_resume = coroutine.get("resume");
|
||||
m_coroutine_yield = coroutine.get("yield");
|
||||
|
||||
} );
|
||||
state.debug = new DebugHandler( state )
|
||||
{
|
||||
private int count = 0;
|
||||
private boolean hasSoftAbort;
|
||||
|
||||
@Override
|
||||
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
|
||||
{
|
||||
int count = ++this.count;
|
||||
if( count > 100000 )
|
||||
{
|
||||
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
|
||||
this.count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
handleSoftAbort();
|
||||
}
|
||||
|
||||
super.onInstruction( ds, di, pc, extras, top );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll() throws LuaError
|
||||
{
|
||||
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
|
||||
handleSoftAbort();
|
||||
}
|
||||
|
||||
private void handleSoftAbort() throws LuaError {
|
||||
// If the soft abort has been cleared then we can reset our flags and continue.
|
||||
String message = m_softAbortMessage;
|
||||
if (message == null) {
|
||||
hasSoftAbort = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSoftAbort && m_hardAbortMessage == null) {
|
||||
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
|
||||
return;
|
||||
}
|
||||
|
||||
hasSoftAbort = true;
|
||||
throw new LuaError(message);
|
||||
}
|
||||
};
|
||||
|
||||
m_globals = new LuaTable();
|
||||
state.setupThread( m_globals );
|
||||
|
||||
// Add basic libraries
|
||||
m_globals.load( state, new BaseLib() );
|
||||
m_globals.load( state, new TableLib() );
|
||||
m_globals.load( state, new StringLib() );
|
||||
m_globals.load( state, new MathLib() );
|
||||
m_globals.load( state, new CoroutineLib() );
|
||||
m_globals.load( state, new Bit32Lib() );
|
||||
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
|
||||
|
||||
// Register custom load/loadstring provider which automatically adds prefixes.
|
||||
LibFunction.bind( state, m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } );
|
||||
|
||||
// Remove globals we don't want to expose
|
||||
m_globals.set( "collectgarbage", LuaValue.NIL );
|
||||
m_globals.set( "dofile", LuaValue.NIL );
|
||||
m_globals.set( "loadfile", LuaValue.NIL );
|
||||
m_globals.set( "module", LuaValue.NIL );
|
||||
m_globals.set( "require", LuaValue.NIL );
|
||||
m_globals.set( "package", LuaValue.NIL );
|
||||
m_globals.set( "io", LuaValue.NIL );
|
||||
m_globals.set( "os", LuaValue.NIL );
|
||||
m_globals.set( "print", LuaValue.NIL );
|
||||
m_globals.set( "luajava", LuaValue.NIL );
|
||||
m_globals.set( "debug", LuaValue.NIL );
|
||||
m_globals.set( "newproxy", LuaValue.NIL );
|
||||
m_globals.set( "__inext", LuaValue.NIL );
|
||||
m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
m_globals.rawset( "dofile", Constants.NIL );
|
||||
m_globals.rawset( "loadfile", Constants.NIL );
|
||||
m_globals.rawset( "print", Constants.NIL );
|
||||
|
||||
// Add version globals
|
||||
m_globals.set( "_VERSION", "Lua 5.1" );
|
||||
m_globals.set( "_HOST", computer.getAPIEnvironment().getComputerEnvironment().getHostString() );
|
||||
m_globals.set( "_CC_DEFAULT_SETTINGS", toValue( ComputerCraft.default_computer_settings ) );
|
||||
m_globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
|
||||
m_globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) );
|
||||
m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.default_computer_settings ) );
|
||||
if( ComputerCraft.disable_lua51_features )
|
||||
{
|
||||
m_globals.set( "_CC_DISABLE_LUA51_FEATURES", toValue( true ) );
|
||||
m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE );
|
||||
}
|
||||
|
||||
// Our main function will go here
|
||||
@@ -123,7 +149,7 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
m_softAbortMessage = null;
|
||||
m_hardAbortMessage = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addAPI( ILuaAPI api )
|
||||
{
|
||||
@@ -132,10 +158,10 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
String[] names = api.getNames();
|
||||
for( String name : names )
|
||||
{
|
||||
m_globals.set( name, table );
|
||||
m_globals.rawset( name, table );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void loadBios( InputStream bios )
|
||||
{
|
||||
@@ -144,56 +170,31 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Read the whole bios into a string
|
||||
String biosText;
|
||||
try
|
||||
{
|
||||
InputStreamReader isr;
|
||||
try
|
||||
{
|
||||
isr = new InputStreamReader( bios, "UTF-8" );
|
||||
}
|
||||
catch( UnsupportedEncodingException e )
|
||||
{
|
||||
isr = new InputStreamReader( bios );
|
||||
}
|
||||
BufferedReader reader = new BufferedReader( isr );
|
||||
StringBuilder fileText = new StringBuilder( "" );
|
||||
String line = reader.readLine();
|
||||
while( line != null ) {
|
||||
fileText.append( line );
|
||||
line = reader.readLine();
|
||||
if( line != null ) {
|
||||
fileText.append( "\n" );
|
||||
}
|
||||
}
|
||||
biosText = fileText.toString();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new LuaError( "Could not read file" );
|
||||
}
|
||||
|
||||
// Load it
|
||||
LuaValue program = m_assert.call( m_loadString.call(
|
||||
toValue( biosText ), toValue( "bios.lua" )
|
||||
));
|
||||
m_mainRoutine = m_coroutine_create.call( program );
|
||||
LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals );
|
||||
m_mainRoutine = new LuaThread( m_state, value, m_globals );
|
||||
}
|
||||
catch( LuaError e )
|
||||
catch( CompileException e )
|
||||
{
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
m_mainRoutine.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
ComputerCraft.log.warn( "Could not load bios.lua ", e );
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
((LuaThread)m_mainRoutine).abandon();
|
||||
m_mainRoutine.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleEvent( String eventName, Object[] arguments )
|
||||
{
|
||||
@@ -206,35 +207,28 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
LuaValue[] resumeArgs;
|
||||
{
|
||||
Varargs resumeArgs = Constants.NONE;
|
||||
if( eventName != null )
|
||||
{
|
||||
resumeArgs = toValues( arguments, 2 );
|
||||
resumeArgs[0] = m_mainRoutine;
|
||||
resumeArgs[1] = toValue( eventName );
|
||||
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
resumeArgs = new LuaValue[1];
|
||||
resumeArgs[0] = m_mainRoutine;
|
||||
}
|
||||
|
||||
Varargs results = m_coroutine_resume.invoke( LuaValue.varargsOf( resumeArgs ) );
|
||||
if( m_hardAbortMessage != null )
|
||||
|
||||
Varargs results = m_mainRoutine.resume( resumeArgs );
|
||||
if( m_hardAbortMessage != null )
|
||||
{
|
||||
throw new LuaError( m_hardAbortMessage );
|
||||
}
|
||||
else if( results.arg1().checkboolean() == false )
|
||||
else if( !results.first().checkBoolean() )
|
||||
{
|
||||
throw new LuaError( results.arg(2).checkstring().toString() );
|
||||
throw new LuaError( results.arg( 2 ).checkString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
LuaValue filter = results.arg(2);
|
||||
if( filter.isstring() )
|
||||
LuaValue filter = results.arg( 2 );
|
||||
if( filter.isString() )
|
||||
{
|
||||
m_eventFilter = filter.toString();
|
||||
}
|
||||
@@ -243,16 +237,16 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
m_eventFilter = null;
|
||||
}
|
||||
}
|
||||
|
||||
LuaThread mainThread = (LuaThread)m_mainRoutine;
|
||||
if( mainThread.getStatus().equals("dead") )
|
||||
|
||||
LuaThread mainThread = m_mainRoutine;
|
||||
if( mainThread.getStatus().equals( "dead" ) )
|
||||
{
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
catch( LuaError e )
|
||||
{
|
||||
((LuaThread)m_mainRoutine).abandon();
|
||||
m_mainRoutine.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
finally
|
||||
@@ -262,13 +256,13 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void softAbort( String abortMessage )
|
||||
{
|
||||
m_softAbortMessage = abortMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void hardAbort( String abortMessage )
|
||||
{
|
||||
m_softAbortMessage = abortMessage;
|
||||
@@ -280,100 +274,88 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean restoreState( InputStream input )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isFinished()
|
||||
{
|
||||
return (m_mainRoutine == null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void unload()
|
||||
{
|
||||
if( m_mainRoutine != null )
|
||||
{
|
||||
LuaThread mainThread = (LuaThread)m_mainRoutine;
|
||||
LuaThread mainThread = m_mainRoutine;
|
||||
mainThread.abandon();
|
||||
m_mainRoutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryAbort() throws LuaError
|
||||
{
|
||||
// while( m_stopped )
|
||||
// {
|
||||
// m_coroutine_yield.call();
|
||||
// }
|
||||
|
||||
String abortMessage = m_softAbortMessage;
|
||||
if( abortMessage != null )
|
||||
{
|
||||
m_softAbortMessage = null;
|
||||
m_hardAbortMessage = null;
|
||||
throw new LuaError( abortMessage );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private LuaTable wrapLuaObject( ILuaObject object )
|
||||
{
|
||||
LuaTable table = new LuaTable();
|
||||
String[] methods = object.getMethodNames();
|
||||
for(int i=0; i<methods.length; ++i )
|
||||
for( int i = 0; i < methods.length; ++i )
|
||||
{
|
||||
if( methods[i] != null )
|
||||
if( methods[ i ] != null )
|
||||
{
|
||||
final int method = i;
|
||||
final ILuaObject apiObject = object;
|
||||
final String methodName = methods[i];
|
||||
table.set( methodName, new VarArgFunction() {
|
||||
final String methodName = methods[ i ];
|
||||
table.rawset( methodName, new VarArgFunction()
|
||||
{
|
||||
@Override
|
||||
public Varargs invoke( Varargs _args )
|
||||
public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
|
||||
{
|
||||
tryAbort();
|
||||
Object[] arguments = toObjects( _args, 1 );
|
||||
Object[] results;
|
||||
try
|
||||
{
|
||||
results = apiObject.callMethod( new ILuaContext() {
|
||||
results = apiObject.callMethod( new ILuaContext()
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
|
||||
{
|
||||
Object[] results = pullEventRaw( filter );
|
||||
if( results.length >= 1 && results[0].equals( "terminate" ) )
|
||||
if( results.length >= 1 && results[ 0 ].equals( "terminate" ) )
|
||||
{
|
||||
throw new LuaException( "Terminated", 0 );
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object[] pullEventRaw( String filter ) throws InterruptedException
|
||||
{
|
||||
return yield( new Object[] { filter } );
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
|
||||
{
|
||||
try
|
||||
{
|
||||
LuaValue[] yieldValues = toValues( yieldArgs, 0 );
|
||||
Varargs results = m_coroutine_yield.invoke( LuaValue.varargsOf( yieldValues ) );
|
||||
Varargs results = LuaThread.yield( state, toValues( yieldArgs ) );
|
||||
return toObjects( results, 1 );
|
||||
}
|
||||
catch( OrphanedThread e )
|
||||
{
|
||||
throw new InterruptedException();
|
||||
}
|
||||
catch( Throwable e )
|
||||
{
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -448,10 +430,10 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
Object[] response = pullEvent( "task_complete" );
|
||||
if( response.length >= 3 && response[ 1 ] instanceof Number && response[ 2 ] instanceof Boolean )
|
||||
{
|
||||
if( ( (Number)response[ 1 ] ).intValue() == taskID )
|
||||
if( ((Number) response[ 1 ]).intValue() == taskID )
|
||||
{
|
||||
Object[] returnValues = new Object[ response.length - 3 ];
|
||||
if( (Boolean)response[ 2 ] )
|
||||
if( (Boolean) response[ 2 ] )
|
||||
{
|
||||
// Extract the return values from the event and return them
|
||||
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
|
||||
@@ -460,9 +442,9 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
else
|
||||
{
|
||||
// Extract the error message from the event and raise it
|
||||
if( response.length >= 4 && response[3] instanceof String )
|
||||
if( response.length >= 4 && response[ 3 ] instanceof String )
|
||||
{
|
||||
throw new LuaException( (String)response[ 3 ] );
|
||||
throw new LuaException( (String) response[ 3 ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -479,7 +461,7 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
throw new OrphanedThread();
|
||||
}
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
throw new LuaError( e.getMessage(), e.getLevel() );
|
||||
@@ -492,7 +474,7 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
}
|
||||
throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 );
|
||||
}
|
||||
return LuaValue.varargsOf( toValues( results, 0 ) );
|
||||
return toValues( results );
|
||||
}
|
||||
} );
|
||||
}
|
||||
@@ -500,192 +482,257 @@ public class LuaJLuaMachine implements ILuaMachine
|
||||
return table;
|
||||
}
|
||||
|
||||
private LuaValue toValue( Object object )
|
||||
private LuaValue toValue( Object object, Map<Object, LuaValue> values )
|
||||
{
|
||||
if( object == null )
|
||||
{
|
||||
return LuaValue.NIL;
|
||||
return Constants.NIL;
|
||||
}
|
||||
else if( object instanceof Number )
|
||||
{
|
||||
double d = ((Number)object).doubleValue();
|
||||
return LuaValue.valueOf( d );
|
||||
double d = ((Number) object).doubleValue();
|
||||
return valueOf( d );
|
||||
}
|
||||
else if( object instanceof Boolean )
|
||||
{
|
||||
boolean b = (Boolean) object;
|
||||
return LuaValue.valueOf( b );
|
||||
return valueOf( (Boolean) object );
|
||||
}
|
||||
else if( object instanceof String )
|
||||
{
|
||||
String s = object.toString();
|
||||
return LuaValue.valueOf( s );
|
||||
return valueOf( s );
|
||||
}
|
||||
else if( object instanceof byte[] )
|
||||
{
|
||||
byte[] b = (byte[]) object;
|
||||
return LuaValue.valueOf( Arrays.copyOf( b, b.length ) );
|
||||
return valueOf( Arrays.copyOf( b, b.length ) );
|
||||
}
|
||||
else if( object instanceof Map )
|
||||
{
|
||||
// Table:
|
||||
// Start remembering stuff
|
||||
boolean clearWhenDone = false;
|
||||
try
|
||||
if( values == null )
|
||||
{
|
||||
if( m_valuesInProgress == null )
|
||||
{
|
||||
m_valuesInProgress = new IdentityHashMap<>();
|
||||
clearWhenDone = true;
|
||||
}
|
||||
else if( m_valuesInProgress.containsKey( object ) )
|
||||
{
|
||||
return m_valuesInProgress.get( object );
|
||||
}
|
||||
LuaValue table = new LuaTable();
|
||||
m_valuesInProgress.put( object, table );
|
||||
values = new IdentityHashMap<>();
|
||||
}
|
||||
else if( values.containsKey( object ) )
|
||||
{
|
||||
return values.get( object );
|
||||
}
|
||||
LuaTable table = new LuaTable();
|
||||
values.put( object, table );
|
||||
|
||||
// Convert all keys
|
||||
for( Map.Entry<?, ?> pair : ((Map<?, ?>) object).entrySet() )
|
||||
{
|
||||
LuaValue key = toValue( pair.getKey() );
|
||||
LuaValue value = toValue( pair.getValue() );
|
||||
if( !key.isnil() && !value.isnil() )
|
||||
{
|
||||
table.set( key, value );
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
finally
|
||||
// Convert all keys
|
||||
for( Map.Entry<?, ?> pair : ((Map<?, ?>) object).entrySet() )
|
||||
{
|
||||
// Clear (if exiting top level)
|
||||
if( clearWhenDone )
|
||||
LuaValue key = toValue( pair.getKey(), values );
|
||||
LuaValue value = toValue( pair.getValue(), values );
|
||||
if( !key.isNil() && !value.isNil() )
|
||||
{
|
||||
m_valuesInProgress = null;
|
||||
table.rawset( key, value );
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
else if( object instanceof ILuaObject )
|
||||
{
|
||||
return wrapLuaObject( (ILuaObject)object );
|
||||
return wrapLuaObject( (ILuaObject) object );
|
||||
}
|
||||
else
|
||||
{
|
||||
return LuaValue.NIL;
|
||||
}
|
||||
return Constants.NIL;
|
||||
}
|
||||
}
|
||||
|
||||
private LuaValue[] toValues( Object[] objects, int leaveEmpty )
|
||||
private Varargs toValues( Object[] objects )
|
||||
{
|
||||
if( objects == null || objects.length == 0 )
|
||||
if( objects == null || objects.length == 0 )
|
||||
{
|
||||
return new LuaValue[ leaveEmpty ];
|
||||
return Constants.NONE;
|
||||
}
|
||||
|
||||
LuaValue[] values = new LuaValue[objects.length + leaveEmpty];
|
||||
for( int i=0; i<values.length; ++i )
|
||||
|
||||
LuaValue[] values = new LuaValue[ objects.length ];
|
||||
for( int i = 0; i < values.length; ++i )
|
||||
{
|
||||
if( i < leaveEmpty )
|
||||
{
|
||||
values[i] = null;
|
||||
continue;
|
||||
}
|
||||
Object object = objects[i - leaveEmpty];
|
||||
values[i] = toValue( object );
|
||||
Object object = objects[ i ];
|
||||
values[ i ] = toValue( object, null );
|
||||
}
|
||||
return values;
|
||||
return varargsOf( values );
|
||||
}
|
||||
|
||||
private Object toObject( LuaValue value )
|
||||
private static Object toObject( LuaValue value, Map<LuaValue, Object> objects )
|
||||
{
|
||||
switch( value.type() )
|
||||
{
|
||||
case LuaValue.TNIL:
|
||||
case LuaValue.TNONE:
|
||||
case Constants.TNIL:
|
||||
case Constants.TNONE:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case LuaValue.TINT:
|
||||
case LuaValue.TNUMBER:
|
||||
case Constants.TINT:
|
||||
case Constants.TNUMBER:
|
||||
{
|
||||
return value.todouble();
|
||||
return value.toDouble();
|
||||
}
|
||||
case LuaValue.TBOOLEAN:
|
||||
case Constants.TBOOLEAN:
|
||||
{
|
||||
return value.toboolean();
|
||||
return value.toBoolean();
|
||||
}
|
||||
case LuaValue.TSTRING:
|
||||
case Constants.TSTRING:
|
||||
{
|
||||
LuaString str = value.checkstring();
|
||||
return str.tojstring();
|
||||
return value.toString();
|
||||
}
|
||||
case LuaValue.TTABLE:
|
||||
case Constants.TTABLE:
|
||||
{
|
||||
// Table:
|
||||
boolean clearWhenDone = false;
|
||||
try
|
||||
// Start remembering stuff
|
||||
if( objects == null )
|
||||
{
|
||||
// Start remembering stuff
|
||||
if( m_objectsInProgress == null )
|
||||
{
|
||||
m_objectsInProgress = new IdentityHashMap<>();
|
||||
clearWhenDone = true;
|
||||
}
|
||||
else if( m_objectsInProgress.containsKey( value ) )
|
||||
{
|
||||
return m_objectsInProgress.get( value );
|
||||
}
|
||||
Map<Object, Object> table = new HashMap<>();
|
||||
m_objectsInProgress.put( value, table );
|
||||
|
||||
// Convert all keys
|
||||
LuaValue k = LuaValue.NIL;
|
||||
while( true )
|
||||
{
|
||||
Varargs keyValue = value.next( k );
|
||||
k = keyValue.arg1();
|
||||
if( k.isnil() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
LuaValue v = keyValue.arg(2);
|
||||
Object keyObject = toObject(k);
|
||||
Object valueObject = toObject(v);
|
||||
if( keyObject != null && valueObject != null )
|
||||
{
|
||||
table.put( keyObject, valueObject );
|
||||
}
|
||||
}
|
||||
return table;
|
||||
objects = new IdentityHashMap<>();
|
||||
}
|
||||
finally
|
||||
else if( objects.containsKey( value ) )
|
||||
{
|
||||
// Clear (if exiting top level)
|
||||
if( clearWhenDone )
|
||||
return objects.get( value );
|
||||
}
|
||||
Map<Object, Object> table = new HashMap<>();
|
||||
objects.put( value, table );
|
||||
|
||||
LuaTable luaTable = (LuaTable) value;
|
||||
|
||||
// Convert all keys
|
||||
LuaValue k = Constants.NIL;
|
||||
while( true )
|
||||
{
|
||||
Varargs keyValue;
|
||||
try
|
||||
{
|
||||
m_objectsInProgress = null;
|
||||
keyValue = luaTable.next( k );
|
||||
}
|
||||
catch( LuaError luaError )
|
||||
{
|
||||
break;
|
||||
}
|
||||
k = keyValue.first();
|
||||
if( k.isNil() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
LuaValue v = keyValue.arg( 2 );
|
||||
Object keyObject = toObject( k, objects );
|
||||
Object valueObject = toObject( v, objects );
|
||||
if( keyObject != null && valueObject != null )
|
||||
{
|
||||
table.put( keyObject, valueObject );
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object[] toObjects( Varargs values, int startIdx )
|
||||
|
||||
private static Object[] toObjects( Varargs values, int startIdx )
|
||||
{
|
||||
int count = values.narg();
|
||||
int count = values.count();
|
||||
Object[] objects = new Object[ count - startIdx + 1 ];
|
||||
for( int n=startIdx; n<=count; ++n )
|
||||
for( int n = startIdx; n <= count; ++n )
|
||||
{
|
||||
int i = n - startIdx;
|
||||
LuaValue value = values.arg(n);
|
||||
objects[i] = toObject( value );
|
||||
LuaValue value = values.arg( n );
|
||||
objects[ i ] = toObject( value, null );
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
private static class PrefixLoader extends VarArgFunction
|
||||
{
|
||||
private static final LuaString FUNCTION_STR = valueOf( "function" );
|
||||
private static final LuaString EQ_STR = valueOf( "=" );
|
||||
|
||||
@Override
|
||||
public Varargs invoke( LuaState state, Varargs args ) throws LuaError
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
case 0: // "load", // ( func [,chunkname] ) -> chunk | nil, msg
|
||||
{
|
||||
LuaValue func = args.arg( 1 ).checkFunction();
|
||||
LuaString chunkname = args.arg( 2 ).optLuaString( FUNCTION_STR );
|
||||
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
|
||||
{
|
||||
chunkname = OperationHelper.concat( EQ_STR, chunkname );
|
||||
}
|
||||
return BaseLib.loadStream( state, new StringInputStream( state, func ), chunkname );
|
||||
}
|
||||
case 1: // "loadstring", // ( string [,chunkname] ) -> chunk | nil, msg
|
||||
{
|
||||
LuaString script = args.arg( 1 ).checkLuaString();
|
||||
LuaString chunkname = args.arg( 2 ).optLuaString( script );
|
||||
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
|
||||
{
|
||||
chunkname = OperationHelper.concat( EQ_STR, chunkname );
|
||||
}
|
||||
return BaseLib.loadStream( state, script.toInputStream(), chunkname );
|
||||
}
|
||||
}
|
||||
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringInputStream extends InputStream
|
||||
{
|
||||
private final LuaState state;
|
||||
private final LuaValue func;
|
||||
private byte[] bytes;
|
||||
private int offset, remaining = 0;
|
||||
|
||||
public StringInputStream( LuaState state, LuaValue func )
|
||||
{
|
||||
this.state = state;
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException
|
||||
{
|
||||
if( remaining <= 0 )
|
||||
{
|
||||
LuaValue s;
|
||||
try
|
||||
{
|
||||
s = OperationHelper.call( state, func );
|
||||
} catch (LuaError e)
|
||||
{
|
||||
throw new IOException( e );
|
||||
}
|
||||
|
||||
if( s.isNil() )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
LuaString ls;
|
||||
try
|
||||
{
|
||||
ls = s.strvalue();
|
||||
} catch (LuaError e)
|
||||
{
|
||||
throw new IOException( e );
|
||||
}
|
||||
bytes = ls.bytes;
|
||||
offset = ls.offset;
|
||||
remaining = ls.length;
|
||||
if( remaining <= 0 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
--remaining;
|
||||
return bytes[offset++];
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.lua;
|
||||
import dan200.computercraft.core.apis.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
@@ -6,7 +6,11 @@
|
||||
|
||||
package dan200.computercraft.server.proxy;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.blocks.TileComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
|
||||
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
|
||||
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
|
||||
@@ -64,11 +68,6 @@ public class ComputerCraftProxyServer extends ComputerCraftProxyCommon
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playRecord( SoundEvent record, String recordInfo, World world, BlockPos pos )
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDiskDriveGUI( InventoryPlayer inventory, TileDiskDrive drive )
|
||||
@@ -100,6 +99,12 @@ public class ComputerCraftProxyServer extends ComputerCraftProxyCommon
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getComputerGUI( IComputer computer, int width, int height, ComputerFamily family )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getWorldDir( World world )
|
||||
{
|
||||
|
@@ -0,0 +1,60 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class CommandComputer extends CommandBase
|
||||
{
|
||||
@Override
|
||||
@Nonnull
|
||||
public String getName()
|
||||
{
|
||||
return "computer";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public String getUsage( @Nonnull ICommandSender sender )
|
||||
{
|
||||
return "computer <id> <value1> [value2]...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args ) throws CommandException
|
||||
{
|
||||
if( args.length < 2 )
|
||||
{
|
||||
throw new CommandException( "Usage: /computer <id> <value1> [value2]..." );
|
||||
}
|
||||
try
|
||||
{
|
||||
ServerComputer computer = ComputerCraft.serverComputerRegistry.lookup( Integer.valueOf( args[ 0 ] ) );
|
||||
if( computer != null && computer.getFamily() == ComputerFamily.Command )
|
||||
{
|
||||
computer.queueEvent( "computer_command", ArrayUtils.remove( args, 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CommandException( "Computer #" + args[ 0 ] + " is not a Command Computer" );
|
||||
}
|
||||
}
|
||||
catch( NumberFormatException e )
|
||||
{
|
||||
throw new CommandException( "Invalid ID" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,451 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerTimeTracker;
|
||||
import dan200.computercraft.shared.command.framework.*;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
|
||||
|
||||
public final class CommandComputerCraft extends CommandDelegate
|
||||
{
|
||||
public CommandComputerCraft()
|
||||
{
|
||||
super( create() );
|
||||
}
|
||||
|
||||
private static ISubCommand create()
|
||||
{
|
||||
CommandRoot root = new CommandRoot(
|
||||
"computercraft", "Various commands for controlling computers.",
|
||||
"The /computercraft command provides various debugging and administrator tools for controlling and " +
|
||||
"interacting with computers."
|
||||
);
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"dump", "[id]", "Display the status of computers.", UserLevel.OWNER_OP,
|
||||
"Display the status of all computers or specific information about one computer. You can specify the " +
|
||||
"computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\")."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
TextTable table = new TextTable( "Instance", "Id", "On", "Position" );
|
||||
|
||||
List<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
|
||||
// Unless we're on a server, limit the number of rows we can send.
|
||||
if( !(context.getSender() instanceof MinecraftServer) )
|
||||
{
|
||||
World world = context.getSender().getEntityWorld();
|
||||
BlockPos pos = context.getSender().getPosition();
|
||||
|
||||
computers.sort( ( a, b ) -> {
|
||||
if( a.getWorld() == b.getWorld() && a.getWorld() == world )
|
||||
{
|
||||
return Double.compare( a.getPosition().distanceSq( pos ), b.getPosition().distanceSq( pos ) );
|
||||
}
|
||||
else if( a.getWorld() == world )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if( b.getWorld() == world )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Integer.compare( a.getInstanceID(), b.getInstanceID() );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
table.addRow(
|
||||
linkComputer( computer ),
|
||||
text( Integer.toString( computer.getID() ) ),
|
||||
bool( computer.isOn() ),
|
||||
linkPosition( context, computer )
|
||||
);
|
||||
}
|
||||
|
||||
table.displayTo( context.getSender() );
|
||||
}
|
||||
else if( arguments.size() == 1 )
|
||||
{
|
||||
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||
|
||||
TextTable table = new TextTable();
|
||||
table.addRow( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) );
|
||||
table.addRow( header( "Id" ), text( Integer.toString( computer.getID() ) ) );
|
||||
table.addRow( header( "Label" ), text( computer.getLabel() ) );
|
||||
table.addRow( header( "On" ), bool( computer.isOn() ) );
|
||||
table.addRow( header( "Position" ), linkPosition( context, computer ) );
|
||||
table.addRow( header( "Family" ), text( computer.getFamily().toString() ) );
|
||||
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
IPeripheral peripheral = computer.getPeripheral( i );
|
||||
if( peripheral != null )
|
||||
{
|
||||
table.addRow( header( "Peripheral " + Computer.s_sideNames[i] ), text( peripheral.getType() ) );
|
||||
}
|
||||
}
|
||||
|
||||
table.displayTo( context.getSender() );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CommandException( context.getFullUsage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 1
|
||||
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||
: Collections.emptyList();
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"shutdown", "[ids...]", "Shutdown computers remotely.", UserLevel.OWNER_OP,
|
||||
"Shutdown the listed computers or all if none are specified. You can specify the computer's instance id " +
|
||||
"(e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\")."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
Set<ServerComputer> computers = Sets.newHashSet();
|
||||
if( arguments.size() > 0 )
|
||||
{
|
||||
for( String arg : arguments )
|
||||
{
|
||||
computers.addAll( ComputerSelector.getComputers( arg ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
}
|
||||
|
||||
int shutdown = 0;
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
if( computer.isOn() ) shutdown++;
|
||||
computer.unload();
|
||||
}
|
||||
context.getSender().sendMessage( text( "Shutdown " + shutdown + " / " + computers.size() + " computers" ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 0
|
||||
? Collections.emptyList()
|
||||
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"turn-on", "ids...", "Turn computers on remotely.", UserLevel.OWNER_OP,
|
||||
"Turn on the listed computers. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) " +
|
||||
"or label (e.g. \"@My Computer\")."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
Set<ServerComputer> computers = Sets.newHashSet();
|
||||
if( arguments.size() > 0 )
|
||||
{
|
||||
for( String arg : arguments )
|
||||
{
|
||||
computers.addAll( ComputerSelector.getComputers( arg ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
}
|
||||
|
||||
int on = 0;
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
if( !computer.isOn() ) on++;
|
||||
computer.turnOn();
|
||||
}
|
||||
context.getSender().sendMessage( text( "Turned on " + on + " / " + computers.size() + " computers" ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 0
|
||||
? Collections.emptyList()
|
||||
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"tp", "<id>", "Teleport to a specific computer.", UserLevel.OP,
|
||||
"Teleport to the location of a computer. You can either specify the computer's instance " +
|
||||
"id (e.g. 123) or computer id (e.g #123)."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() != 1 ) throw new CommandException( context.getFullUsage() );
|
||||
|
||||
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||
World world = computer.getWorld();
|
||||
BlockPos pos = computer.getPosition();
|
||||
|
||||
if( world == null || pos == null ) throw new CommandException( "Cannot locate computer in world" );
|
||||
|
||||
ICommandSender sender = context.getSender();
|
||||
if( !(sender instanceof Entity) ) throw new CommandException( "Sender is not an entity" );
|
||||
|
||||
if( sender instanceof EntityPlayerMP )
|
||||
{
|
||||
EntityPlayerMP entity = (EntityPlayerMP) sender;
|
||||
if( entity.getEntityWorld() != world )
|
||||
{
|
||||
context.getServer().getPlayerList().changePlayerDimension( entity, world.provider.getDimension() );
|
||||
}
|
||||
|
||||
entity.setPositionAndUpdate( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5 );
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity entity = (Entity) sender;
|
||||
if( entity.getEntityWorld() != world )
|
||||
{
|
||||
entity.changeDimension( world.provider.getDimension() );
|
||||
}
|
||||
|
||||
entity.setLocationAndAngles(
|
||||
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5,
|
||||
entity.rotationYaw, entity.rotationPitch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 1
|
||||
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||
: Collections.emptyList();
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"view", "<id>", "View the terminal of a computer.", UserLevel.OP,
|
||||
"Open the terminal of a computer, allowing remote control of a computer. This does not provide access to " +
|
||||
"turtle's inventories. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123)."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() != 1 ) throw new CommandException( context.getFullUsage() );
|
||||
|
||||
ICommandSender sender = context.getSender();
|
||||
if( !(sender instanceof EntityPlayerMP) )
|
||||
{
|
||||
throw new CommandException( "Cannot open terminal for non-player" );
|
||||
}
|
||||
|
||||
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||
ComputerCraft.openComputerGUI( (EntityPlayerMP) sender, computer );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 1
|
||||
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||
: Collections.emptyList();
|
||||
}
|
||||
} );
|
||||
|
||||
CommandRoot track = new CommandRoot( "track", "Track execution times for computers.",
|
||||
"Track how long computers execute for, as well as how many events they handle. This presents information in " +
|
||||
"a similar way to /forge track and can be useful for diagnosing lag." );
|
||||
root.register( track );
|
||||
|
||||
track.register( new SubCommandBase(
|
||||
"start", "Start tracking all computers", UserLevel.OWNER_OP,
|
||||
"Start tracking all computers' execution times and event counts. This will discard the results of previous runs."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
ComputerTimeTracker.start();
|
||||
|
||||
String stopCommand = "/" + context.parent().getFullPath() + " stop";
|
||||
context.getSender().sendMessage( list(
|
||||
text( "Run " ),
|
||||
link( text( stopCommand ), stopCommand, "Click to stop tracking" ),
|
||||
text( " to stop tracking and view the results" )
|
||||
) );
|
||||
}
|
||||
} );
|
||||
|
||||
track.register( new SubCommandBase(
|
||||
"stop", "Stop tracking all computers", UserLevel.OWNER_OP,
|
||||
"Stop tracking all computers' events and execution times"
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( !ComputerTimeTracker.stop() ) throw new CommandException( "Tracking not enabled" );
|
||||
displayTimings( context );
|
||||
}
|
||||
} );
|
||||
|
||||
track.register( new SubCommandBase(
|
||||
"dump", "Dump the latest track results", UserLevel.OWNER_OP,
|
||||
"Dump the latest results of computer tracking."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
displayTimings( context );
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"reload", "Reload the ComputerCraft config file", UserLevel.OWNER_OP,
|
||||
"Reload the ComputerCraft config file"
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
ComputerCraft.loadConfig();
|
||||
ComputerCraft.syncConfig();
|
||||
context.getSender().sendMessage( new TextComponentString( "Reloaded config" ) );
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static ITextComponent linkComputer( ServerComputer computer )
|
||||
{
|
||||
return link(
|
||||
text( Integer.toString( computer.getInstanceID() ) ),
|
||||
"/computercraft dump " + computer.getInstanceID(),
|
||||
"View more info about this computer"
|
||||
);
|
||||
}
|
||||
|
||||
private static ITextComponent linkPosition( CommandContext context, ServerComputer computer )
|
||||
{
|
||||
if( UserLevel.OP.canExecute( context ) )
|
||||
{
|
||||
return link(
|
||||
position( computer.getPosition() ),
|
||||
"/computercraft tp " + computer.getInstanceID(),
|
||||
"Teleport to this computer"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return position( computer.getPosition() );
|
||||
}
|
||||
}
|
||||
|
||||
private static void displayTimings( CommandContext context ) throws CommandException
|
||||
{
|
||||
List<ComputerTimeTracker.Timings> timings = ComputerTimeTracker.getTimings();
|
||||
if( timings.isEmpty() ) throw new CommandException( "No timings available" );
|
||||
|
||||
timings.sort( Comparator.comparing( ComputerTimeTracker.Timings::getAverage ).reversed() );
|
||||
TextTable table = new TextTable( "Computer", "Tasks", "Total", "Average", "Maximum" );
|
||||
|
||||
Map<Computer, ServerComputer> lookup = new HashMap<>();
|
||||
int maxId = 0, maxInstance = 0;
|
||||
for( ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers() )
|
||||
{
|
||||
lookup.put( server.getComputer(), server );
|
||||
|
||||
if( server.getInstanceID() > maxInstance ) maxInstance = server.getInstanceID();
|
||||
if( server.getID() > maxId ) maxId = server.getID();
|
||||
}
|
||||
|
||||
ICommandSender sender = context.getSender();
|
||||
boolean isPlayer = sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
|
||||
|
||||
for( ComputerTimeTracker.Timings entry : timings )
|
||||
{
|
||||
Computer computer = entry.getComputer();
|
||||
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
|
||||
|
||||
ITextComponent computerComponent = new TextComponentString( "" )
|
||||
.appendSibling( serverComputer == null ? text( "?" ) : linkComputer( serverComputer ) )
|
||||
.appendText( " (id " + entry.getComputerId() + ")" );
|
||||
|
||||
if( serverComputer != null && UserLevel.OP.canExecute( context ) && isPlayer )
|
||||
{
|
||||
computerComponent
|
||||
.appendText( " " )
|
||||
.appendSibling( link(
|
||||
text( "\u261b" ),
|
||||
"/computercraft tp " + serverComputer.getInstanceID(),
|
||||
"Teleport to this computer"
|
||||
) )
|
||||
.appendText( " " )
|
||||
.appendSibling( link(
|
||||
text( "\u20e2" ),
|
||||
"/computercraft view " + serverComputer.getInstanceID(),
|
||||
"View this computer"
|
||||
) );
|
||||
}
|
||||
|
||||
table.addRow(
|
||||
computerComponent,
|
||||
formatted( "%4d", entry.getTasks() ),
|
||||
text( String.format( "%7.1f", entry.getTotalTime() / 1e6 ) + "ms" ),
|
||||
text( String.format( "%4.1f", entry.getAverage() / 1e6 ) + "ms" ),
|
||||
text( String.format( "%5.1f", entry.getMaxTime() / 1e6 ) + "ms" )
|
||||
);
|
||||
}
|
||||
|
||||
table.displayTo( context.getSender() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.command.CommandException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class ComputerSelector
|
||||
{
|
||||
private static List<ServerComputer> getComputers( Predicate<ServerComputer> predicate, String selector ) throws CommandException
|
||||
{
|
||||
// We copy it to prevent concurrent modifications.
|
||||
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
List<ServerComputer> candidates = Lists.newArrayList();
|
||||
for( ServerComputer searchComputer : computers )
|
||||
{
|
||||
if( predicate.test( searchComputer ) ) candidates.add( searchComputer );
|
||||
}
|
||||
|
||||
if( candidates.isEmpty() )
|
||||
{
|
||||
throw new CommandException( "No computer matching " + selector );
|
||||
}
|
||||
else
|
||||
{
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
|
||||
public static ServerComputer getComputer( String selector ) throws CommandException
|
||||
{
|
||||
List<ServerComputer> computers = getComputers( selector );
|
||||
if( computers.size() == 1 )
|
||||
{
|
||||
return computers.get( 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder builder = new StringBuilder( "Multiple computers matching " )
|
||||
.append( selector ).append( " (instances " );
|
||||
|
||||
for( int i = 0; i < computers.size(); i++ )
|
||||
{
|
||||
if( i > 1 ) builder.append( ", " );
|
||||
builder.append( computers.get( i ).getInstanceID() );
|
||||
}
|
||||
builder.append( ")" );
|
||||
|
||||
throw new CommandException( builder.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ServerComputer> getComputers( String selector ) throws CommandException
|
||||
{
|
||||
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
|
||||
{
|
||||
selector = selector.substring( 1 );
|
||||
|
||||
int id;
|
||||
try
|
||||
{
|
||||
id = Integer.parseInt( selector );
|
||||
}
|
||||
catch( NumberFormatException e )
|
||||
{
|
||||
throw new CommandException( "'" + selector + "' is not a valid number" );
|
||||
}
|
||||
|
||||
return getComputers( x -> x.getID() == id, selector );
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '@' )
|
||||
{
|
||||
String label = selector.substring( 1 );
|
||||
return getComputers( x -> Objects.equals( label, x.getLabel() ), selector );
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '~' )
|
||||
{
|
||||
String familyName = selector.substring( 1 );
|
||||
return getComputers( x -> x.getFamily().name().equalsIgnoreCase( familyName ), selector );
|
||||
}
|
||||
else
|
||||
{
|
||||
int instance;
|
||||
try
|
||||
{
|
||||
instance = Integer.parseInt( selector );
|
||||
}
|
||||
catch( NumberFormatException e )
|
||||
{
|
||||
throw new CommandException( "'" + selector + "' is not a valid number" );
|
||||
}
|
||||
|
||||
ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instance );
|
||||
if( computer == null )
|
||||
{
|
||||
throw new CommandException( "No such computer for instance id " + instance );
|
||||
}
|
||||
else
|
||||
{
|
||||
return Collections.singletonList( computer );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> completeComputer( String selector )
|
||||
{
|
||||
TreeSet<String> options = Sets.newTreeSet();
|
||||
|
||||
// We copy it to prevent concurrent modifications.
|
||||
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
|
||||
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
|
||||
{
|
||||
selector = selector.substring( 1 );
|
||||
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
String id = Integer.toString( computer.getID() );
|
||||
if( id.startsWith( selector ) ) options.add( "#" + id );
|
||||
}
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '@' )
|
||||
{
|
||||
String label = selector.substring( 1 );
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
String thisLabel = computer.getLabel();
|
||||
if( thisLabel != null && thisLabel.startsWith( label ) ) options.add( "@" + thisLabel );
|
||||
}
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '~' )
|
||||
{
|
||||
String familyName = selector.substring( 1 ).toLowerCase( Locale.ENGLISH );
|
||||
for( ComputerFamily family : ComputerFamily.values() )
|
||||
{
|
||||
if( family.name().toLowerCase( Locale.ENGLISH ).startsWith( familyName ) )
|
||||
{
|
||||
options.add( "~" + family.name() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
String id = Integer.toString( computer.getInstanceID() );
|
||||
if( id.startsWith( selector ) ) options.add( id );
|
||||
}
|
||||
}
|
||||
|
||||
if( options.size() > 100 )
|
||||
{
|
||||
ArrayList<String> result = Lists.newArrayListWithCapacity( 100 );
|
||||
for( String element : options )
|
||||
{
|
||||
if( result.size() > 100 ) break;
|
||||
result.add( element );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Lists.newArrayList( options );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.Container;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ContainerViewComputer extends Container implements IContainerComputer
|
||||
{
|
||||
private final IComputer computer;
|
||||
|
||||
public ContainerViewComputer( IComputer computer )
|
||||
{
|
||||
this.computer = computer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IComputer getComputer()
|
||||
{
|
||||
return computer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInteractWith( @Nonnull EntityPlayer player )
|
||||
{
|
||||
if( computer instanceof ServerComputer )
|
||||
{
|
||||
ServerComputer serverComputer = (ServerComputer) computer;
|
||||
|
||||
// If this computer no longer exists then discard it.
|
||||
if( ComputerCraft.serverComputerRegistry.get( serverComputer.getInstanceID() ) != serverComputer )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're a command computer then ensure we're in creative
|
||||
if( serverComputer.getFamily() == ComputerFamily.Command )
|
||||
{
|
||||
MinecraftServer server = player.getServer();
|
||||
if( server == null || !server.isCommandBlockEnabled() )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
|
||||
return false;
|
||||
}
|
||||
else if( !ComputerCraft.canPlayerUseCommands( player ) || !player.capabilities.isCreativeMode )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.Style;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.event.ClickEvent;
|
||||
import net.minecraft.util.text.event.HoverEvent;
|
||||
|
||||
/**
|
||||
* Various helpers for building chat messages
|
||||
*/
|
||||
public final class ChatHelpers
|
||||
{
|
||||
private static final TextFormatting HEADER = TextFormatting.LIGHT_PURPLE;
|
||||
private static final TextFormatting SYNOPSIS = TextFormatting.AQUA;
|
||||
private static final TextFormatting NAME = TextFormatting.GREEN;
|
||||
|
||||
public static ITextComponent coloured( String text, TextFormatting colour )
|
||||
{
|
||||
ITextComponent component = new TextComponentString( text == null ? "" : text );
|
||||
component.getStyle().setColor( colour );
|
||||
return component;
|
||||
}
|
||||
|
||||
public static ITextComponent text( String text )
|
||||
{
|
||||
return new TextComponentString( text == null ? "" : text );
|
||||
}
|
||||
|
||||
public static ITextComponent list( ITextComponent... children )
|
||||
{
|
||||
ITextComponent component = new TextComponentString( "" );
|
||||
for( ITextComponent child : children )
|
||||
{
|
||||
component.appendSibling( child );
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
public static ITextComponent getHelp( CommandContext context, ISubCommand command, String prefix )
|
||||
{
|
||||
ITextComponent output = new TextComponentString( "" )
|
||||
.appendSibling( coloured( "/" + prefix + " " + command.getUsage( context ), HEADER ) )
|
||||
.appendText( " " )
|
||||
.appendSibling( coloured( command.getSynopsis(), SYNOPSIS ) );
|
||||
|
||||
String desc = command.getDescription();
|
||||
if( !Strings.isNullOrEmpty( desc ) ) output.appendText( "\n" + desc );
|
||||
|
||||
if( command instanceof CommandRoot )
|
||||
{
|
||||
for( ISubCommand subCommand : ((CommandRoot) command).getSubCommands().values() )
|
||||
{
|
||||
if( !subCommand.checkPermission( context ) ) continue;
|
||||
|
||||
output.appendText( "\n" );
|
||||
|
||||
ITextComponent component = coloured( subCommand.getName(), NAME );
|
||||
component.getStyle().setClickEvent( new ClickEvent(
|
||||
ClickEvent.Action.SUGGEST_COMMAND,
|
||||
"/" + prefix + " " + subCommand.getName()
|
||||
) );
|
||||
output.appendSibling( component );
|
||||
|
||||
output.appendText( " - " + subCommand.getSynopsis() );
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static ITextComponent position( BlockPos pos )
|
||||
{
|
||||
if( pos == null ) return text( "<no pos>" );
|
||||
return formatted( "%d, %d, %d", pos.getX(), pos.getY(), pos.getZ() );
|
||||
}
|
||||
|
||||
public static ITextComponent bool( boolean value )
|
||||
{
|
||||
if( value )
|
||||
{
|
||||
ITextComponent component = new TextComponentString( "Y" );
|
||||
component.getStyle().setColor( TextFormatting.GREEN );
|
||||
return component;
|
||||
}
|
||||
else
|
||||
{
|
||||
ITextComponent component = new TextComponentString( "N" );
|
||||
component.getStyle().setColor( TextFormatting.RED );
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
public static ITextComponent formatted( String format, Object... args )
|
||||
{
|
||||
return new TextComponentString( String.format( format, args ) );
|
||||
}
|
||||
|
||||
public static ITextComponent link( ITextComponent component, String command, String toolTip )
|
||||
{
|
||||
Style style = component.getStyle();
|
||||
|
||||
if( style.getColor() == null ) style.setColor( TextFormatting.YELLOW );
|
||||
style.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) );
|
||||
style.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TextComponentString( toolTip ) ) );
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
public static ITextComponent header( String text )
|
||||
{
|
||||
return coloured( text, HEADER );
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the way a command was invoked, including the command sender, the current server and
|
||||
* the "path" to this command.
|
||||
*/
|
||||
public final class CommandContext
|
||||
{
|
||||
private final MinecraftServer server;
|
||||
private final ICommandSender sender;
|
||||
private final List<ISubCommand> path;
|
||||
|
||||
public CommandContext( MinecraftServer server, ICommandSender sender, ISubCommand initial )
|
||||
{
|
||||
this.server = server;
|
||||
this.sender = sender;
|
||||
this.path = Collections.singletonList( initial );
|
||||
}
|
||||
|
||||
private CommandContext( MinecraftServer server, ICommandSender sender, List<ISubCommand> path )
|
||||
{
|
||||
this.server = server;
|
||||
this.sender = sender;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public CommandContext enter( ISubCommand child )
|
||||
{
|
||||
List<ISubCommand> newPath = Lists.newArrayListWithExpectedSize( path.size() + 1 );
|
||||
newPath.addAll( path );
|
||||
newPath.add( child );
|
||||
return new CommandContext( server, sender, newPath );
|
||||
}
|
||||
|
||||
public CommandContext parent()
|
||||
{
|
||||
if( path.size() == 1 ) throw new IllegalStateException( "No parent command" );
|
||||
return new CommandContext( server, sender, path.subList( 0, path.size() - 1 ) );
|
||||
}
|
||||
|
||||
public String getFullPath()
|
||||
{
|
||||
StringBuilder out = new StringBuilder();
|
||||
boolean first = true;
|
||||
for( ISubCommand command : path )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append( ' ' );
|
||||
}
|
||||
|
||||
out.append( command.getName() );
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public String getFullUsage()
|
||||
{
|
||||
return "/" + getFullPath() + " " + path.get( path.size() - 1 ).getUsage( this );
|
||||
}
|
||||
|
||||
public List<ISubCommand> getPath()
|
||||
{
|
||||
return Collections.unmodifiableList( path );
|
||||
}
|
||||
|
||||
public String getRootCommand()
|
||||
{
|
||||
return path.get( 0 ).getName();
|
||||
}
|
||||
|
||||
public MinecraftServer getServer()
|
||||
{
|
||||
return server;
|
||||
}
|
||||
|
||||
public ICommandSender getSender()
|
||||
{
|
||||
return sender;
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link net.minecraft.command.ICommand} which delegates to a {@link ISubCommand}.
|
||||
*/
|
||||
public class CommandDelegate implements ICommand
|
||||
{
|
||||
private final ISubCommand command;
|
||||
|
||||
public CommandDelegate( ISubCommand command )
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return command.getName();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( @Nonnull ICommandSender sender )
|
||||
{
|
||||
return new CommandContext( sender.getServer(), sender, command ).getFullUsage();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getAliases()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args ) throws CommandException
|
||||
{
|
||||
try
|
||||
{
|
||||
command.execute( new CommandContext( server, sender, command ), Arrays.asList( args ) );
|
||||
}
|
||||
catch( CommandException e )
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch( Throwable e )
|
||||
{
|
||||
ComputerCraft.log.error( "Unhandled exception in command", e );
|
||||
throw new CommandException( "Unhandled exception: " + e.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getTabCompletions( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args, @Nullable BlockPos pos )
|
||||
{
|
||||
return command.getCompletion( new CommandContext( server, sender, command ), Arrays.asList( args ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender )
|
||||
{
|
||||
return command.checkPermission( new CommandContext( server, sender, command ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsernameIndex( @Nonnull String[] args, int index )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( @Nonnull ICommand o )
|
||||
{
|
||||
return getName().compareTo( o.getName() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.CommandException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A command which delegates to a series of sub commands
|
||||
*/
|
||||
public class CommandRoot implements ISubCommand
|
||||
{
|
||||
private final String name;
|
||||
private final String synopsis;
|
||||
private final String description;
|
||||
private final Map<String, ISubCommand> subCommands = Maps.newHashMap();
|
||||
|
||||
public CommandRoot( String name, String synopsis, String description )
|
||||
{
|
||||
this.name = name;
|
||||
this.synopsis = synopsis;
|
||||
this.description = description;
|
||||
|
||||
register( new SubCommandHelp( this ) );
|
||||
}
|
||||
|
||||
public void register( ISubCommand command )
|
||||
{
|
||||
subCommands.put( command.getName(), command );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( CommandContext context )
|
||||
{
|
||||
StringBuilder out = new StringBuilder( "<" );
|
||||
boolean first = true;
|
||||
for( ISubCommand command : subCommands.values() )
|
||||
{
|
||||
if( command.checkPermission( context ) )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append( "|" );
|
||||
}
|
||||
|
||||
out.append( command.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
return out.append( ">" ).toString();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSynopsis()
|
||||
{
|
||||
return synopsis;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull CommandContext context )
|
||||
{
|
||||
for( ISubCommand command : subCommands.values() )
|
||||
{
|
||||
if( !(command instanceof SubCommandHelp) && command.checkPermission( context ) ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Map<String, ISubCommand> getSubCommands()
|
||||
{
|
||||
return Collections.unmodifiableMap( subCommands );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
context.getSender().sendMessage( ChatHelpers.getHelp( context, this, context.getFullPath() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ISubCommand command = subCommands.get( arguments.get( 0 ) );
|
||||
if( command == null || !command.checkPermission( context ) )
|
||||
{
|
||||
throw new CommandException( context.getFullUsage() );
|
||||
}
|
||||
|
||||
command.execute( context.enter( command ), arguments.subList( 1, arguments.size() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
return Lists.newArrayList( subCommands.keySet() );
|
||||
}
|
||||
else if( arguments.size() == 1 )
|
||||
{
|
||||
List<String> list = Lists.newArrayList();
|
||||
String match = arguments.get( 0 );
|
||||
|
||||
for( ISubCommand command : subCommands.values() )
|
||||
{
|
||||
if( CommandBase.doesStringStartWith( match, command.getName() ) && command.checkPermission( context ) )
|
||||
{
|
||||
list.add( command.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
else
|
||||
{
|
||||
ISubCommand command = subCommands.get( arguments.get( 0 ) );
|
||||
if( command == null || !command.checkPermission( context ) ) return Collections.emptyList();
|
||||
|
||||
return command.getCompletion( context, arguments.subList( 1, arguments.size() ) );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A slightly different implementation of {@link ICommand} which is delegated to.
|
||||
*/
|
||||
public interface ISubCommand
|
||||
{
|
||||
/**
|
||||
* Get the name of this command
|
||||
*
|
||||
* @return The name of this command
|
||||
* @see ICommand#getName()
|
||||
*/
|
||||
@Nonnull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Get the usage of this command
|
||||
*
|
||||
* @param context The context this command is executed in
|
||||
* @return The usage of this command
|
||||
* @see ICommand#getUsage(ICommandSender)
|
||||
*/
|
||||
@Nonnull
|
||||
String getUsage( CommandContext context );
|
||||
|
||||
/**
|
||||
* Get a short description of this command, including its usage.
|
||||
*
|
||||
* @return The command's synopsis
|
||||
*/
|
||||
@Nonnull
|
||||
String getSynopsis();
|
||||
|
||||
/**
|
||||
* Get the lengthy description of this command. This synopsis is prepended to this.
|
||||
*
|
||||
* @return The command's description
|
||||
*/
|
||||
@Nonnull
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Determine whether a given command sender has permission to execute this command.
|
||||
*
|
||||
* @param context The current command context.
|
||||
* @return Whether this command can be executed.
|
||||
* @see ICommand#checkPermission(MinecraftServer, ICommandSender)
|
||||
*/
|
||||
boolean checkPermission( @Nonnull CommandContext context );
|
||||
|
||||
/**
|
||||
* Execute this command
|
||||
*
|
||||
* @param context The current command context.
|
||||
* @param arguments The arguments passed @throws CommandException When an error occurs
|
||||
* @see ICommand#execute(MinecraftServer, ICommandSender, String[])
|
||||
*/
|
||||
void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException;
|
||||
|
||||
/**
|
||||
* Get a list of possible completions
|
||||
*
|
||||
* @param context The current command context.
|
||||
* @param arguments The arguments passed. You should complete the last one.
|
||||
* @return List of possible completions
|
||||
* @see ICommand#getTabCompletions(MinecraftServer, ICommandSender, String[], BlockPos)
|
||||
*/
|
||||
@Nonnull
|
||||
List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments );
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SubCommandBase implements ISubCommand
|
||||
{
|
||||
private final String name;
|
||||
private final String usage;
|
||||
private final String synopsis;
|
||||
private final String description;
|
||||
private final UserLevel level;
|
||||
|
||||
public SubCommandBase( String name, String usage, String synopsis, UserLevel level, String description )
|
||||
{
|
||||
this.name = name;
|
||||
this.usage = usage;
|
||||
this.synopsis = synopsis;
|
||||
this.description = description;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public SubCommandBase( String name, String synopsis, UserLevel level, String description )
|
||||
{
|
||||
this( name, "", synopsis, level, description );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( CommandContext context )
|
||||
{
|
||||
return usage;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSynopsis()
|
||||
{
|
||||
return synopsis;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull CommandContext context )
|
||||
{
|
||||
return level.canExecute( context );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import joptsimple.internal.Strings;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.CommandException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SubCommandHelp implements ISubCommand
|
||||
{
|
||||
private final CommandRoot branchCommand;
|
||||
|
||||
public SubCommandHelp( CommandRoot branchCommand )
|
||||
{
|
||||
this.branchCommand = branchCommand;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( CommandContext context )
|
||||
{
|
||||
return "[command]";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSynopsis()
|
||||
{
|
||||
return "Provide help for a specific command";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull CommandContext context )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
ISubCommand command = branchCommand;
|
||||
|
||||
for( int i = 0; i < arguments.size(); i++ )
|
||||
{
|
||||
String commandName = arguments.get( i );
|
||||
if( command instanceof CommandRoot )
|
||||
{
|
||||
command = ((CommandRoot) command).getSubCommands().get( commandName );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CommandException( Strings.join( arguments.subList( 0, i ), " " ) + " has no sub-commands" );
|
||||
}
|
||||
|
||||
if( command == null )
|
||||
{
|
||||
throw new CommandException( "No such command " + Strings.join( arguments.subList( 0, i + 1 ), " " ) );
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder prefix = new StringBuilder( context.parent().getFullPath() );
|
||||
for( String argument : arguments )
|
||||
{
|
||||
prefix.append( ' ' ).append( argument );
|
||||
}
|
||||
context.getSender().sendMessage( ChatHelpers.getHelp( context, command, prefix.toString() ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
CommandRoot command = branchCommand;
|
||||
|
||||
for( int i = 0; i < arguments.size() - 1; i++ )
|
||||
{
|
||||
String commandName = arguments.get( i );
|
||||
ISubCommand subCommand = command.getSubCommands().get( commandName );
|
||||
|
||||
if( subCommand instanceof CommandRoot )
|
||||
{
|
||||
command = (CommandRoot) subCommand;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
return Lists.newArrayList( command.getSubCommands().keySet() );
|
||||
}
|
||||
else
|
||||
{
|
||||
List<String> list = Lists.newArrayList();
|
||||
String match = arguments.get( arguments.size() - 1 );
|
||||
|
||||
for( String entry : command.getSubCommands().keySet() )
|
||||
{
|
||||
if( CommandBase.doesStringStartWith( match, entry ) )
|
||||
{
|
||||
list.add( entry );
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,261 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.text;
|
||||
|
||||
public class TextTable
|
||||
{
|
||||
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
private static final int[] CHAR_WIDTHS = new int[]{
|
||||
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
|
||||
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
|
||||
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
|
||||
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
|
||||
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
|
||||
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
|
||||
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
|
||||
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
|
||||
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
|
||||
};
|
||||
|
||||
private static final ITextComponent SEPARATOR = coloured( " | ", TextFormatting.GRAY );
|
||||
private static final ITextComponent LINE = text( "\n" );
|
||||
|
||||
private static int getWidth( char character, ICommandSender sender )
|
||||
{
|
||||
if( sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer) )
|
||||
{
|
||||
// Use font widths here.
|
||||
if( character == 167 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if( character == 32 )
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
else if( CHARACTERS.indexOf( character ) != -1 )
|
||||
{
|
||||
return CHAR_WIDTHS[character];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Eh, close enough.
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getWidth( ITextComponent text, ICommandSender sender )
|
||||
{
|
||||
int sum = 0;
|
||||
String chars = text.getUnformattedText();
|
||||
for( int i = 0; i < chars.length(); i++ )
|
||||
{
|
||||
sum += getWidth( chars.charAt( i ), sender );
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
private static boolean isPlayer( ICommandSender sender )
|
||||
{
|
||||
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
|
||||
}
|
||||
|
||||
private static int getMaxWidth( ICommandSender sender )
|
||||
{
|
||||
return isPlayer( sender ) ? 320 : 80;
|
||||
}
|
||||
|
||||
private int columns = -1;
|
||||
private final ITextComponent[] header;
|
||||
private final List<ITextComponent[]> rows = Lists.newArrayList();
|
||||
|
||||
public TextTable( @Nonnull ITextComponent... header )
|
||||
{
|
||||
this.header = header;
|
||||
this.columns = header.length;
|
||||
}
|
||||
|
||||
public TextTable()
|
||||
{
|
||||
header = null;
|
||||
}
|
||||
|
||||
public TextTable( @Nonnull String... header )
|
||||
{
|
||||
this.header = new ITextComponent[header.length];
|
||||
for( int i = 0; i < header.length; i++ )
|
||||
{
|
||||
this.header[i] = ChatHelpers.header( header[i] );
|
||||
}
|
||||
this.columns = header.length;
|
||||
}
|
||||
|
||||
public void addRow( @Nonnull ITextComponent... row )
|
||||
{
|
||||
if( columns == -1 )
|
||||
{
|
||||
columns = row.length;
|
||||
}
|
||||
else if( row.length != columns )
|
||||
{
|
||||
throw new IllegalArgumentException( "Row is the incorrect length" );
|
||||
}
|
||||
|
||||
rows.add( row );
|
||||
}
|
||||
|
||||
public void displayTo( ICommandSender sender )
|
||||
{
|
||||
if( columns <= 0 ) return;
|
||||
|
||||
final int maxWidth = getMaxWidth( sender );
|
||||
|
||||
int[] minWidths = new int[columns];
|
||||
int[] maxWidths = new int[columns];
|
||||
int[] rowWidths = new int[columns];
|
||||
|
||||
if( header != null )
|
||||
{
|
||||
for( int i = 0; i < columns; i++ )
|
||||
{
|
||||
maxWidths[i] = minWidths[i] = getWidth( header[i], sender );
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the number of rows to something sensible.
|
||||
int limit = isPlayer( sender ) ? 30 : 100;
|
||||
if( limit > rows.size() ) limit = rows.size();
|
||||
|
||||
for( int y = 0; y < limit; y++ )
|
||||
{
|
||||
ITextComponent[] row = rows.get( y );
|
||||
for( int i = 0; i < row.length; i++ )
|
||||
{
|
||||
int width = getWidth( row[i], sender );
|
||||
rowWidths[i] += width;
|
||||
if( width > maxWidths[i] )
|
||||
{
|
||||
maxWidths[i] = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the average width
|
||||
for( int i = 0; i < columns; i++ )
|
||||
{
|
||||
rowWidths[i] = Math.max( rowWidths[i], rows.size() );
|
||||
}
|
||||
|
||||
int totalWidth = (columns - 1) * getWidth( SEPARATOR, sender );
|
||||
for( int x : maxWidths ) totalWidth += x;
|
||||
|
||||
// TODO: Limit the widths of some entries if totalWidth > maxWidth
|
||||
|
||||
ITextComponent out = new TextComponentString( "" );
|
||||
|
||||
if( header != null )
|
||||
{
|
||||
for( int i = 0; i < columns; i++ )
|
||||
{
|
||||
if( i != 0 ) out.appendSibling( SEPARATOR );
|
||||
appendFixed( out, sender, header[i], maxWidths[i] );
|
||||
}
|
||||
out.appendSibling( LINE );
|
||||
|
||||
// Round the width up rather than down
|
||||
int rowCharWidth = getWidth( '=', sender );
|
||||
int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1);
|
||||
out.appendSibling( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
|
||||
out.appendSibling( LINE );
|
||||
}
|
||||
|
||||
for( int i = 0; i < limit; i++ )
|
||||
{
|
||||
ITextComponent[] row = rows.get( i );
|
||||
if( i != 0 ) out.appendSibling( LINE );
|
||||
for( int j = 0; j < columns; j++ )
|
||||
{
|
||||
if( j != 0 ) out.appendSibling( SEPARATOR );
|
||||
appendFixed( out, sender, row[j], maxWidths[j] );
|
||||
}
|
||||
}
|
||||
|
||||
if( limit != rows.size() )
|
||||
{
|
||||
out.appendSibling( LINE );
|
||||
out.appendSibling( coloured( (rows.size() - limit) + " additional rows...", TextFormatting.AQUA ) );
|
||||
}
|
||||
|
||||
sender.sendMessage( out );
|
||||
}
|
||||
|
||||
private static void appendFixed( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
|
||||
{
|
||||
int length = getWidth( entry, sender );
|
||||
int delta = length - maxWidth;
|
||||
if( delta < 0 )
|
||||
{
|
||||
// Convert to overflow;
|
||||
delta = -delta;
|
||||
|
||||
// We have to remove some padding as there is a padding added between formatted and unformatted text
|
||||
if( !entry.getStyle().isEmpty() && isPlayer( sender ) ) delta -= 1;
|
||||
|
||||
out.appendSibling( entry );
|
||||
|
||||
int spaceWidth = getWidth( ' ', sender );
|
||||
|
||||
int spaces = delta / spaceWidth;
|
||||
int missing = delta % spaceWidth;
|
||||
spaces -= missing;
|
||||
|
||||
ITextComponent component = new TextComponentString( StringUtils.repeat( ' ', spaces < 0 ? 0 : spaces ) );
|
||||
if( missing > 0 )
|
||||
{
|
||||
ITextComponent bold = new TextComponentString( StringUtils.repeat( ' ', missing ) );
|
||||
bold.getStyle().setBold( true );
|
||||
component.appendSibling( bold );
|
||||
}
|
||||
|
||||
out.appendSibling( component );
|
||||
}
|
||||
else if( delta > 0 )
|
||||
{
|
||||
out.appendSibling( entry );
|
||||
}
|
||||
else
|
||||
{
|
||||
out.appendSibling( entry );
|
||||
|
||||
// We have to add some padding as we expect a padding between formatted and unformatted text
|
||||
// and there won't be.
|
||||
if( entry.getStyle().isEmpty() && isPlayer( sender ) ) out.appendText( " " );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
/**
|
||||
* The level a user must be at in order to execute a command.
|
||||
*/
|
||||
public enum UserLevel
|
||||
{
|
||||
/**
|
||||
* Only can be used by the owner of the server: namely the server console or the player in SSP.
|
||||
*/
|
||||
OWNER,
|
||||
|
||||
/**
|
||||
* Can only be used by ops.
|
||||
*/
|
||||
OP,
|
||||
|
||||
/**
|
||||
* Can be used by any op, or the player in SSP.
|
||||
*/
|
||||
OWNER_OP,
|
||||
|
||||
/**
|
||||
* Can be used by anyone.
|
||||
*/
|
||||
ANYONE;
|
||||
|
||||
public int toLevel()
|
||||
{
|
||||
switch( this )
|
||||
{
|
||||
case OWNER:
|
||||
return 4;
|
||||
case OP:
|
||||
case OWNER_OP:
|
||||
return 2;
|
||||
case ANYONE:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canExecute( CommandContext context )
|
||||
{
|
||||
if( this == ANYONE ) return true;
|
||||
|
||||
// We *always* allow level 0 stuff, even if the
|
||||
MinecraftServer server = context.getServer();
|
||||
ICommandSender sender = context.getSender();
|
||||
|
||||
if( server.isSinglePlayer() && sender instanceof EntityPlayerMP &&
|
||||
((EntityPlayerMP) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerOwner() ) )
|
||||
{
|
||||
if( this == OWNER || this == OWNER_OP ) return true;
|
||||
}
|
||||
|
||||
return sender.canUseCommand( toLevel(), context.getRootCommand() );
|
||||
}
|
||||
}
|
@@ -68,7 +68,7 @@ public abstract class BlockGeneric extends Block implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer player, boolean willHarvest )
|
||||
public boolean removedByPlayer( @Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer player, boolean willHarvest )
|
||||
{
|
||||
if( !world.isRemote )
|
||||
{
|
||||
@@ -111,18 +111,18 @@ public abstract class BlockGeneric extends Block implements
|
||||
public final void breakBlock( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState newState )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
super.breakBlock( world, pos, newState );
|
||||
world.removeTileEntity( pos );
|
||||
if( tile != null && tile instanceof TileGeneric )
|
||||
{
|
||||
TileGeneric generic = (TileGeneric)tile;
|
||||
generic.destroy();
|
||||
}
|
||||
super.breakBlock( world, pos, newState );
|
||||
world.removeTileEntity( pos );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public final ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult target, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player )
|
||||
public ItemStack getPickBlock( @Nonnull IBlockState state, RayTraceResult target, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
if( tile != null && tile instanceof TileGeneric )
|
||||
@@ -241,10 +241,9 @@ public abstract class BlockGeneric extends Block implements
|
||||
if( collision.size() > 0 )
|
||||
{
|
||||
AxisAlignedBB aabb = collision.get( 0 );
|
||||
for (int i=1; i<collision.size(); ++i )
|
||||
for( int i = 1; i < collision.size(); i++ )
|
||||
{
|
||||
AxisAlignedBB other = collision.get( 1 );
|
||||
aabb = aabb.union( other );
|
||||
aabb = aabb.union( collision.get( i ) );
|
||||
}
|
||||
return aabb;
|
||||
}
|
||||
@@ -337,22 +336,6 @@ public abstract class BlockGeneric extends Block implements
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean eventReceived( IBlockState state, World world, BlockPos pos, int eventID, int eventParameter )
|
||||
{
|
||||
if( world.isRemote )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
if( tile != null && tile instanceof TileGeneric )
|
||||
{
|
||||
TileGeneric generic = (TileGeneric)tile;
|
||||
generic.onBlockEvent( eventID, eventParameter );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public final TileEntity createTileEntity( @Nonnull World world, @Nonnull IBlockState state )
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user