mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-05 15:00:29 +00:00
Merge branch 'master' into mc-1.14.x
This also deletes display list support - MC 1.14 now requires VBOs to be supported in some capacity.
This commit is contained in:
commit
d847a4d9e0
@ -14,5 +14,9 @@ trim_trailing_whitespace = false
|
||||
[*.sexp]
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
[*.properties]
|
||||
insert_final_newline = false
|
||||
|
13
.github/workflows/main-ci.yml
vendored
13
.github/workflows/main-ci.yml
vendored
@ -15,6 +15,14 @@ jobs:
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon || ./gradlew build --no-daemon
|
||||
|
||||
@ -34,6 +42,9 @@ jobs:
|
||||
- name: Lint Lua code
|
||||
run: |
|
||||
test -d bin || mkdir bin
|
||||
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/bin/illuaminate
|
||||
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||
chmod +x bin/illuaminate
|
||||
bin/illuaminate lint
|
||||
|
||||
- name: Check whitespace
|
||||
run: python3 tools/check-lines.py
|
||||
|
16
.github/workflows/make-doc.sh
vendored
Executable file
16
.github/workflows/make-doc.sh
vendored
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
DEST="${GITHUB_REF#refs/*/}"
|
||||
echo "Uploading docs to https://tweaked.cc/$DEST"
|
||||
|
||||
# Setup ssh key
|
||||
mkdir -p "$HOME/.ssh/"
|
||||
echo "$SSH_KEY" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
|
||||
# And upload
|
||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
|
||||
"$GITHUB_WORKSPACE/doc/" \
|
||||
"$SSH_USER@$SSH_HOST:/var/www/tweaked.cc/$DEST"
|
29
.github/workflows/make-doc.yml
vendored
Normal file
29
.github/workflows/make-doc.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Build documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
tags:
|
||||
|
||||
jobs:
|
||||
make_doc:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
test -d bin || mkdir bin
|
||||
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||
chmod +x bin/illuaminate
|
||||
bin/illuaminate doc-gen
|
||||
|
||||
- name: Upload documentation
|
||||
run: .github/workflows/make-doc.sh 2> /dev/null
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.SSH_KEY }}
|
||||
SSH_USER: ${{ secrets.SSH_USER }}
|
||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||
SSH_PORT: ${{ secrets.SSH_PORT }}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,6 +3,8 @@
|
||||
/logs
|
||||
/build
|
||||
/out
|
||||
/doc/**/*.html
|
||||
/doc/index.json
|
||||
|
||||
# Runtime directories
|
||||
/run
|
||||
|
36
CONTRIBUTING.md
Normal file
36
CONTRIBUTING.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Contributing to CC: Tweaked
|
||||
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
|
||||
provides an introduction as to how to get started in helping out.
|
||||
|
||||
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
|
||||
|
||||
## Reporting issues
|
||||
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so,
|
||||
do use the issue templates - they provide a useful hint on what information to provide.
|
||||
|
||||
## Developing
|
||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it. This is a pretty simple
|
||||
process.
|
||||
|
||||
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||
- **Setup Forge:** `./gradlew build`
|
||||
- **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
|
||||
|
||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
|
||||
These commands may take a few minutes to run the first time, as the environment is set up, but should be much faster
|
||||
afterwards.
|
||||
|
||||
### Code linters
|
||||
CC: Tweaked uses a couple of "linters" on its source code, to enforce a consistent style across the project. While these
|
||||
are run whenever you submit a PR, it's often useful to run this before committing.
|
||||
|
||||
- **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or
|
||||
`./gradle check`.
|
||||
- **[illuaminate]:** Checks Lua code for semantic and styleistic issues. See [the usage section][illuaminate-usage] for
|
||||
how to download and run it.
|
||||
|
||||
[new-issue]: https://github.com/SquidDev-CC/CC-Tweaked/issues/new/choose "Create a new issue"
|
||||
[community]: README.md#Community "Get in touch with the community."
|
||||
[checkstyle]: https://checkstyle.org/
|
||||
[illuaminate]: https://github.com/SquidDev/illuaminate/
|
||||
[illuaminate-usage]: https://github.com/SquidDev/illuaminate/blob/master/README.md#usage
|
16
README.md
16
README.md
@ -37,20 +37,14 @@ several features have been included, such as full block modems, the Cobalt runti
|
||||
computers.
|
||||
|
||||
## Contributing
|
||||
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. In order to start helping
|
||||
develop CC:T, you'll need to follow these steps:
|
||||
|
||||
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||
- **Setup Forge:** `./gradlew build`
|
||||
- **Test your changes:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
|
||||
|
||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
|
||||
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you want to get started
|
||||
developing the mod, [check out the instructions here](CONTRIBUTING.md#developing).
|
||||
|
||||
## Community
|
||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
||||
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.gg/H2UyJXe)!
|
||||
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=#computercraft), if
|
||||
that's more your cup of tea.
|
||||
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.computercraft.cc)!
|
||||
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=computercraft), if that's
|
||||
more your cup of tea.
|
||||
|
||||
I'd generally recommend you don't contact me directly (email, DM, etc...) unless absolutely necessary (i.e. in order to
|
||||
report exploits). You'll get a far quicker response if you ask the whole community!
|
||||
|
@ -104,7 +104,7 @@ dependencies {
|
||||
|
||||
runtimeOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.27")
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'
|
||||
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
|
||||
|
11
doc/index.md
Normal file
11
doc/index.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ![CC: Tweaked](logo.png) [![Download CC: Tweaked on CurseForge](http://cf.way2muchnoise.eu/title/cc-tweaked.svg)](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
|
||||
|
||||
CC: Tweaked is a fork of [ComputerCraft], adding programmable computers, turtles and more to Minecraft.
|
||||
|
||||
This website contains documentation for all Lua libraries and APIs from the latest version of CC: Tweaked. This
|
||||
documentation is still in development, so will most likely be incomplete. If you've found something you think is wrong,
|
||||
or would like to help out [please get in touch on GitHub][gh].
|
||||
|
||||
[bug]: https://github.com/SquidDev-CC/CC-Tweaked/issues/new/choose
|
||||
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
|
||||
[gh]: https://github.com/SquidDev-CC/CC-Tweaked "CC:Tweaked on GitHub"
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
6
doc/stub/commands.lua
Normal file
6
doc/stub/commands.lua
Normal file
@ -0,0 +1,6 @@
|
||||
function exec(command) end
|
||||
function execAsync(commad) end
|
||||
function list() end
|
||||
function getBlockPosition() end
|
||||
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
||||
function getBlockInfo(x, y, z) end
|
42
doc/stub/fs.lua
Normal file
42
doc/stub/fs.lua
Normal file
@ -0,0 +1,42 @@
|
||||
--- The FS API allows you to manipulate files and the filesystem.
|
||||
--
|
||||
-- @module fs
|
||||
|
||||
function list(path) end
|
||||
function combine(base, child) end
|
||||
function getName(path) end
|
||||
function getSize(path) end
|
||||
function exists(path) end
|
||||
function isDir(path) end
|
||||
function isReadOnly(path) end
|
||||
function makeDir(path) end
|
||||
function move(from, to) end
|
||||
function copy(from, to) end
|
||||
function delete(path) end
|
||||
function open(path, mode) end
|
||||
function getDrive(path) end
|
||||
function getFreeSpace(path) end
|
||||
function find(pattern) end
|
||||
function getDir(path) end
|
||||
|
||||
--- A file handle which can be read from.
|
||||
--
|
||||
-- @type ReadHandle
|
||||
-- @see fs.open
|
||||
local ReadHandle = {}
|
||||
function ReadHandle.read(count) end
|
||||
function ReadHandle.readAll() end
|
||||
function ReadHandle.readLine(with_trailing) end
|
||||
function ReadHandle.seek(whence, offset) end
|
||||
function ReadHandle.close() end
|
||||
|
||||
--- A file handle which can be written to.
|
||||
--
|
||||
-- @type WriteHandle
|
||||
-- @see fs.open
|
||||
local WriteHandle = {}
|
||||
function WriteHandle.write(text) end
|
||||
function WriteHandle.writeLine(text) end
|
||||
function WriteHandle.flush(text) end
|
||||
function WriteHandle.seek(whence, offset) end
|
||||
function WriteHandle.close() end
|
229
doc/stub/http.lua
Normal file
229
doc/stub/http.lua
Normal file
@ -0,0 +1,229 @@
|
||||
--- The http library allows communicating with web servers, sending and
|
||||
-- receiving data from them.
|
||||
--
|
||||
-- #### `http_check` event
|
||||
--
|
||||
-- @module http
|
||||
|
||||
--- Asynchronously make a HTTP request to the given url.
|
||||
--
|
||||
-- This returns immediately, a [`http_success`](#http-success-event) or
|
||||
-- [`http_failure`](#http-failure-event) will be queued once the request has
|
||||
-- completed.
|
||||
--
|
||||
-- @tparam string url The url to request
|
||||
-- @tparam[opt] string body An optional string containing the body of the
|
||||
-- request. If specified, a `POST` request will be made instead.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of this request.
|
||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
|
||||
-- the body will not be UTF-8 encoded, and the received response will not be
|
||||
-- decoded.
|
||||
--
|
||||
-- @tparam[2] {
|
||||
-- url = string, body? = string, headers? = { [string] = string },
|
||||
-- binary? = boolean, method? = string, redirect? = boolean,
|
||||
-- } request Options for the request.
|
||||
--
|
||||
-- This table form is an expanded version of the previous syntax. All arguments
|
||||
-- from above are passed in as fields instead (for instance,
|
||||
-- `http.request("https://example.com")` becomes `http.request { url =
|
||||
-- "https://example.com" }`).
|
||||
--
|
||||
-- This table also accepts several additional options:
|
||||
--
|
||||
-- - `method`: Which HTTP method to use, for instance `"PATCH"` or `"DELETE"`.
|
||||
-- - `redirect`: Whether to follow HTTP redirects. Defaults to true.
|
||||
--
|
||||
-- @see http.get For a synchronous way to make GET requests.
|
||||
-- @see http.post For a synchronous way to make POST requests.
|
||||
function request(...) end
|
||||
|
||||
--- Make a HTTP GET request to the given url.
|
||||
--
|
||||
-- @tparam string url The url to request
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of this request.
|
||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
|
||||
-- the body will not be UTF-8 encoded, and the received response will not be
|
||||
-- decoded.
|
||||
--
|
||||
-- @tparam[2] {
|
||||
-- url = string, headers? = { [string] = string },
|
||||
-- binary? = boolean, method? = string, redirect? = boolean,
|
||||
-- } request Options for the request. See @{http.request} for details on how
|
||||
-- these options behave.
|
||||
--
|
||||
-- @treturn Response The resulting http response, which can be read from.
|
||||
-- @treturn[2] nil When the http request failed, such as in the event of a 404
|
||||
-- error or connection timeout.
|
||||
-- @treturn string A message detailing why the request failed.
|
||||
-- @treturn Response|nil The failing http response, if available.
|
||||
--
|
||||
-- @usage Make a request to [example.computercraft.cc](https://example.computercraft.cc),
|
||||
-- and print the returned page.
|
||||
-- ```lua
|
||||
-- local request = http.get("https://example.computercraft.cc")
|
||||
-- print(request.readAll())
|
||||
-- -- => HTTP is working!
|
||||
-- request.close()
|
||||
-- ```
|
||||
function get(...) end
|
||||
|
||||
--- Make a HTTP POST request to the given url.
|
||||
--
|
||||
-- @tparam string url The url to request
|
||||
-- @tparam string body The body of the POST request.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of this request.
|
||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
|
||||
-- the body will not be UTF-8 encoded, and the received response will not be
|
||||
-- decoded.
|
||||
--
|
||||
-- @tparam[2] {
|
||||
-- url = string, body? = string, headers? = { [string] = string },
|
||||
-- binary? = boolean, method? = string, redirect? = boolean,
|
||||
-- } request Options for the request. See @{http.request} for details on how
|
||||
-- these options behave.
|
||||
--
|
||||
-- @treturn Response The resulting http response, which can be read from.
|
||||
-- @treturn[2] nil When the http request failed, such as in the event of a 404
|
||||
-- error or connection timeout.
|
||||
-- @treturn string A message detailing why the request failed.
|
||||
-- @treturn Response|nil The failing http response, if available.
|
||||
function post(...) end
|
||||
|
||||
--- A http response. This acts very much like a @{fs.ReadHandle|file}, though
|
||||
-- provides some http specific methods.
|
||||
--
|
||||
-- #### `http_success` event
|
||||
-- #### `http_failure` event
|
||||
--
|
||||
-- @type Response
|
||||
-- @see http.request On how to make a http request.
|
||||
local Response = {}
|
||||
|
||||
--- Returns the response code and response message returned by the server
|
||||
--
|
||||
-- @treturn number The response code (i.e. 200)
|
||||
-- @treturn string The response message (i.e. "OK")
|
||||
function Response.getResponseCode() end
|
||||
|
||||
--- Get a table containing the response's headers, in a format similar to that
|
||||
-- required by @{http.request}. If multiple headers are sent with the same
|
||||
-- name, they will be combined with a comma.
|
||||
--
|
||||
-- @treturn { [string]=string } The response's headers.
|
||||
-- Make a request to [example.computercraft.cc](https://example.computercraft.cc),
|
||||
-- and print the returned headers.
|
||||
-- ```lua
|
||||
-- local request = http.get("https://example.computercraft.cc")
|
||||
-- print(textutils.serialize(request.getResponseHeaders()))
|
||||
-- -- => {
|
||||
-- -- [ "Content-Type" ] = "text/plain; charset=utf8",
|
||||
-- -- [ "content-length" ] = 17,
|
||||
-- -- ...
|
||||
-- -- }
|
||||
-- request.close()
|
||||
-- ```
|
||||
function Response.getResponseHeaders() end
|
||||
|
||||
function Response.read(count) end
|
||||
function Response.readAll() end
|
||||
function Response.readLine(with_trailing) end
|
||||
function Response.seek(whence, offset) end
|
||||
function Response.close() end
|
||||
|
||||
--- Asynchronously determine whether a URL can be requested.
|
||||
--
|
||||
-- If this returns `true`, one should also listen for [`http_check`
|
||||
-- events](#http-check-event) which will container further information about
|
||||
-- whether the URL is allowed or not.
|
||||
--
|
||||
-- @tparam string url The URL to check.
|
||||
-- @treturn true When this url is not invalid. This does not imply that it is
|
||||
-- allowed - see the comment above.
|
||||
-- @treturn[2] false When this url is invalid.
|
||||
-- @treturn string A reason why this URL is not valid (for instance, if it is
|
||||
-- malformed, or blocked).
|
||||
--
|
||||
-- @see http.checkURL For a synchronous version.
|
||||
function checkURLAsync(url) end
|
||||
|
||||
--- Determine whether a URL can be requested.
|
||||
--
|
||||
-- If this returns `true`, one should also listen for [`http_check`
|
||||
-- events](#http-check-event) which will container further information about
|
||||
-- whether the URL is allowed or not.
|
||||
--
|
||||
-- @tparam string url The URL to check.
|
||||
-- @treturn true When this url is valid and can be requested via @{http.request}.
|
||||
-- @treturn[2] false When this url is invalid.
|
||||
-- @treturn string A reason why this URL is not valid (for instance, if it is
|
||||
-- malformed, or blocked).
|
||||
--
|
||||
-- @see http.checkURLAsync For an asynchronous version.
|
||||
--
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- print(http.checkURL("https://example.computercraft.cc/"))
|
||||
-- -- => true
|
||||
-- print(http.checkURL("http://localhost/"))
|
||||
-- -- => false Domain not permitted
|
||||
-- print(http.checkURL("not a url"))
|
||||
-- -- => false URL malformed
|
||||
-- ```
|
||||
function checkURL(url) end
|
||||
|
||||
--- Open a websocket.
|
||||
--
|
||||
-- @tparam string url The websocket url to connect to. This should have the
|
||||
-- `ws://` or `wss://` protocol.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of the initial websocket connection.
|
||||
--
|
||||
-- @treturn Websocket The websocket connection.
|
||||
-- @treturn[2] false If the websocket connection failed.
|
||||
-- @treturn string An error message describing why the connection failed.
|
||||
function websocket(url, headers) end
|
||||
|
||||
--- Asynchronously open a websocket.
|
||||
--
|
||||
-- This returns immediately, a [`websocket_success`](#websocket-success-event)
|
||||
-- or [`websocket_failure`](#websocket-failure-event) will be queued once the
|
||||
-- request has completed.
|
||||
--
|
||||
-- @tparam string url The websocket url to connect to. This should have the
|
||||
-- `ws://` or `wss://` protocol.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of the initial websocket connection.
|
||||
function websocketAsync(url, headers) end
|
||||
|
||||
--- A websocket, which can be used to send an receive messages with a web
|
||||
-- server.
|
||||
--
|
||||
-- @type Websocket
|
||||
-- @see http.websocket On how to open a websocket.
|
||||
local Websocket = {}
|
||||
|
||||
--- Send a websocket message to the connected server.
|
||||
--
|
||||
-- @tparam string message The message to send.
|
||||
-- @tparam[opt] boolean binary Whether this message should be treated as a
|
||||
-- binary string, rather than encoded text.
|
||||
-- @throws If the websocket has been closed.
|
||||
function Websocket.send(message, binary) end
|
||||
|
||||
--- Wait for a message from the server.
|
||||
--
|
||||
-- @tparam[opt] number timeout The number of seconds to wait if no message is
|
||||
-- received.
|
||||
-- @treturn[1] string The received message.
|
||||
-- @treturn boolean If this was a binary message.
|
||||
-- @treturn[2] nil If the websocket was closed while waiting, or if we timed out.
|
||||
-- @throws If the websocket has been closed.
|
||||
function Websocket.receive(timeout) end
|
||||
|
||||
--- Close this websocket. This will terminate the connection, meaning messages
|
||||
-- can no longer be sent or received along it.
|
||||
function Websocket.close() end
|
17
doc/stub/os.lua
Normal file
17
doc/stub/os.lua
Normal file
@ -0,0 +1,17 @@
|
||||
function queueEvent(event, ...) end
|
||||
function startTimer(delay) end
|
||||
function setAlarm(time) end
|
||||
function shutdown() end
|
||||
function reboot() end
|
||||
function getComputerID() end
|
||||
computerID = getComputerID
|
||||
function setComputerLabel(label) end
|
||||
function getComputerLabel() end
|
||||
computerLabel = getComputerLabel
|
||||
function clock() end
|
||||
function time(timezone) end
|
||||
function day(timezone) end
|
||||
function cancelTimer(id) end
|
||||
function cancelAlarm(id) end
|
||||
function epoch(timezone) end
|
||||
function date(format, time) end
|
14
doc/stub/redstone.lua
Normal file
14
doc/stub/redstone.lua
Normal file
@ -0,0 +1,14 @@
|
||||
function getSides() end
|
||||
function setOutput(side, on) end
|
||||
function getOutput(side) end
|
||||
function getInput(side) end
|
||||
function setBundledOutput(side, output) end
|
||||
function getBundledOutput(side) end
|
||||
function getBundledInput(side) end
|
||||
function testBundledInput(side, mask) end
|
||||
function setAnalogOutput(side, value) end
|
||||
setAnalogueOutput = setAnalogOutput
|
||||
function getAnalogOutput(sid) end
|
||||
getAnalogueOutput = getAnalogOutput
|
||||
function getAnalogInput(side) end
|
||||
getAnalogueInput = getAnaloguInput
|
52
doc/stub/term.lua
Normal file
52
doc/stub/term.lua
Normal file
@ -0,0 +1,52 @@
|
||||
function write(text) end
|
||||
function scroll(lines) end
|
||||
function setCursorPos(x, y) end
|
||||
function setCursorBlink(blink) end
|
||||
function getCursorPos() end
|
||||
function getSize() end
|
||||
function clear() end
|
||||
function clearLine() end
|
||||
function setTextColour(colour) end
|
||||
setTextColor = setTextColour
|
||||
function setBackgroundColour(colour) end
|
||||
setBackgroundColor = setBackgroundColour
|
||||
function isColour() end
|
||||
isColor = isColour
|
||||
function getTextColour() end
|
||||
getTextColor = getTextColor
|
||||
function getBackgroundColour() end
|
||||
getBackgroundColour = getBackgroundColour
|
||||
function blit(text, text_colours, background_colours) end
|
||||
function setPaletteColour(colour, ...) end
|
||||
setPaletteColour = setPaletteColour
|
||||
function getPaletteColour(colour, ...) end
|
||||
getPaletteColour = getPaletteColour
|
||||
function nativePaletteColour(colour) end
|
||||
nativePaletteColour = nativePaletteColour
|
||||
|
||||
--- @type Redirect
|
||||
local Redirect = {}
|
||||
|
||||
Redirect.write = write
|
||||
Redirect.scroll = scroll
|
||||
Redirect.setCursorPos = setCursorPos
|
||||
Redirect.setCursorBlink = setCursorBlink
|
||||
Redirect.getCursorPos = getCursorPos
|
||||
Redirect.getSize = getSize
|
||||
Redirect.clear = clear
|
||||
Redirect.clearLine = clearLine
|
||||
Redirect.setTextColour = setTextColour
|
||||
Redirect.setTextColor = setTextColor
|
||||
Redirect.setBackgroundColour = setBackgroundColour
|
||||
Redirect.setBackgroundColor = setBackgroundColor
|
||||
Redirect.isColour = isColour
|
||||
Redirect.isColor = isColor
|
||||
Redirect.getTextColour = getTextColour
|
||||
Redirect.getTextColor = getTextColor
|
||||
Redirect.getBackgroundColour = getBackgroundColour
|
||||
Redirect.getBackgroundColor = getBackgroundColor
|
||||
Redirect.blit = blit
|
||||
Redirect.setPaletteColour = setPaletteColour
|
||||
Redirect.setPaletteColor = setPaletteColor
|
||||
Redirect.getPaletteColour = getPaletteColour
|
||||
Redirect.getPaletteColor = getPaletteColor
|
230
doc/stub/turtle.lua
Normal file
230
doc/stub/turtle.lua
Normal file
@ -0,0 +1,230 @@
|
||||
--- Move the turtle forward one block.
|
||||
-- @treturn boolean Whether the turtle could successfully move.
|
||||
-- @treturn string|nil The reason the turtle could not move.
|
||||
function forward() end
|
||||
|
||||
--- Move the turtle backwards one block.
|
||||
-- @treturn boolean Whether the turtle could successfully move.
|
||||
-- @treturn string|nil The reason the turtle could not move.
|
||||
function back() end
|
||||
|
||||
--- Move the turtle up one block.
|
||||
-- @treturn boolean Whether the turtle could successfully move.
|
||||
-- @treturn string|nil The reason the turtle could not move.
|
||||
function up() end
|
||||
|
||||
--- Move the turtle down one block.
|
||||
-- @treturn boolean Whether the turtle could successfully move.
|
||||
-- @treturn string|nil The reason the turtle could not move.
|
||||
function down() end
|
||||
|
||||
--- Rotate the turtle 90 degress to the left.
|
||||
function turnLeft() end
|
||||
|
||||
--- Rotate the turtle 90 degress to the right.
|
||||
function turnRight() end
|
||||
|
||||
--- Attempt to break the block in front of the turtle.
|
||||
--
|
||||
-- This requires a turtle tool capable of breaking the block. Diamond pickaxes
|
||||
-- (mining turtles) can break any vanilla block, but other tools (such as axes)
|
||||
-- are more limited.
|
||||
--
|
||||
-- @tparam[opt] "left"|"right" side The specific tool to use.
|
||||
-- @treturn boolean Whether a block was broken.
|
||||
-- @treturn string|nil The reason no block was broken.
|
||||
function dig(side) end
|
||||
|
||||
--- Attempt to break the block above the turtle. See @{dig} for full details.
|
||||
--
|
||||
-- @tparam[opt] "left"|"right" side The specific tool to use.
|
||||
-- @treturn boolean Whether a block was broken.
|
||||
-- @treturn string|nil The reason no block was broken.
|
||||
function digUp(side) end
|
||||
|
||||
--- Attempt to break the block below the turtle. See @{dig} for full details.
|
||||
--
|
||||
-- @tparam[opt] "left"|"right" side The specific tool to use.
|
||||
-- @treturn boolean Whether a block was broken.
|
||||
-- @treturn string|nil The reason no block was broken.
|
||||
function digDown(side) end
|
||||
|
||||
--- Attack the entity in front of the turtle.
|
||||
--
|
||||
-- @tparam[opt] "left"|"right" side The specific tool to use.
|
||||
-- @treturn boolean Whether an entity was attacked.
|
||||
-- @treturn string|nil The reason nothing was attacked.
|
||||
function attack(side) end
|
||||
|
||||
--- Attack the entity above the turtle.
|
||||
--
|
||||
-- @tparam[opt] "left"|"right" side The specific tool to use.
|
||||
-- @treturn boolean Whether an entity was attacked.
|
||||
-- @treturn string|nil The reason nothing was attacked.
|
||||
function attackUp(side) end
|
||||
|
||||
--- Attack the entity below the turtle.
|
||||
--
|
||||
-- @tparam[opt] "left"|"right" side The specific tool to use.
|
||||
-- @treturn boolean Whether an entity was attacked.
|
||||
-- @treturn string|nil The reason nothing was attacked.
|
||||
function attackDown(side) end
|
||||
|
||||
--- Place a block or item into the world in front of the turtle.
|
||||
--
|
||||
-- @treturn boolean Whether the block could be placed.
|
||||
-- @treturn string|nil The reason the block was not placed.
|
||||
function place() end
|
||||
|
||||
--- Place a block or item into the world above the turtle.
|
||||
--
|
||||
-- @treturn boolean Whether the block could be placed.
|
||||
-- @treturn string|nil The reason the block was not placed.
|
||||
function placeUp() end
|
||||
|
||||
--- Place a block or item into the world below the turtle.
|
||||
--
|
||||
-- @treturn boolean Whether the block could be placed.
|
||||
-- @treturn string|nil The reason the block was not placed.
|
||||
function placeDown() end
|
||||
|
||||
--- Drop the currently selected stack into the inventory in front of the turtle,
|
||||
-- or as an item into the world if there is no inventory.
|
||||
--
|
||||
-- @tparam[opt] number count The number of items to drop. If not given, the
|
||||
-- entire stack will be dropped.
|
||||
-- @treturn boolean Whether items were dropped.
|
||||
-- @treturn string|nil The reason the no items were dropped.
|
||||
-- @see select
|
||||
function drop(count) end
|
||||
|
||||
--- Drop the currently selected stack into the inventory above the turtle, or as
|
||||
-- an item into the world if there is no inventory.
|
||||
--
|
||||
-- @tparam[opt] number count The number of items to drop. If not given, the
|
||||
-- entire stack will be dropped.
|
||||
-- @treturn boolean Whether items were dropped.
|
||||
-- @treturn string|nil The reason the no items were dropped.
|
||||
-- @see select
|
||||
function dropUp(count) end
|
||||
|
||||
--- Drop the currently selected stack into the inventory below the turtle, or as
|
||||
-- an item into the world if there is no inventory.
|
||||
--
|
||||
-- @tparam[opt] number count The number of items to drop. If not given, the
|
||||
-- entire stack will be dropped.
|
||||
-- @treturn boolean Whether items were dropped.
|
||||
-- @treturn string|nil The reason the no items were dropped.
|
||||
-- @see select
|
||||
function dropDown(count) end
|
||||
|
||||
--- Suck an item from the inventory in front of the turtle, or from an item
|
||||
-- floating in the world.
|
||||
--
|
||||
-- This will pull items into the first acceptable slot, starting at the
|
||||
-- @{select|currently selected} one.
|
||||
--
|
||||
-- @tparam[opt] number count The number of items to suck. If not given, up to a
|
||||
-- stack of items will be picked up.
|
||||
-- @treturn boolean Whether items were picked up.
|
||||
-- @treturn string|nil The reason the no items were picked up.
|
||||
function suck(count) end
|
||||
|
||||
--- Suck an item from the inventory above the turtle, or from an item floating
|
||||
-- in the world.
|
||||
--
|
||||
-- @tparam[opt] number count The number of items to suck. If not given, up to a
|
||||
-- stack of items will be picked up.
|
||||
-- @treturn boolean Whether items were picked up.
|
||||
-- @treturn string|nil The reason the no items were picked up.
|
||||
function suckUp(count) end
|
||||
|
||||
--- Suck an item from the inventory below the turtle, or from an item floating
|
||||
-- in the world.
|
||||
--
|
||||
-- @tparam[opt] number count The number of items to suck. If not given, up to a
|
||||
-- stack of items will be picked up.
|
||||
-- @treturn boolean Whether items were picked up.
|
||||
-- @treturn string|nil The reason the no items were picked up.
|
||||
function suckDown(count) end
|
||||
|
||||
--- Check if there is a solid block in front of the turtle. In this case, solid
|
||||
-- refers to any non-air or liquid block.
|
||||
--
|
||||
-- @treturn boolean If there is a solid block in front.
|
||||
function detect() end
|
||||
|
||||
--- Check if there is a solid block above the turtle.
|
||||
--
|
||||
-- @treturn boolean If there is a solid block above.
|
||||
function detectUp() end
|
||||
|
||||
--- Check if there is a solid block below the turtle.
|
||||
--
|
||||
-- @treturn boolean If there is a solid block below.
|
||||
function detectDown() end
|
||||
|
||||
function compare() end
|
||||
function compareUp() end
|
||||
function compareDown() end
|
||||
|
||||
function inspect() end
|
||||
function inspectUp() end
|
||||
function inspectDown() end
|
||||
|
||||
|
||||
--- Change the currently selected slot.
|
||||
--
|
||||
-- The selected slot is determines what slot actions like @{drop} or
|
||||
-- @{getItemCount} act on.
|
||||
--
|
||||
-- @tparam number slot The slot to select.
|
||||
-- @see getSelectedSlot
|
||||
function select(slot) end
|
||||
|
||||
--- Get the currently selected slot.
|
||||
--
|
||||
-- @treturn number The current slot.
|
||||
-- @see select
|
||||
function getSelectedSlot() end
|
||||
|
||||
--- Get the number of items in the given slot.
|
||||
--
|
||||
-- @tparam[opt] number slot The slot we wish to check. Defaults to the @{turtle.select|selected slot}.
|
||||
-- @treturn number The number of items in this slot.
|
||||
function getItemCount(slot) end
|
||||
|
||||
--- Get the remaining number of items which may be stored in this stack.
|
||||
--
|
||||
-- For instance, if a slot contains 13 blocks of dirt, it has room for another 51.
|
||||
--
|
||||
-- @tparam[opt] number slot The slot we wish to check. Defaults to the @{turtle.select|selected slot}.
|
||||
-- @treturn number The space left in this slot.
|
||||
function getItemSpace(slot) end
|
||||
|
||||
|
||||
--- Get detailed information about the items in the given slot.
|
||||
--
|
||||
-- @tparam[opt] number slot The slot to get information about. Defaults to the @{turtle.select|selected slot}.
|
||||
-- @treturn nil|table Information about the given slot, or @{nil} if it is empty.
|
||||
-- @usage Print the current slot, assuming it contains 13 dirt.
|
||||
--
|
||||
-- print(textutils.serialize(turtle.getItemDetail()))
|
||||
-- -- => {
|
||||
-- -- name = "minecraft:dirt",
|
||||
-- -- damage = 0,
|
||||
-- -- count = 13,
|
||||
-- -- }
|
||||
function getItemDetail(slot) end
|
||||
|
||||
function getFuelLevel() end
|
||||
|
||||
function refuel(count) end
|
||||
function compareTo(slot) end
|
||||
function transferTo(slot, count) end
|
||||
|
||||
function getFuelLimit() end
|
||||
function equipLeft() end
|
||||
function equipRight() end
|
||||
|
||||
function craft(limit) end
|
186
doc/styles.css
Normal file
186
doc/styles.css
Normal file
@ -0,0 +1,186 @@
|
||||
/* Basic reset on elements */
|
||||
h1, h2, h3, h4, p, table, div, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* Make the page a little more airy */
|
||||
body {
|
||||
margin: 20px auto;
|
||||
max-width: 1200px;
|
||||
padding: 0 10px;
|
||||
line-height: 1.6;
|
||||
color: #222;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* Try to use system default fonts. */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans",
|
||||
"Droid Sans", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
code, pre, .parameter, .type, .definition-name, .reference {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
/* Some definitions of basic tags */
|
||||
code {
|
||||
color: #c7254e;
|
||||
background-color: #f9f2f4;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.9em 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
font-weight: lighter;
|
||||
border-bottom: solid 1px #aaa;
|
||||
}
|
||||
|
||||
h2, h3, h4 { margin: 1.4em 0 0.3em;}
|
||||
h2 { font-size: 1.25em; }
|
||||
h3 { font-size: 1.15em; font-weight: bold; }
|
||||
h4 { font-size: 1.06em; }
|
||||
|
||||
a, a:visited, a:active { font-weight: bold; color: #004080; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
blockquote { margin-left: 3em; }
|
||||
|
||||
/* Stop sublists from having initial vertical space */
|
||||
ul ul { margin-top: 0px; }
|
||||
ol ul { margin-top: 0px; }
|
||||
ol ol { margin-top: 0px; }
|
||||
ul ol { margin-top: 0px; }
|
||||
|
||||
/* Make the target distinct; helps when we're navigating to a function */
|
||||
a:target + * { background-color: #FFFF99; }
|
||||
|
||||
/* Allow linking to any subsection */
|
||||
a[name]::before { content: "#"; }
|
||||
|
||||
/* Layout */
|
||||
#main {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
min-height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
#main > nav {
|
||||
flex-basis: 30%;
|
||||
min-width: 150px;
|
||||
max-width: 250px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
nav h1, nav ul { padding: 0em 10px; }
|
||||
|
||||
nav h2 {
|
||||
background-color:#e7e7e7;
|
||||
font-size: 1.1em;
|
||||
color:#000000;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
flex-shrink: 1;
|
||||
flex-basis: 80%;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/* The definition lists at the top of each page */
|
||||
table.definition-list {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.definition-list td, table.definition-list th {
|
||||
border: 1px solid #cccccc;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
table.definition-list th {
|
||||
background-color: #f0f0f0;
|
||||
min-width: 200px;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.definition-list td { width: 100%; }
|
||||
|
||||
dl.definition dt {
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 1em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
dl.definition dt .definition-name {
|
||||
padding: 0 0.1em;
|
||||
margin: 0 0.1em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
dl.definition dd {
|
||||
padding-bottom: 1em;
|
||||
margin: 10px 0 0 20px;
|
||||
}
|
||||
|
||||
dl.definition h3 {
|
||||
font-size: .95em;
|
||||
}
|
||||
|
||||
/* Links to source-code */
|
||||
.source-link { font-size: 0.8em; }
|
||||
.source-link::before { content: '[' }
|
||||
.source-link::after { content: ']' }
|
||||
a.source-link, a.source-link:visited, a.source-link:active { color: #505050; }
|
||||
|
||||
/* Method definitions */
|
||||
span.parameter:after { content:":"; padding-left: 0.3em; }
|
||||
.optional { text-decoration: underline dotted; }
|
||||
|
||||
/** Fancy colour display. */
|
||||
.colour-ref {
|
||||
display: inline-block;
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
margin: 0.1em 0.1em 0.3em 0.1em; /* Terrrible hack to force vertical alignment. */
|
||||
border: solid 1px black;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* styles for prettification of source */
|
||||
.highlight .comment { color: #558817; }
|
||||
.highlight .constant { color: #a8660d; }
|
||||
.highlight .escape { color: #844631; }
|
||||
.highlight .keyword { color: #aa5050; font-weight: bold; }
|
||||
.highlight .library { color: #0e7c6b; }
|
||||
.highlight .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
|
||||
.highlight .string { color: #8080ff; }
|
||||
.highlight .literal-kw { color: #8080ff; }
|
||||
.highlight .number { color: #f8660d; }
|
||||
.highlight .operator { color: #2239a8; font-weight: bold; }
|
||||
.highlight .preprocessor, pre .prepro { color: #a33243; }
|
||||
.highlight .global { color: #800080; }
|
||||
.highlight .user-keyword { color: #800080; }
|
||||
.highlight .prompt { color: #558817; }
|
||||
.highlight .url { color: #272fc2; text-decoration: underline; }
|
@ -1,19 +1,51 @@
|
||||
; -*- mode: Lisp;-*-
|
||||
|
||||
(sources
|
||||
/doc/stub/
|
||||
/src/main/resources/assets/computercraft/lua/bios.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/
|
||||
/src/test/resources/test-rom)
|
||||
|
||||
|
||||
(doc
|
||||
(title "CC: Tweaked")
|
||||
(index doc/index.md)
|
||||
(source-link https://github.com/SquidDev-CC/CC-Tweaked/blob/${commit}/${path}#L${line})
|
||||
|
||||
(library-path
|
||||
/doc/stub/
|
||||
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/command
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/turtle
|
||||
|
||||
/src/main/resources/assets/computercraft/lua/rom/modules/main
|
||||
/src/main/resources/assets/computercraft/lua/rom/modules/command
|
||||
/src/main/resources/assets/computercraft/lua/rom/modules/turtle))
|
||||
|
||||
(at /
|
||||
(linters
|
||||
syntax:string-index
|
||||
|
||||
;; It'd be nice to avoid this, but right now there's a lot of instances of
|
||||
;; it.
|
||||
-var:set-loop
|
||||
|
||||
;; It's useful to name arguments for documentation, so we allow this. It'd
|
||||
;; be good to find a compromise in the future, but this works for now.
|
||||
-var:unused-arg))
|
||||
-var:unused-arg
|
||||
|
||||
;; Suppress a couple of documentation comments warnings for now. We'll
|
||||
;; hopefully be able to remove them in the future.
|
||||
-doc:undocumented -doc:undocumented-arg -doc:unresolved-reference
|
||||
-var:unresolved-member)
|
||||
(lint
|
||||
(bracket-spaces
|
||||
(call no-space)
|
||||
(function-args no-space)
|
||||
(parens no-space)
|
||||
(table space)
|
||||
(index no-space))))
|
||||
|
||||
;; We disable the unused global linter in bios.lua and the APIs. In the future
|
||||
;; hopefully we'll get illuaminate to handle this.
|
||||
@ -21,8 +53,26 @@
|
||||
(/src/main/resources/assets/computercraft/lua/bios.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/)
|
||||
(linters -var:unused-global)
|
||||
(lint
|
||||
(allow-toplevel-global true)))
|
||||
(lint (allow-toplevel-global true)))
|
||||
|
||||
;; These warnings are broken right now
|
||||
(at (bios.lua worm.lua) (linters -control:unreachable))
|
||||
;; Silence some variable warnings in documentation stubs.
|
||||
(at /doc/stub
|
||||
(linters -var:unused-global)
|
||||
(lint (allow-toplevel-global true)))
|
||||
|
||||
;; Ensure any fully documented modules stay fully documented.
|
||||
(at
|
||||
(/src/main/resources/assets/computercraft/lua/rom/apis/colors.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/colours.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/disk.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/gps.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/help.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/keys.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/paintutils.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/parallel.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/peripheral.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/rednet.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/settings.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/texutils.lua
|
||||
/src/main/resources/assets/computercraft/lua/rom/apis/vector.lua)
|
||||
(linters doc:undocumented doc:undocumented-arg))
|
||||
|
@ -24,6 +24,7 @@ import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem;
|
||||
import dan200.computercraft.shared.peripheral.monitor.BlockMonitor;
|
||||
import dan200.computercraft.shared.peripheral.printer.BlockPrinter;
|
||||
import dan200.computercraft.shared.peripheral.speaker.BlockSpeaker;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
||||
@ -81,7 +82,7 @@ public final class ComputerCraft
|
||||
public static long httpMaxDownload = 16 * 1024 * 1024;
|
||||
public static long httpMaxUpload = 4 * 1024 * 1024;
|
||||
public static int httpMaxWebsockets = 4;
|
||||
public static int httpMaxWebsocketMessage = Websocket.MAX_MESSAGE_SIZE;
|
||||
public static int httpMaxWebsocketMessage = 128 * 1024;
|
||||
|
||||
public static boolean enableCommandBlock = false;
|
||||
public static int modem_range = 64;
|
||||
@ -89,6 +90,7 @@ public final class ComputerCraft
|
||||
public static int modem_rangeDuringStorm = 64;
|
||||
public static int modem_highAltitudeRangeDuringStorm = 384;
|
||||
public static int maxNotesPerTick = 8;
|
||||
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
|
||||
|
||||
public static boolean turtlesNeedFuel = true;
|
||||
public static int turtleFuelLimit = 20000;
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api.filesystem;
|
||||
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A simple version of {@link BasicFileAttributes}, which provides what information a {@link IMount} already exposes.
|
||||
*/
|
||||
final class FileAttributes implements BasicFileAttributes
|
||||
{
|
||||
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
|
||||
|
||||
private final boolean isDirectory;
|
||||
private final long size;
|
||||
|
||||
FileAttributes( boolean isDirectory, long size )
|
||||
{
|
||||
this.isDirectory = isDirectory;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime lastModifiedTime()
|
||||
{
|
||||
return EPOCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime lastAccessTime()
|
||||
{
|
||||
return EPOCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime creationTime()
|
||||
{
|
||||
return EPOCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegularFile()
|
||||
{
|
||||
return !isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory()
|
||||
{
|
||||
return isDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSymbolicLink()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOther()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fileKey()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public class FileOperationException extends IOException
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public FileOperationException( String message )
|
||||
public FileOperationException( @Nonnull String message )
|
||||
{
|
||||
super( Objects.requireNonNull( message, "message cannot be null" ) );
|
||||
this.filename = null;
|
||||
|
@ -14,6 +14,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -93,4 +94,18 @@ public interface IMount
|
||||
{
|
||||
return Channels.newChannel( openForRead( path ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes about the given file.
|
||||
*
|
||||
* @param path The path to query.
|
||||
* @return File attributes for the given file.
|
||||
* @throws IOException If the file does not exist, or attributes could not be fetched.
|
||||
*/
|
||||
@Nonnull
|
||||
default BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( !exists( path ) ) throw new FileOperationException( path, "No such file" );
|
||||
return new FileAttributes( isDirectory( path ), getSize( path ) );
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
/**
|
||||
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
|
||||
@ -105,4 +106,16 @@ public interface IWritableMount extends IMount
|
||||
* @throws IOException If the remaining space could not be computed.
|
||||
*/
|
||||
long getRemainingSpace() throws IOException;
|
||||
|
||||
/**
|
||||
* Get the capacity of this mount. This should be equal to the size of all files/directories on this mount, minus
|
||||
* the {@link #getRemainingSpace()}.
|
||||
*
|
||||
* @return The capacity of this mount, in bytes.
|
||||
*/
|
||||
@Nonnull
|
||||
default OptionalLong getCapacity()
|
||||
{
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
}
|
||||
|
@ -6,194 +6,323 @@
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class FixedWidthFontRenderer
|
||||
{
|
||||
private static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" );
|
||||
public static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/term_background.png" );
|
||||
|
||||
/**
|
||||
* Like {@link DefaultVertexFormats#POSITION_TEX_COLOR}, but flipped. This is backported from 1.15, hence the
|
||||
* custom format.
|
||||
*/
|
||||
public static final VertexFormat POSITION_COLOR_TEX = new VertexFormat();
|
||||
|
||||
static
|
||||
{
|
||||
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.POSITION_3F );
|
||||
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.COLOR_4UB );
|
||||
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.TEX_2F );
|
||||
}
|
||||
|
||||
public static final int FONT_HEIGHT = 9;
|
||||
public static final int FONT_WIDTH = 6;
|
||||
public static final float WIDTH = 256.0f;
|
||||
|
||||
private static FixedWidthFontRenderer instance;
|
||||
|
||||
public static FixedWidthFontRenderer instance()
|
||||
{
|
||||
if( instance != null ) return instance;
|
||||
return instance = new FixedWidthFontRenderer();
|
||||
}
|
||||
|
||||
private final TextureManager m_textureManager;
|
||||
public static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
|
||||
public static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
|
||||
|
||||
private FixedWidthFontRenderer()
|
||||
{
|
||||
m_textureManager = Minecraft.getInstance().getTextureManager();
|
||||
}
|
||||
|
||||
private static void greyscaleify( double[] rgb )
|
||||
private static float toGreyscale( double[] rgb )
|
||||
{
|
||||
Arrays.fill( rgb, (rgb[0] + rgb[1] + rgb[2]) / 3.0f );
|
||||
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
|
||||
}
|
||||
|
||||
private void drawChar( BufferBuilder renderer, double x, double y, int index, int color, Palette p, boolean greyscale )
|
||||
private static int getColour( char c )
|
||||
{
|
||||
int i = "0123456789abcdef".indexOf( c );
|
||||
return i < 0 ? 0 : 15 - i;
|
||||
}
|
||||
|
||||
private static void drawChar( BufferBuilder buffer, float x, float y, int index, float r, float g, float b )
|
||||
{
|
||||
// Short circuit to avoid the common case - the texture should be blank here after all.
|
||||
if( index == '\0' || index == ' ' ) return;
|
||||
|
||||
int column = index % 16;
|
||||
int row = index / 16;
|
||||
|
||||
double[] colour = p.getColour( 15 - color );
|
||||
if( greyscale )
|
||||
{
|
||||
greyscaleify( colour );
|
||||
}
|
||||
float r = (float) colour[0];
|
||||
float g = (float) colour[1];
|
||||
float b = (float) colour[2];
|
||||
|
||||
int xStart = 1 + column * (FONT_WIDTH + 2);
|
||||
int yStart = 1 + row * (FONT_HEIGHT + 2);
|
||||
|
||||
renderer.pos( x, y, 0.0 ).tex( xStart / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
buffer.pos( x, y, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, yStart / WIDTH ).endVertex();
|
||||
buffer.pos( x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
|
||||
buffer.pos( x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
|
||||
buffer.pos( x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
|
||||
buffer.pos( x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
|
||||
buffer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
|
||||
}
|
||||
|
||||
private void drawQuad( BufferBuilder renderer, double x, double y, int color, double width, Palette p, boolean greyscale )
|
||||
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, float r, float g, float b )
|
||||
{
|
||||
double[] colour = p.getColour( 15 - color );
|
||||
buffer.pos( x, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_START ).endVertex();
|
||||
buffer.pos( x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
|
||||
buffer.pos( x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
|
||||
buffer.pos( x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
|
||||
buffer.pos( x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
|
||||
buffer.pos( x + width, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_END ).endVertex();
|
||||
}
|
||||
|
||||
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
|
||||
{
|
||||
double[] colour = palette.getColour( getColour( colourIndex ) );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
greyscaleify( colour );
|
||||
r = g = b = toGreyscale( colour );
|
||||
}
|
||||
else
|
||||
{
|
||||
r = (float) colour[0];
|
||||
g = (float) colour[1];
|
||||
b = (float) colour[2];
|
||||
}
|
||||
float r = (float) colour[0];
|
||||
float g = (float) colour[1];
|
||||
float b = (float) colour[2];
|
||||
|
||||
renderer.pos( x, y, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x + width, y, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x + width, y, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( x + width, y + FONT_HEIGHT, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
drawQuad( buffer, x, y, width, height, r, g, b );
|
||||
}
|
||||
|
||||
private boolean isGreyScale( int colour )
|
||||
private static void drawBackground(
|
||||
@Nonnull BufferBuilder renderer, float x, float y,
|
||||
@Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
|
||||
float leftMarginSize, float rightMarginSize, float height
|
||||
)
|
||||
{
|
||||
return colour == 0 || colour == 15 || colour == 7 || colour == 8;
|
||||
}
|
||||
if( leftMarginSize > 0 )
|
||||
{
|
||||
drawQuad( renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
|
||||
}
|
||||
|
||||
public void drawStringBackgroundPart( int x, int y, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
|
||||
{
|
||||
// Draw the quads
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder renderer = tessellator.getBuffer();
|
||||
renderer.begin( GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION_COLOR );
|
||||
if( leftMarginSize > 0.0 )
|
||||
if( rightMarginSize > 0 )
|
||||
{
|
||||
int colour1 = "0123456789abcdef".indexOf( backgroundColour.charAt( 0 ) );
|
||||
if( colour1 < 0 || (greyScale && !isGreyScale( colour1 )) )
|
||||
{
|
||||
colour1 = 15;
|
||||
}
|
||||
drawQuad( renderer, x - leftMarginSize, y, colour1, leftMarginSize, p, greyScale );
|
||||
}
|
||||
if( rightMarginSize > 0.0 )
|
||||
{
|
||||
int colour2 = "0123456789abcdef".indexOf( backgroundColour.charAt( backgroundColour.length() - 1 ) );
|
||||
if( colour2 < 0 || (greyScale && !isGreyScale( colour2 )) )
|
||||
{
|
||||
colour2 = 15;
|
||||
}
|
||||
drawQuad( renderer, x + backgroundColour.length() * FONT_WIDTH, y, colour2, rightMarginSize, p, greyScale );
|
||||
drawQuad( renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) );
|
||||
}
|
||||
|
||||
// Batch together runs of identical background cells.
|
||||
int blockStart = 0;
|
||||
char blockColour = '\0';
|
||||
for( int i = 0; i < backgroundColour.length(); i++ )
|
||||
{
|
||||
int colour = "0123456789abcdef".indexOf( backgroundColour.charAt( i ) );
|
||||
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
|
||||
char colourIndex = backgroundColour.charAt( i );
|
||||
if( colourIndex == blockColour ) continue;
|
||||
|
||||
if( blockColour != '\0' )
|
||||
{
|
||||
colour = 15;
|
||||
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
|
||||
}
|
||||
drawQuad( renderer, x + i * FONT_WIDTH, y, colour, FONT_WIDTH, p, greyScale );
|
||||
|
||||
blockColour = colourIndex;
|
||||
blockStart = i;
|
||||
}
|
||||
|
||||
if( blockColour != '\0' )
|
||||
{
|
||||
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
|
||||
}
|
||||
GlStateManager.disableTexture();
|
||||
tessellator.draw();
|
||||
GlStateManager.enableTexture();
|
||||
}
|
||||
|
||||
public void drawStringTextPart( int x, int y, TextBuffer s, TextBuffer textColour, boolean greyScale, Palette p )
|
||||
public static void drawString(
|
||||
@Nonnull BufferBuilder renderer, float x, float y,
|
||||
@Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
|
||||
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize
|
||||
)
|
||||
{
|
||||
// Draw the quads
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder renderer = tessellator.getBuffer();
|
||||
renderer.begin( GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION_TEX_COLOR );
|
||||
for( int i = 0; i < s.length(); i++ )
|
||||
if( backgroundColour != null )
|
||||
{
|
||||
// Switch colour
|
||||
int colour = "0123456789abcdef".indexOf( textColour.charAt( i ) );
|
||||
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
|
||||
drawBackground( renderer, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT );
|
||||
}
|
||||
|
||||
for( int i = 0; i < text.length(); i++ )
|
||||
{
|
||||
double[] colour = palette.getColour( getColour( textColour.charAt( i ) ) );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
colour = 0;
|
||||
r = g = b = toGreyscale( colour );
|
||||
}
|
||||
else
|
||||
{
|
||||
r = (float) colour[0];
|
||||
g = (float) colour[1];
|
||||
b = (float) colour[2];
|
||||
}
|
||||
|
||||
// Draw char
|
||||
int index = s.charAt( i );
|
||||
if( index < 0 || index > 255 )
|
||||
{
|
||||
index = '?';
|
||||
}
|
||||
drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale );
|
||||
int index = text.charAt( i );
|
||||
if( index > 255 ) index = '?';
|
||||
drawChar( renderer, x + i * FONT_WIDTH, y, index, r, g, b );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void drawString(
|
||||
float x, float y, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
|
||||
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize
|
||||
)
|
||||
{
|
||||
bindFont();
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
begin( buffer );
|
||||
drawString( buffer, x, y, text, textColour, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize );
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
public void drawString( TextBuffer s, int x, int y, TextBuffer textColour, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
|
||||
public static void drawTerminalWithoutCursor(
|
||||
@Nonnull BufferBuilder buffer, float x, float y,
|
||||
@Nonnull Terminal terminal, boolean greyscale,
|
||||
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
|
||||
)
|
||||
{
|
||||
// Draw background
|
||||
if( backgroundColour != null )
|
||||
Palette palette = terminal.getPalette();
|
||||
int height = terminal.getHeight();
|
||||
|
||||
// Top and bottom margins
|
||||
drawBackground(
|
||||
buffer, x, y - topMarginSize,
|
||||
terminal.getBackgroundColourLine( 0 ), palette, greyscale,
|
||||
leftMarginSize, rightMarginSize, topMarginSize
|
||||
);
|
||||
|
||||
drawBackground(
|
||||
buffer, x, y + height * FONT_HEIGHT,
|
||||
terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
|
||||
leftMarginSize, rightMarginSize, bottomMarginSize
|
||||
);
|
||||
|
||||
// The main text
|
||||
for( int i = 0; i < height; i++ )
|
||||
{
|
||||
// Bind the background texture
|
||||
m_textureManager.bindTexture( BACKGROUND );
|
||||
|
||||
// Draw the quads
|
||||
drawStringBackgroundPart( x, y, backgroundColour, leftMarginSize, rightMarginSize, greyScale, p );
|
||||
}
|
||||
|
||||
// Draw text
|
||||
if( s != null && textColour != null )
|
||||
{
|
||||
// Bind the font texture
|
||||
bindFont();
|
||||
|
||||
// Draw the quads
|
||||
drawStringTextPart( x, y, s, textColour, greyScale, p );
|
||||
drawString(
|
||||
buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
|
||||
terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ),
|
||||
palette, greyscale, leftMarginSize, rightMarginSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public int getStringWidth( String s )
|
||||
public static void drawCursor(
|
||||
@Nonnull BufferBuilder buffer, float x, float y,
|
||||
@Nonnull Terminal terminal, boolean greyscale
|
||||
)
|
||||
{
|
||||
if( s == null )
|
||||
Palette palette = terminal.getPalette();
|
||||
int width = terminal.getWidth();
|
||||
int height = terminal.getHeight();
|
||||
|
||||
int cursorX = terminal.getCursorX();
|
||||
int cursorY = terminal.getCursorY();
|
||||
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height && FrameInfo.getGlobalCursorBlink() )
|
||||
{
|
||||
return 0;
|
||||
double[] colour = palette.getColour( 15 - terminal.getTextColour() );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
r = g = b = toGreyscale( colour );
|
||||
}
|
||||
else
|
||||
{
|
||||
r = (float) colour[0];
|
||||
g = (float) colour[1];
|
||||
b = (float) colour[2];
|
||||
}
|
||||
|
||||
drawChar( buffer, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', r, g, b );
|
||||
}
|
||||
return s.length() * FONT_WIDTH;
|
||||
}
|
||||
|
||||
public void bindFont()
|
||||
public static void drawTerminal(
|
||||
@Nonnull BufferBuilder buffer, float x, float y,
|
||||
@Nonnull Terminal terminal, boolean greyscale,
|
||||
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
|
||||
)
|
||||
{
|
||||
m_textureManager.bindTexture( FONT );
|
||||
drawTerminalWithoutCursor( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
|
||||
drawCursor( buffer, x, y, terminal, greyscale );
|
||||
}
|
||||
|
||||
public static void drawTerminal(
|
||||
float x, float y, @Nonnull Terminal terminal, boolean greyscale,
|
||||
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
|
||||
)
|
||||
{
|
||||
bindFont();
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
begin( buffer );
|
||||
drawTerminal( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
public static void drawEmptyTerminal( float x, float y, float width, float height )
|
||||
{
|
||||
bindFont();
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
begin( buffer );
|
||||
|
||||
Colour colour = Colour.Black;
|
||||
drawQuad( buffer, x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
|
||||
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
public static void drawBlocker( @Nonnull BufferBuilder buffer, float x, float y, float width, float height )
|
||||
{
|
||||
Colour colour = Colour.Black;
|
||||
drawQuad( buffer, x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
|
||||
}
|
||||
|
||||
public static void drawBlocker( float x, float y, float width, float height )
|
||||
{
|
||||
bindFont();
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
begin( buffer );
|
||||
drawBlocker( buffer, x, y, width, height );
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
public static void bindFont()
|
||||
{
|
||||
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
|
||||
GlStateManager.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
|
||||
|
||||
GlStateManager.enableTexture();
|
||||
}
|
||||
|
||||
public static void begin( BufferBuilder buffer )
|
||||
{
|
||||
buffer.begin( GL11.GL_TRIANGLES, POSITION_COLOR_TEX );
|
||||
}
|
||||
}
|
||||
|
@ -5,29 +5,18 @@
|
||||
*/
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.IGuiEventListener;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.util.SharedConstants;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.BACKGROUND;
|
||||
|
||||
public class WidgetTerminal implements IGuiEventListener
|
||||
{
|
||||
private static final float TERMINATE_TIME = 0.5f;
|
||||
@ -329,89 +318,19 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
Terminal terminal = computer != null ? computer.getTerminal() : null;
|
||||
if( terminal != null )
|
||||
{
|
||||
// Draw the terminal
|
||||
boolean greyscale = !computer.isColour();
|
||||
Palette palette = terminal.getPalette();
|
||||
|
||||
// Get the data from the terminal first
|
||||
// Unfortunately we have to keep the lock for the whole of drawing, so the text doesn't change under us.
|
||||
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
|
||||
boolean tblink = terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink();
|
||||
int tw = terminal.getWidth();
|
||||
int th = terminal.getHeight();
|
||||
int tx = terminal.getCursorX();
|
||||
int ty = terminal.getCursorY();
|
||||
|
||||
// Draw margins
|
||||
TextBuffer emptyLine = new TextBuffer( ' ', tw );
|
||||
if( topMargin > 0 )
|
||||
{
|
||||
fontRenderer.drawString( emptyLine, originX, originY - topMargin,
|
||||
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ),
|
||||
leftMargin, rightMargin, greyscale, palette );
|
||||
}
|
||||
|
||||
if( bottomMargin > 0 )
|
||||
{
|
||||
fontRenderer.drawString( emptyLine, originX, originY + bottomMargin + (th - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
|
||||
terminal.getTextColourLine( th - 1 ), terminal.getBackgroundColourLine( th - 1 ),
|
||||
leftMargin, rightMargin, greyscale, palette );
|
||||
}
|
||||
|
||||
// Draw lines
|
||||
int y = originY;
|
||||
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, originX, y, colour, backgroundColour, leftMargin, rightMargin, greyscale, palette );
|
||||
y += FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
}
|
||||
|
||||
if( tblink && tx >= 0 && ty >= 0 && tx < tw && ty < th )
|
||||
{
|
||||
TextBuffer cursor = new TextBuffer( '_', 1 );
|
||||
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
|
||||
|
||||
fontRenderer.drawString(
|
||||
cursor,
|
||||
originX + FixedWidthFontRenderer.FONT_WIDTH * tx,
|
||||
originY + FixedWidthFontRenderer.FONT_HEIGHT * ty,
|
||||
cursorColour, null,
|
||||
0, 0,
|
||||
greyscale,
|
||||
palette
|
||||
);
|
||||
}
|
||||
FixedWidthFontRenderer.drawTerminal(
|
||||
originX, originY,
|
||||
terminal, !computer.isColour(), topMargin, bottomMargin, leftMargin, rightMargin
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw a black background
|
||||
Colour black = Colour.Black;
|
||||
GlStateManager.color4f( black.getR(), black.getG(), black.getB(), 1.0f );
|
||||
try
|
||||
{
|
||||
int x = originX - leftMargin;
|
||||
int y = originY - rightMargin;
|
||||
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
|
||||
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
|
||||
int x = originX - leftMargin;
|
||||
int y = originY - rightMargin;
|
||||
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
|
||||
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
|
||||
|
||||
client.getTextureManager().bindTexture( BACKGROUND );
|
||||
|
||||
Tessellator tesslector = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tesslector.getBuffer();
|
||||
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
|
||||
buffer.pos( x, y + height, 0 ).tex( 0 / 256.0, height / 256.0 ).endVertex();
|
||||
buffer.pos( x + width, y + height, 0 ).tex( width / 256.0, height / 256.0 ).endVertex();
|
||||
buffer.pos( x + width, y, 0 ).tex( width / 256.0, 0 / 256.0 ).endVertex();
|
||||
buffer.pos( x, y, 0 ).tex( 0 / 256.0, 0 / 256.0 ).endVertex();
|
||||
tesslector.draw();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
|
||||
}
|
||||
FixedWidthFontRenderer.drawEmptyTerminal( x, y, width, height );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,15 +7,12 @@ package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@ -27,7 +24,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
|
||||
import static dan200.computercraft.client.gui.GuiComputer.*;
|
||||
|
||||
/**
|
||||
@ -105,21 +103,11 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
|
||||
|
||||
if( computer != null && terminal != null )
|
||||
{
|
||||
// If we've a computer and terminal then attempt to render it.
|
||||
renderTerminal( terminal, !computer.isColour(), width, height );
|
||||
FixedWidthFontRenderer.drawTerminal( MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise render a plain background
|
||||
Minecraft.getInstance().getTextureManager().bindTexture( BACKGROUND );
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
|
||||
Colour black = Colour.Black;
|
||||
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION );
|
||||
renderTexture( buffer, 0, 0, 0, 0, width, height, black.getR(), black.getG(), black.getB() );
|
||||
tessellator.draw();
|
||||
FixedWidthFontRenderer.drawEmptyTerminal( 0, 0, width, height );
|
||||
}
|
||||
|
||||
GlStateManager.enableDepthTest();
|
||||
@ -190,53 +178,6 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
|
||||
GlStateManager.enableTexture();
|
||||
}
|
||||
|
||||
private static void renderTerminal( Terminal terminal, boolean greyscale, int width, int height )
|
||||
{
|
||||
synchronized( terminal )
|
||||
{
|
||||
int termWidth = terminal.getWidth();
|
||||
int termHeight = terminal.getHeight();
|
||||
|
||||
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
|
||||
Palette palette = terminal.getPalette();
|
||||
|
||||
// Render top/bottom borders
|
||||
TextBuffer emptyLine = new TextBuffer( ' ', termWidth );
|
||||
fontRenderer.drawString(
|
||||
emptyLine, MARGIN, 0,
|
||||
terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), MARGIN, MARGIN, greyscale, palette
|
||||
);
|
||||
fontRenderer.drawString(
|
||||
emptyLine, MARGIN, 2 * MARGIN + (termHeight - 1) * FixedWidthFontRenderer.FONT_HEIGHT,
|
||||
terminal.getTextColourLine( termHeight - 1 ), terminal.getBackgroundColourLine( termHeight - 1 ), MARGIN, MARGIN, greyscale, palette
|
||||
);
|
||||
|
||||
// Render the actual text
|
||||
for( int line = 0; line < termWidth; line++ )
|
||||
{
|
||||
TextBuffer text = terminal.getLine( line );
|
||||
TextBuffer colour = terminal.getTextColourLine( line );
|
||||
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
|
||||
fontRenderer.drawString(
|
||||
text, MARGIN, MARGIN + line * FONT_HEIGHT,
|
||||
colour, backgroundColour, MARGIN, MARGIN, greyscale, palette
|
||||
);
|
||||
}
|
||||
|
||||
// And render the cursor;
|
||||
int tx = terminal.getCursorX(), ty = terminal.getCursorY();
|
||||
if( terminal.getCursorBlink() && FrameInfo.getGlobalCursorBlink() &&
|
||||
tx >= 0 && ty >= 0 && tx < termWidth && ty < termHeight )
|
||||
{
|
||||
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
|
||||
fontRenderer.drawString(
|
||||
new TextBuffer( '_', 1 ), MARGIN + FONT_WIDTH * tx, MARGIN + FONT_HEIGHT * ty,
|
||||
cursorColour, null, 0, 0, greyscale, palette
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
|
||||
{
|
||||
renderTexture( builder, x, y, textureX, textureY, width, height, width, height, r, g, b );
|
||||
|
@ -63,11 +63,12 @@ public final class PrintoutRenderer
|
||||
|
||||
public static void drawText( int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
|
||||
{
|
||||
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
|
||||
|
||||
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
|
||||
{
|
||||
fontRenderer.drawString( text[start + line], x, y + line * FONT_HEIGHT, colours[start + line], null, 0, 0, false, Palette.DEFAULT );
|
||||
FixedWidthFontRenderer.drawString(
|
||||
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT,
|
||||
false, 0, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,11 +79,13 @@ public final class PrintoutRenderer
|
||||
GlStateManager.enableTexture();
|
||||
GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
|
||||
|
||||
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
|
||||
|
||||
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
|
||||
{
|
||||
fontRenderer.drawString( new TextBuffer( text[start + line] ), x, y + line * FONT_HEIGHT, new TextBuffer( colours[start + line] ), null, 0, 0, false, Palette.DEFAULT );
|
||||
FixedWidthFontRenderer.drawString(
|
||||
x, y + line * FONT_HEIGHT,
|
||||
new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ),
|
||||
null, Palette.DEFAULT, false, 0, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,33 +10,29 @@ import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
|
||||
|
||||
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
{
|
||||
@Override
|
||||
public void render( TileMonitor tileEntity, double posX, double posY, double posZ, float f, int i )
|
||||
{
|
||||
if( tileEntity != null )
|
||||
{
|
||||
renderMonitorAt( tileEntity, posX, posY, posZ, f, i );
|
||||
}
|
||||
}
|
||||
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
|
||||
|
||||
private static void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
|
||||
@Override
|
||||
public void render( @Nonnull TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
|
||||
{
|
||||
// Render from the origin monitor
|
||||
ClientMonitor originTerminal = monitor.getClientMonitor();
|
||||
@ -69,223 +65,129 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
float pitch = DirectionUtil.toPitchAngle( front );
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
try
|
||||
|
||||
// Setup initial transform
|
||||
GlStateManager.translated( posX + 0.5, posY + 0.5, posZ + 0.5 );
|
||||
GlStateManager.rotatef( -yaw, 0.0f, 1.0f, 0.0f );
|
||||
GlStateManager.rotatef( pitch, 1.0f, 0.0f, 0.0f );
|
||||
GlStateManager.translated(
|
||||
-0.5 + TileMonitor.RENDER_BORDER + RENDER_MARGIN,
|
||||
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + RENDER_MARGIN) + 0,
|
||||
0.5
|
||||
);
|
||||
double xSize = origin.getWidth() - 2.0 * (RENDER_MARGIN + TileMonitor.RENDER_BORDER);
|
||||
double ySize = origin.getHeight() - 2.0 * (RENDER_MARGIN + TileMonitor.RENDER_BORDER);
|
||||
|
||||
// Get renderers
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
// Set up render state for monitors. We disable writing to the depth buffer (we draw a "blocker" later),
|
||||
// and setup lighting so that we render with a glow.
|
||||
GlStateManager.depthMask( false );
|
||||
GLX.glMultiTexCoord2f( GLX.GL_TEXTURE1, 0xFFFF, 0xFFFF );
|
||||
GlStateManager.disableLighting();
|
||||
mc.gameRenderer.disableLightmap();
|
||||
|
||||
Terminal terminal = originTerminal.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
// Setup initial transform
|
||||
GlStateManager.translated( posX + 0.5, posY + 0.5, posZ + 0.5 );
|
||||
GlStateManager.rotatef( -yaw, 0.0f, 1.0f, 0.0f );
|
||||
GlStateManager.rotatef( pitch, 1.0f, 0.0f, 0.0f );
|
||||
GlStateManager.translated(
|
||||
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
|
||||
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN) + 0,
|
||||
0.5
|
||||
);
|
||||
double xSize = origin.getWidth() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
|
||||
double ySize = origin.getHeight() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
|
||||
// Draw a terminal
|
||||
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
|
||||
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
|
||||
|
||||
// Get renderers
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder renderer = tessellator.getBuffer();
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.scaled( (float) xScale, (float) -yScale, 1.0f );
|
||||
|
||||
// Get terminal
|
||||
boolean redraw = originTerminal.pollTerminalChanged();
|
||||
renderTerminal( originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
|
||||
|
||||
// Draw the contents
|
||||
GlStateManager.depthMask( false );
|
||||
GLX.glMultiTexCoord2f( GLX.GL_TEXTURE1, 0xFFFF, 0xFFFF );
|
||||
GlStateManager.disableLighting();
|
||||
mc.gameRenderer.disableLightmap();
|
||||
try
|
||||
{
|
||||
Terminal terminal = originTerminal.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
Palette palette = terminal.getPalette();
|
||||
|
||||
// Allocate display lists
|
||||
if( originTerminal.renderDisplayLists == null )
|
||||
{
|
||||
originTerminal.createLists();
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
// Draw a terminal
|
||||
boolean greyscale = !originTerminal.isColour();
|
||||
int width = terminal.getWidth();
|
||||
int height = terminal.getHeight();
|
||||
int cursorX = terminal.getCursorX();
|
||||
int cursorY = terminal.getCursorY();
|
||||
FixedWidthFontRenderer fontRenderer = FixedWidthFontRenderer.instance();
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
try
|
||||
{
|
||||
double xScale = xSize / (width * FixedWidthFontRenderer.FONT_WIDTH);
|
||||
double yScale = ySize / (height * FixedWidthFontRenderer.FONT_HEIGHT);
|
||||
GlStateManager.scaled( xScale, -yScale, 1.0 );
|
||||
|
||||
// Draw background
|
||||
mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND );
|
||||
if( redraw )
|
||||
{
|
||||
// Build background display list
|
||||
GlStateManager.newList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
|
||||
double marginYSize = TileMonitor.RENDER_MARGIN / yScale;
|
||||
double marginSquash = marginYSize / FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
|
||||
// Top and bottom margins
|
||||
GlStateManager.pushMatrix();
|
||||
try
|
||||
{
|
||||
GlStateManager.scaled( 1.0, marginSquash, 1.0 );
|
||||
GlStateManager.translated( 0.0, -marginYSize / marginSquash, 0.0 );
|
||||
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette );
|
||||
GlStateManager.translated( 0.0, (marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT) / marginSquash, 0.0 );
|
||||
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
// Backgrounds
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
fontRenderer.drawStringBackgroundPart(
|
||||
0, FixedWidthFontRenderer.FONT_HEIGHT * y,
|
||||
terminal.getBackgroundColourLine( y ),
|
||||
marginXSize, marginXSize,
|
||||
greyscale,
|
||||
palette
|
||||
);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.endList();
|
||||
}
|
||||
}
|
||||
GlStateManager.callList( originTerminal.renderDisplayLists[0] );
|
||||
GlStateManager.clearCurrentColor();
|
||||
|
||||
// Draw text
|
||||
fontRenderer.bindFont();
|
||||
if( redraw )
|
||||
{
|
||||
// Build text display list
|
||||
GlStateManager.newList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
// Lines
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
fontRenderer.drawStringTextPart(
|
||||
0, FixedWidthFontRenderer.FONT_HEIGHT * y,
|
||||
terminal.getLine( y ),
|
||||
terminal.getTextColourLine( y ),
|
||||
greyscale,
|
||||
palette
|
||||
);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.endList();
|
||||
}
|
||||
}
|
||||
GlStateManager.callList( originTerminal.renderDisplayLists[1] );
|
||||
GlStateManager.clearCurrentColor();
|
||||
|
||||
// Draw cursor
|
||||
fontRenderer.bindFont();
|
||||
if( redraw )
|
||||
{
|
||||
// Build cursor display list
|
||||
GlStateManager.newList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
// Cursor
|
||||
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height )
|
||||
{
|
||||
TextBuffer cursor = new TextBuffer( "_" );
|
||||
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
|
||||
fontRenderer.drawString(
|
||||
cursor,
|
||||
FixedWidthFontRenderer.FONT_WIDTH * cursorX,
|
||||
FixedWidthFontRenderer.FONT_HEIGHT * cursorY,
|
||||
cursorColour, null,
|
||||
0, 0,
|
||||
greyscale,
|
||||
palette
|
||||
);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.endList();
|
||||
}
|
||||
}
|
||||
if( FrameInfo.getGlobalCursorBlink() )
|
||||
{
|
||||
GlStateManager.callList( originTerminal.renderDisplayLists[2] );
|
||||
GlStateManager.clearCurrentColor();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw a big black quad
|
||||
mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND );
|
||||
final Colour colour = Colour.Black;
|
||||
|
||||
final float r = colour.getR();
|
||||
final float g = colour.getG();
|
||||
final float b = colour.getB();
|
||||
|
||||
renderer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_COLOR );
|
||||
renderer.pos( -TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0D ).tex( 0.0, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( -TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).tex( 0.0, 1.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0D ).tex( 1.0, 0.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).tex( 1.0, 1.0 ).color( r, g, b, 1.0f ).endVertex();
|
||||
tessellator.draw();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.depthMask( true );
|
||||
mc.gameRenderer.enableLightmap();
|
||||
GlStateManager.enableLighting();
|
||||
}
|
||||
|
||||
// Draw the depth blocker
|
||||
GlStateManager.colorMask( false, false, false, false );
|
||||
try
|
||||
{
|
||||
mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND );
|
||||
renderer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
|
||||
renderer.pos( -TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
|
||||
renderer.pos( -TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
|
||||
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
|
||||
renderer.pos( xSize + TileMonitor.RENDER_MARGIN, -ySize - TileMonitor.RENDER_MARGIN, 0.0 ).endVertex();
|
||||
tessellator.draw();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.colorMask( true, true, true, true );
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
else
|
||||
{
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(
|
||||
-MARGIN, MARGIN,
|
||||
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
|
||||
);
|
||||
}
|
||||
|
||||
// Tear down render state for monitors.
|
||||
GlStateManager.depthMask( true );
|
||||
mc.gameRenderer.enableLightmap();
|
||||
GlStateManager.enableLighting();
|
||||
|
||||
// Draw the depth blocker
|
||||
GlStateManager.colorMask( false, false, false, false );
|
||||
FixedWidthFontRenderer.drawBlocker(
|
||||
(float) -TileMonitor.RENDER_MARGIN, (float) TileMonitor.RENDER_MARGIN,
|
||||
(float) (xSize + 2 * TileMonitor.RENDER_MARGIN), (float) -(ySize + TileMonitor.RENDER_MARGIN * 2)
|
||||
);
|
||||
GlStateManager.colorMask( true, true, true, true );
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
private static void renderTerminal( ClientMonitor monitor, float xMargin, float yMargin )
|
||||
{
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
|
||||
boolean redraw = monitor.pollTerminalChanged();
|
||||
|
||||
// Setup the buffers if needed. We get the renderer here, to avoid the (unlikely) race condition between
|
||||
// creating the buffers and rendering.
|
||||
MonitorRenderer renderer = MonitorRenderer.current();
|
||||
if( monitor.createBuffer( renderer ) ) redraw = true;
|
||||
|
||||
FixedWidthFontRenderer.bindFont();
|
||||
|
||||
switch( renderer )
|
||||
{
|
||||
case VBO:
|
||||
{
|
||||
VertexBuffer vbo = monitor.buffer;
|
||||
if( redraw )
|
||||
{
|
||||
renderTerminalTo( monitor, buffer, xMargin, yMargin );
|
||||
buffer.finishDrawing();
|
||||
buffer.reset();
|
||||
vbo.bufferData( buffer.getByteBuffer() );
|
||||
}
|
||||
|
||||
vbo.bindBuffer();
|
||||
setupBufferFormat();
|
||||
vbo.drawArrays( GL11.GL_TRIANGLES );
|
||||
VertexBuffer.unbindBuffer();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't draw the cursor with a buffer, as it's dynamic and so we'll end up refreshing far more than is
|
||||
// reasonable.
|
||||
FixedWidthFontRenderer.begin( buffer );
|
||||
FixedWidthFontRenderer.drawCursor( buffer, 0, 0, monitor.getTerminal(), !monitor.isColour() );
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
private static void renderTerminalTo( ClientMonitor monitor, BufferBuilder buffer, float xMargin, float yMargin )
|
||||
{
|
||||
FixedWidthFontRenderer.begin( buffer );
|
||||
FixedWidthFontRenderer.drawTerminalWithoutCursor(
|
||||
buffer, 0, 0,
|
||||
monitor.getTerminal(), !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
|
||||
);
|
||||
}
|
||||
|
||||
public static void setupBufferFormat()
|
||||
{
|
||||
int stride = FixedWidthFontRenderer.POSITION_COLOR_TEX.getSize();
|
||||
GlStateManager.vertexPointer( 3, GL11.GL_FLOAT, stride, 0 );
|
||||
GlStateManager.enableClientState( GL11.GL_VERTEX_ARRAY );
|
||||
|
||||
GlStateManager.colorPointer( 4, GL11.GL_UNSIGNED_BYTE, stride, 12 );
|
||||
GlStateManager.enableClientState( GL11.GL_COLOR_ARRAY );
|
||||
|
||||
GlStateManager.texCoordPointer( 2, GL11.GL_FLOAT, stride, 16 );
|
||||
GlStateManager.enableClientState( GL11.GL_TEXTURE_COORD_ARRAY );
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,11 @@ import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
|
||||
@ -76,6 +81,8 @@ public class FSAPI implements ILuaAPI
|
||||
"getFreeSpace",
|
||||
"find",
|
||||
"getDir",
|
||||
"getCapacity",
|
||||
"attributes",
|
||||
};
|
||||
}
|
||||
|
||||
@ -315,9 +322,8 @@ public class FSAPI implements ILuaAPI
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 14:
|
||||
case 14: // find
|
||||
{
|
||||
// find
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
@ -329,15 +335,50 @@ public class FSAPI implements ILuaAPI
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 15:
|
||||
case 15: // getDir
|
||||
{
|
||||
// getDir
|
||||
String path = getString( args, 0 );
|
||||
return new Object[] { FileSystem.getDirectory( path ) };
|
||||
}
|
||||
case 16: // getCapacity
|
||||
{
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
OptionalLong capacity = m_fileSystem.getCapacity( path );
|
||||
return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
case 17: // attributes
|
||||
{
|
||||
String path = getString( args, 0 );
|
||||
try
|
||||
{
|
||||
BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
|
||||
result.put( "created", getFileTime( attributes.creationTime() ) );
|
||||
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
|
||||
result.put( "isDir", attributes.isDirectory() );
|
||||
return new Object[] { result };
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new LuaException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
default:
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static long getFileTime( FileTime time )
|
||||
{
|
||||
return time == null ? 0 : time.toMillis();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A Lua exception which does not contain its stack trace.
|
||||
*/
|
||||
public class FastLuaException extends LuaException
|
||||
{
|
||||
private static final long serialVersionUID = 5957864899303561143L;
|
||||
|
||||
public FastLuaException( @Nullable String message )
|
||||
{
|
||||
super( message );
|
||||
}
|
||||
|
||||
public FastLuaException( @Nullable String message, int level )
|
||||
{
|
||||
super( message, level );
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ import javax.annotation.Nullable;
|
||||
|
||||
public interface IAPIEnvironment
|
||||
{
|
||||
String TIMER_EVENT = "timer";
|
||||
|
||||
@FunctionalInterface
|
||||
interface IPeripheralChangeListener
|
||||
{
|
||||
@ -64,6 +66,10 @@ public interface IAPIEnvironment
|
||||
|
||||
void setLabel( @Nullable String label );
|
||||
|
||||
int startTimer( long ticks );
|
||||
|
||||
void cancelTimer( int id );
|
||||
|
||||
void addTrackingChange( @Nonnull TrackingField field, long change );
|
||||
|
||||
default void addTrackingChange( @Nonnull TrackingField field )
|
||||
|
@ -9,6 +9,8 @@ import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.time.Instant;
|
||||
@ -24,24 +26,12 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
private IAPIEnvironment m_apiEnvironment;
|
||||
|
||||
private final Map<Integer, Timer> m_timers;
|
||||
private final Map<Integer, Alarm> m_alarms;
|
||||
private final Int2ObjectMap<Alarm> m_alarms = new Int2ObjectOpenHashMap<>();
|
||||
private int m_clock;
|
||||
private double m_time;
|
||||
private int m_day;
|
||||
|
||||
private int m_nextTimerToken;
|
||||
private int m_nextAlarmToken;
|
||||
|
||||
private static class Timer
|
||||
{
|
||||
int m_ticksLeft;
|
||||
|
||||
Timer( int ticksLeft )
|
||||
{
|
||||
m_ticksLeft = ticksLeft;
|
||||
}
|
||||
}
|
||||
private int m_nextAlarmToken = 0;
|
||||
|
||||
private static class Alarm implements Comparable<Alarm>
|
||||
{
|
||||
@ -66,10 +56,6 @@ public class OSAPI implements ILuaAPI
|
||||
public OSAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_apiEnvironment = environment;
|
||||
m_nextTimerToken = 0;
|
||||
m_nextAlarmToken = 0;
|
||||
m_timers = new HashMap<>();
|
||||
m_alarms = new HashMap<>();
|
||||
}
|
||||
|
||||
// ILuaAPI implementation
|
||||
@ -87,11 +73,6 @@ public class OSAPI implements ILuaAPI
|
||||
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
|
||||
m_clock = 0;
|
||||
|
||||
synchronized( m_timers )
|
||||
{
|
||||
m_timers.clear();
|
||||
}
|
||||
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
m_alarms.clear();
|
||||
@ -101,26 +82,7 @@ public class OSAPI implements ILuaAPI
|
||||
@Override
|
||||
public void update()
|
||||
{
|
||||
synchronized( m_timers )
|
||||
{
|
||||
// Update the clock
|
||||
m_clock++;
|
||||
|
||||
// Countdown all of our active timers
|
||||
Iterator<Map.Entry<Integer, Timer>> it = m_timers.entrySet().iterator();
|
||||
while( it.hasNext() )
|
||||
{
|
||||
Map.Entry<Integer, Timer> entry = it.next();
|
||||
Timer timer = entry.getValue();
|
||||
timer.m_ticksLeft--;
|
||||
if( timer.m_ticksLeft <= 0 )
|
||||
{
|
||||
// Queue the "timer" event
|
||||
queueLuaEvent( "timer", new Object[] { entry.getKey() } );
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
m_clock++;
|
||||
|
||||
// Wait for all of our alarms
|
||||
synchronized( m_alarms )
|
||||
@ -155,11 +117,6 @@ public class OSAPI implements ILuaAPI
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
synchronized( m_timers )
|
||||
{
|
||||
m_timers.clear();
|
||||
}
|
||||
|
||||
synchronized( m_alarms )
|
||||
{
|
||||
m_alarms.clear();
|
||||
@ -229,11 +186,8 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
// startTimer
|
||||
double timer = getFiniteDouble( args, 0 );
|
||||
synchronized( m_timers )
|
||||
{
|
||||
m_timers.put( m_nextTimerToken, new Timer( (int) Math.round( timer / 0.05 ) ) );
|
||||
return new Object[] { m_nextTimerToken++ };
|
||||
}
|
||||
int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) );
|
||||
return new Object[] { id };
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
@ -278,10 +232,7 @@ public class OSAPI implements ILuaAPI
|
||||
return null;
|
||||
}
|
||||
case 10: // clock
|
||||
synchronized( m_timers )
|
||||
{
|
||||
return new Object[] { m_clock * 0.05 };
|
||||
}
|
||||
return new Object[] { m_clock * 0.05 };
|
||||
case 11:
|
||||
{
|
||||
// time
|
||||
@ -345,10 +296,7 @@ public class OSAPI implements ILuaAPI
|
||||
{
|
||||
// cancelTimer
|
||||
int token = getInt( args, 0 );
|
||||
synchronized( m_timers )
|
||||
{
|
||||
m_timers.remove( token );
|
||||
}
|
||||
m_apiEnvironment.cancelTimer( token );
|
||||
return null;
|
||||
}
|
||||
case 14:
|
||||
|
@ -383,22 +383,30 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
String methodName = getString( args, 1 );
|
||||
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
|
||||
|
||||
if( side != null )
|
||||
if( side == null ) throw new LuaException( "No peripheral attached" );
|
||||
|
||||
PeripheralWrapper p;
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
PeripheralWrapper p;
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
p = m_peripherals[side.ordinal()];
|
||||
}
|
||||
if( p != null )
|
||||
{
|
||||
return p.call( context, methodName, methodArgs );
|
||||
}
|
||||
p = m_peripherals[side.ordinal()];
|
||||
}
|
||||
if( p == null ) throw new LuaException( "No peripheral attached" );
|
||||
|
||||
try
|
||||
{
|
||||
return p.call( context, methodName, methodArgs );
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
// We increase the error level by one in order to shift the error level to where peripheral.call was
|
||||
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
|
||||
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
|
||||
throw e;
|
||||
}
|
||||
throw new LuaException( "No peripheral attached" );
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,7 +40,11 @@ import java.util.concurrent.Future;
|
||||
*/
|
||||
public class Websocket extends Resource<Websocket>
|
||||
{
|
||||
public static final int MAX_MESSAGE_SIZE = 64 * 1024;
|
||||
/**
|
||||
* We declare the maximum size to be 2^30 bytes. While messages can be much longer, we set an arbitrary limit as
|
||||
* working with larger messages (especially within a Lua VM) is absurd.
|
||||
*/
|
||||
public static final int MAX_MESSAGE_SIZE = 1 << 30;
|
||||
|
||||
static final String SUCCESS_EVENT = "websocket_success";
|
||||
static final String FAILURE_EVENT = "websocket_failure";
|
||||
|
@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http.websocket;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ArgumentHelper;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
@ -23,6 +24,7 @@ import java.io.Closeable;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
|
||||
import static dan200.computercraft.core.apis.IAPIEnvironment.TIMER_EVENT;
|
||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
|
||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
|
||||
|
||||
@ -53,7 +55,21 @@ public class WebsocketHandle implements ILuaObject, Closeable
|
||||
switch( method )
|
||||
{
|
||||
case 0: // receive
|
||||
{
|
||||
checkOpen();
|
||||
int timeoutId;
|
||||
if( arguments.length <= 0 || arguments[0] == null )
|
||||
{
|
||||
// We do this rather odd argument validation to ensure we can tell the difference between a
|
||||
// negative timeout and an absent one.
|
||||
timeoutId = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
double timeout = ArgumentHelper.getFiniteDouble( arguments, 0 );
|
||||
timeoutId = websocket.environment().startTimer( Math.round( timeout / 0.05 ) );
|
||||
}
|
||||
|
||||
while( true )
|
||||
{
|
||||
Object[] event = context.pullEvent( null );
|
||||
@ -63,9 +79,17 @@ public class WebsocketHandle implements ILuaObject, Closeable
|
||||
}
|
||||
else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
|
||||
{
|
||||
// If the socket is closed abort.
|
||||
return null;
|
||||
}
|
||||
else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
|
||||
&& event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
|
||||
{
|
||||
// If we received a matching timer event then abort.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 1: // send
|
||||
{
|
||||
|
@ -181,7 +181,7 @@ public class Computer
|
||||
executor.tick();
|
||||
|
||||
// Update the environment's internal state.
|
||||
internalEnvironment.update();
|
||||
internalEnvironment.tick();
|
||||
|
||||
// Propagate the environment's output to the world.
|
||||
if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true );
|
||||
|
@ -426,6 +426,7 @@ final class ComputerExecutor
|
||||
}
|
||||
|
||||
// Init APIs
|
||||
computer.getEnvironment().reset();
|
||||
for( ILuaAPI api : apis ) api.startup();
|
||||
|
||||
// Init lua
|
||||
@ -469,6 +470,7 @@ final class ComputerExecutor
|
||||
|
||||
// Shutdown our APIs
|
||||
for( ILuaAPI api : apis ) api.shutdown();
|
||||
computer.getEnvironment().reset();
|
||||
|
||||
// Unload filesystem
|
||||
if( fileSystem != null )
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IWorkMonitor;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
@ -12,9 +13,12 @@ import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Represents the "environment" that a {@link Computer} exists in.
|
||||
@ -53,6 +57,9 @@ public final class Environment implements IAPIEnvironment
|
||||
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
|
||||
private IPeripheralChangeListener peripheralListener = null;
|
||||
|
||||
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
|
||||
private int nextTimerToken = 0;
|
||||
|
||||
Environment( Computer computer )
|
||||
{
|
||||
this.computer = computer;
|
||||
@ -198,17 +205,47 @@ public final class Environment implements IAPIEnvironment
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the main thread to update the internal state of the computer.
|
||||
* Called when the computer starts up or shuts down, to reset any internal state.
|
||||
*
|
||||
* This just queues a {@code redstone} event if the input has changed.
|
||||
* @see ILuaAPI#startup()
|
||||
* @see ILuaAPI#shutdown()
|
||||
*/
|
||||
void update()
|
||||
void reset()
|
||||
{
|
||||
synchronized( timers )
|
||||
{
|
||||
timers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the main thread to update the internal state of the computer.
|
||||
*/
|
||||
void tick()
|
||||
{
|
||||
if( inputChanged )
|
||||
{
|
||||
inputChanged = false;
|
||||
queueEvent( "redstone", null );
|
||||
}
|
||||
|
||||
synchronized( timers )
|
||||
{
|
||||
// Countdown all of our active timers
|
||||
Iterator<Int2ObjectMap.Entry<Timer>> it = timers.int2ObjectEntrySet().iterator();
|
||||
while( it.hasNext() )
|
||||
{
|
||||
Int2ObjectMap.Entry<Timer> entry = it.next();
|
||||
Timer timer = entry.getValue();
|
||||
timer.ticksLeft--;
|
||||
if( timer.ticksLeft <= 0 )
|
||||
{
|
||||
// Queue the "timer" event
|
||||
queueEvent( TIMER_EVENT, new Object[] { entry.getIntKey() } );
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,9 +340,38 @@ public final class Environment implements IAPIEnvironment
|
||||
computer.setLabel( label );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int startTimer( long ticks )
|
||||
{
|
||||
synchronized( timers )
|
||||
{
|
||||
timers.put( nextTimerToken, new Timer( ticks ) );
|
||||
return nextTimerToken++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelTimer( int id )
|
||||
{
|
||||
synchronized( timers )
|
||||
{
|
||||
timers.remove( id );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTrackingChange( @Nonnull TrackingField field, long change )
|
||||
{
|
||||
Tracking.addValue( computer, field, change );
|
||||
}
|
||||
|
||||
private static class Timer
|
||||
{
|
||||
long ticksLeft;
|
||||
|
||||
Timer( long ticksLeft )
|
||||
{
|
||||
this.ticksLeft = ticksLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -143,4 +144,19 @@ public class ComboMount implements IMount
|
||||
}
|
||||
throw new FileOperationException( path, "No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
|
||||
{
|
||||
for( int i = m_parts.length - 1; i >= 0; --i )
|
||||
{
|
||||
IMount part = m_parts[i];
|
||||
if( part.exists( path ) && !part.isDirectory( path ) )
|
||||
{
|
||||
return part.getAttributes( path );
|
||||
}
|
||||
}
|
||||
throw new FileOperationException( path, "No such file" );
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.filesystem.FileOperationException;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
@ -13,11 +14,11 @@ import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Set;
|
||||
|
||||
public class FileMount implements IWritableMount
|
||||
@ -224,6 +225,19 @@ public class FileMount implements IWritableMount
|
||||
throw new FileOperationException( path, "No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( created() )
|
||||
{
|
||||
File file = getRealPath( path );
|
||||
if( file.exists() ) return Files.readAttributes( file.toPath(), BasicFileAttributes.class );
|
||||
}
|
||||
|
||||
throw new FileOperationException( path, "No such file" );
|
||||
}
|
||||
|
||||
// IWritableMount implementation
|
||||
|
||||
@Override
|
||||
@ -360,6 +374,13 @@ public class FileMount implements IWritableMount
|
||||
return Math.max( m_capacity - m_usedSpace, 0 );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OptionalLong getCapacity()
|
||||
{
|
||||
return OptionalLong.of( m_capacity - MINIMUM_FILE_SIZE );
|
||||
}
|
||||
|
||||
private File getRealPath( String path )
|
||||
{
|
||||
return new File( m_rootPath, path );
|
||||
@ -382,23 +403,46 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
|
||||
private static class Visitor extends SimpleFileVisitor<Path>
|
||||
{
|
||||
long size;
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory( Path dir, BasicFileAttributes attrs )
|
||||
{
|
||||
size += MINIMUM_FILE_SIZE;
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
|
||||
{
|
||||
size += Math.max( attrs.size(), MINIMUM_FILE_SIZE );
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed( Path file, IOException exc )
|
||||
{
|
||||
ComputerCraft.log.error( "Error computing file size for {}", file, exc );
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static long measureUsedSpace( File file )
|
||||
{
|
||||
if( !file.exists() ) return 0;
|
||||
|
||||
if( file.isDirectory() )
|
||||
try
|
||||
{
|
||||
long size = MINIMUM_FILE_SIZE;
|
||||
String[] contents = file.list();
|
||||
for( String content : contents )
|
||||
{
|
||||
size += measureUsedSpace( new File( file, content ) );
|
||||
}
|
||||
return size;
|
||||
Visitor visitor = new Visitor();
|
||||
Files.walkFileTree( file.toPath(), visitor );
|
||||
return visitor.size;
|
||||
}
|
||||
else
|
||||
catch( IOException e )
|
||||
{
|
||||
return Math.max( file.length(), MINIMUM_FILE_SIZE );
|
||||
ComputerCraft.log.error( "Error computing file size for {}", file, e );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.filesystem.FileOperationException;
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
@ -23,309 +22,23 @@ import java.nio.channels.Channel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FileSystem
|
||||
{
|
||||
private static class MountWrapper
|
||||
{
|
||||
private String m_label;
|
||||
private String m_location;
|
||||
|
||||
private IMount m_mount;
|
||||
private IWritableMount m_writableMount;
|
||||
|
||||
MountWrapper( String label, String location, IMount mount )
|
||||
{
|
||||
m_label = label;
|
||||
m_location = location;
|
||||
m_mount = mount;
|
||||
m_writableMount = null;
|
||||
}
|
||||
|
||||
MountWrapper( String label, String location, IWritableMount mount )
|
||||
{
|
||||
this( label, location, (IMount) mount );
|
||||
m_writableMount = mount;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return m_label;
|
||||
}
|
||||
|
||||
public String getLocation()
|
||||
{
|
||||
return m_location;
|
||||
}
|
||||
|
||||
public long getFreeSpace()
|
||||
{
|
||||
if( m_writableMount == null )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return m_writableMount.getRemainingSpace();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReadOnly( String path )
|
||||
{
|
||||
return m_writableMount == null;
|
||||
}
|
||||
|
||||
// IMount forwarders:
|
||||
|
||||
public boolean exists( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
return m_mount.exists( path );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDirectory( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
return m_mount.exists( path ) && m_mount.isDirectory( path );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public void list( String path, List<String> contents ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
|
||||
{
|
||||
m_mount.list( path, contents );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw localExceptionOf( path, "Not a directory" );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) )
|
||||
{
|
||||
if( m_mount.isDirectory( path ) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_mount.getSize( path );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw localExceptionOf( path, "No such file" );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public ReadableByteChannel openForRead( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
|
||||
{
|
||||
return m_mount.openChannelForRead( path );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw localExceptionOf( path, "No such file" );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
// IWritableMount forwarders:
|
||||
|
||||
public void makeDirectory( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) )
|
||||
{
|
||||
if( !m_mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_writableMount.makeDirectory( path );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public void delete( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
try
|
||||
{
|
||||
path = toLocal( path );
|
||||
if( m_mount.exists( path ) )
|
||||
{
|
||||
m_writableMount.delete( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public WritableByteChannel openForWrite( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
|
||||
{
|
||||
throw localExceptionOf( path, "Cannot write to directory" );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( !path.isEmpty() )
|
||||
{
|
||||
String dir = getDirectory( path );
|
||||
if( !dir.isEmpty() && !m_mount.exists( path ) )
|
||||
{
|
||||
m_writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return m_writableMount.openChannelForWrite( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public WritableByteChannel openForAppend( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( !m_mount.exists( path ) )
|
||||
{
|
||||
if( !path.isEmpty() )
|
||||
{
|
||||
String dir = getDirectory( path );
|
||||
if( !dir.isEmpty() && !m_mount.exists( path ) )
|
||||
{
|
||||
m_writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return m_writableMount.openChannelForWrite( path );
|
||||
}
|
||||
else if( m_mount.isDirectory( path ) )
|
||||
{
|
||||
throw localExceptionOf( path, "Cannot write to directory" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_writableMount.openChannelForAppend( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
private String toLocal( String path )
|
||||
{
|
||||
return FileSystem.toLocal( path, m_location );
|
||||
}
|
||||
|
||||
private FileSystemException localExceptionOf( IOException e )
|
||||
{
|
||||
if( !m_location.isEmpty() && e instanceof FileOperationException )
|
||||
{
|
||||
FileOperationException ex = (FileOperationException) e;
|
||||
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
|
||||
}
|
||||
|
||||
return new FileSystemException( e.getMessage() );
|
||||
}
|
||||
|
||||
private FileSystemException localExceptionOf( String path, String message )
|
||||
{
|
||||
if( !m_location.isEmpty() ) path = path.isEmpty() ? m_location : m_location + "/" + path;
|
||||
return exceptionOf( path, message );
|
||||
}
|
||||
|
||||
private static FileSystemException exceptionOf( String path, String message )
|
||||
{
|
||||
return new FileSystemException( "/" + path + ": " + message );
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Maximum depth that {@link #copyRecursive(String, MountWrapper, String, MountWrapper, int)} will descend into.
|
||||
*
|
||||
* This is a pretty arbitrary value, though hopefully it is large enough that it'll never be normally hit. This
|
||||
* exists to prevent it overflowing if it ever gets into an infinite loop.
|
||||
*/
|
||||
private static final int MAX_COPY_DEPTH = 128;
|
||||
|
||||
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
|
||||
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
|
||||
private final Map<String, MountWrapper> mounts = new HashMap<>();
|
||||
|
||||
private final HashMap<WeakReference<FileSystemWrapper<?>>, ChannelWrapper<?>> m_openFiles = new HashMap<>();
|
||||
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
|
||||
@ -355,10 +68,7 @@ public class FileSystem
|
||||
{
|
||||
if( mount == null ) throw new NullPointerException();
|
||||
location = sanitizePath( location );
|
||||
if( location.contains( ".." ) )
|
||||
{
|
||||
throw new FileSystemException( "Cannot mount below the root" );
|
||||
}
|
||||
if( location.contains( ".." ) ) throw new FileSystemException( "Cannot mount below the root" );
|
||||
mount( new MountWrapper( label, location, mount ) );
|
||||
}
|
||||
|
||||
@ -379,14 +89,13 @@ public class FileSystem
|
||||
private synchronized void mount( MountWrapper wrapper )
|
||||
{
|
||||
String location = wrapper.getLocation();
|
||||
m_mounts.remove( location );
|
||||
m_mounts.put( location, wrapper );
|
||||
mounts.remove( location );
|
||||
mounts.put( location, wrapper );
|
||||
}
|
||||
|
||||
public synchronized void unmount( String path )
|
||||
{
|
||||
path = sanitizePath( path );
|
||||
m_mounts.remove( path );
|
||||
mounts.remove( sanitizePath( path ) );
|
||||
}
|
||||
|
||||
public synchronized String combine( String path, String childPath )
|
||||
@ -430,27 +139,20 @@ public class FileSystem
|
||||
public static String getName( String path )
|
||||
{
|
||||
path = sanitizePath( path, true );
|
||||
if( path.isEmpty() )
|
||||
{
|
||||
return "root";
|
||||
}
|
||||
if( path.isEmpty() ) return "root";
|
||||
|
||||
int lastSlash = path.lastIndexOf( '/' );
|
||||
if( lastSlash >= 0 )
|
||||
{
|
||||
return path.substring( lastSlash + 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
return path;
|
||||
}
|
||||
return lastSlash >= 0 ? path.substring( lastSlash + 1 ) : path;
|
||||
}
|
||||
|
||||
public synchronized long getSize( String path ) throws FileSystemException
|
||||
{
|
||||
path = sanitizePath( path );
|
||||
MountWrapper mount = getMount( path );
|
||||
return mount.getSize( path );
|
||||
return getMount( sanitizePath( path ) ).getSize( sanitizePath( path ) );
|
||||
}
|
||||
|
||||
public synchronized BasicFileAttributes getAttributes( String path ) throws FileSystemException
|
||||
{
|
||||
return getMount( sanitizePath( path ) ).getAttributes( sanitizePath( path ) );
|
||||
}
|
||||
|
||||
public synchronized String[] list( String path ) throws FileSystemException
|
||||
@ -463,7 +165,7 @@ public class FileSystem
|
||||
mount.list( path, list );
|
||||
|
||||
// Add any mounts that are mounted at this location
|
||||
for( MountWrapper otherMount : m_mounts.values() )
|
||||
for( MountWrapper otherMount : mounts.values() )
|
||||
{
|
||||
if( getDirectory( otherMount.getLocation() ).equals( path ) )
|
||||
{
|
||||
@ -611,15 +313,13 @@ public class FileSystem
|
||||
{
|
||||
throw new FileSystemException( "/" + sourcePath + ": Can't copy a directory inside itself" );
|
||||
}
|
||||
copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ) );
|
||||
copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ), 0 );
|
||||
}
|
||||
|
||||
private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount ) throws FileSystemException
|
||||
private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount, int depth ) throws FileSystemException
|
||||
{
|
||||
if( !sourceMount.exists( sourcePath ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if( !sourceMount.exists( sourcePath ) ) return;
|
||||
if( depth >= MAX_COPY_DEPTH ) throw new FileSystemException( "Too many directories to copy" );
|
||||
|
||||
if( sourceMount.isDirectory( sourcePath ) )
|
||||
{
|
||||
@ -634,7 +334,8 @@ public class FileSystem
|
||||
{
|
||||
copyRecursive(
|
||||
combine( sourcePath, child ), sourceMount,
|
||||
combine( destinationPath, child ), destinationMount
|
||||
combine( destinationPath, child ), destinationMount,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -726,17 +427,25 @@ public class FileSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getFreeSpace( String path ) throws FileSystemException
|
||||
public synchronized long getFreeSpace( String path ) throws FileSystemException
|
||||
{
|
||||
path = sanitizePath( path );
|
||||
MountWrapper mount = getMount( path );
|
||||
return mount.getFreeSpace();
|
||||
}
|
||||
|
||||
private MountWrapper getMount( String path ) throws FileSystemException
|
||||
@Nonnull
|
||||
public synchronized OptionalLong getCapacity( String path ) throws FileSystemException
|
||||
{
|
||||
path = sanitizePath( path );
|
||||
MountWrapper mount = getMount( path );
|
||||
return mount.getCapacity();
|
||||
}
|
||||
|
||||
private synchronized MountWrapper getMount( String path ) throws FileSystemException
|
||||
{
|
||||
// Return the deepest mount that contains a given path
|
||||
Iterator<MountWrapper> it = m_mounts.values().iterator();
|
||||
Iterator<MountWrapper> it = mounts.values().iterator();
|
||||
MountWrapper match = null;
|
||||
int matchLength = 999;
|
||||
while( it.hasNext() )
|
||||
@ -854,8 +563,8 @@ public class FileSystem
|
||||
|
||||
public static boolean contains( String pathA, String pathB )
|
||||
{
|
||||
pathA = sanitizePath( pathA );
|
||||
pathB = sanitizePath( pathB );
|
||||
pathA = sanitizePath( pathA ).toLowerCase( Locale.ROOT );
|
||||
pathB = sanitizePath( pathB ).toLowerCase( Locale.ROOT );
|
||||
|
||||
if( pathB.equals( ".." ) )
|
||||
{
|
||||
|
@ -23,6 +23,9 @@ import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -221,6 +224,20 @@ public class JarMount implements IMount
|
||||
throw new FileOperationException( path, "No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
|
||||
{
|
||||
FileEntry file = get( path );
|
||||
if( file != null )
|
||||
{
|
||||
ZipEntry entry = zip.getEntry( file.path );
|
||||
if( entry != null ) return new ZipEntryAttributes( entry );
|
||||
}
|
||||
|
||||
throw new FileOperationException( path, "No such file" );
|
||||
}
|
||||
|
||||
private static class FileEntry
|
||||
{
|
||||
String path;
|
||||
@ -261,4 +278,76 @@ public class JarMount implements IMount
|
||||
Reference<? extends JarMount> next;
|
||||
while( (next = MOUNT_QUEUE.poll()) != null ) IoUtil.closeQuietly( ((MountReference) next).file );
|
||||
}
|
||||
|
||||
private static class ZipEntryAttributes implements BasicFileAttributes
|
||||
{
|
||||
private final ZipEntry entry;
|
||||
|
||||
ZipEntryAttributes( ZipEntry entry )
|
||||
{
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime lastModifiedTime()
|
||||
{
|
||||
return orEpoch( entry.getLastModifiedTime() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime lastAccessTime()
|
||||
{
|
||||
return orEpoch( entry.getLastAccessTime() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime creationTime()
|
||||
{
|
||||
FileTime time = entry.getCreationTime();
|
||||
return time == null ? lastModifiedTime() : time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegularFile()
|
||||
{
|
||||
return !entry.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory()
|
||||
{
|
||||
return entry.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSymbolicLink()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOther()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size()
|
||||
{
|
||||
return entry.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fileKey()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
|
||||
|
||||
private static FileTime orEpoch( FileTime time )
|
||||
{
|
||||
return time == null ? EPOCH : time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.api.filesystem.FileOperationException;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
class MountWrapper
|
||||
{
|
||||
private String label;
|
||||
private String location;
|
||||
|
||||
private IMount mount;
|
||||
private IWritableMount writableMount;
|
||||
|
||||
MountWrapper( String label, String location, IMount mount )
|
||||
{
|
||||
this.label = label;
|
||||
this.location = location;
|
||||
this.mount = mount;
|
||||
writableMount = null;
|
||||
}
|
||||
|
||||
MountWrapper( String label, String location, IWritableMount mount )
|
||||
{
|
||||
this( label, location, (IMount) mount );
|
||||
writableMount = mount;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getLocation()
|
||||
{
|
||||
return location;
|
||||
}
|
||||
|
||||
public long getFreeSpace()
|
||||
{
|
||||
if( writableMount == null ) return 0;
|
||||
|
||||
try
|
||||
{
|
||||
return writableMount.getRemainingSpace();
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public OptionalLong getCapacity()
|
||||
{
|
||||
return writableMount == null ? OptionalLong.empty() : writableMount.getCapacity();
|
||||
}
|
||||
|
||||
public boolean isReadOnly( String path )
|
||||
{
|
||||
return writableMount == null;
|
||||
}
|
||||
|
||||
public boolean exists( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
return mount.exists( path );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDirectory( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
return mount.exists( path ) && mount.isDirectory( path );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public void list( String path, List<String> contents ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( !mount.exists( path ) || !mount.isDirectory( path ) )
|
||||
{
|
||||
throw localExceptionOf( path, "Not a directory" );
|
||||
}
|
||||
|
||||
mount.list( path, contents );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public long getSize( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
|
||||
return mount.isDirectory( path ) ? 0 : mount.getSize( path );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public BasicFileAttributes getAttributes( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
|
||||
return mount.getAttributes( path );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public ReadableByteChannel openForRead( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( mount.exists( path ) && !mount.isDirectory( path ) )
|
||||
{
|
||||
return mount.openChannelForRead( path );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw localExceptionOf( path, "No such file" );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public void makeDirectory( String path ) throws FileSystemException
|
||||
{
|
||||
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( mount.exists( path ) )
|
||||
{
|
||||
if( !mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
|
||||
}
|
||||
else
|
||||
{
|
||||
writableMount.makeDirectory( path );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public void delete( String path ) throws FileSystemException
|
||||
{
|
||||
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
try
|
||||
{
|
||||
path = toLocal( path );
|
||||
if( mount.exists( path ) )
|
||||
{
|
||||
writableMount.delete( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public WritableByteChannel openForWrite( String path ) throws FileSystemException
|
||||
{
|
||||
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( mount.exists( path ) && mount.isDirectory( path ) )
|
||||
{
|
||||
throw localExceptionOf( path, "Cannot write to directory" );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( !path.isEmpty() )
|
||||
{
|
||||
String dir = FileSystem.getDirectory( path );
|
||||
if( !dir.isEmpty() && !mount.exists( path ) )
|
||||
{
|
||||
writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return writableMount.openChannelForWrite( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
public WritableByteChannel openForAppend( String path ) throws FileSystemException
|
||||
{
|
||||
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
|
||||
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( !mount.exists( path ) )
|
||||
{
|
||||
if( !path.isEmpty() )
|
||||
{
|
||||
String dir = FileSystem.getDirectory( path );
|
||||
if( !dir.isEmpty() && !mount.exists( path ) )
|
||||
{
|
||||
writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return writableMount.openChannelForWrite( path );
|
||||
}
|
||||
else if( mount.isDirectory( path ) )
|
||||
{
|
||||
throw localExceptionOf( path, "Cannot write to directory" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return writableMount.openChannelForAppend( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw localExceptionOf( e );
|
||||
}
|
||||
}
|
||||
|
||||
private String toLocal( String path )
|
||||
{
|
||||
return FileSystem.toLocal( path, location );
|
||||
}
|
||||
|
||||
private FileSystemException localExceptionOf( IOException e )
|
||||
{
|
||||
if( !location.isEmpty() && e instanceof FileOperationException )
|
||||
{
|
||||
FileOperationException ex = (FileOperationException) e;
|
||||
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
|
||||
}
|
||||
|
||||
return new FileSystemException( e.getMessage() );
|
||||
}
|
||||
|
||||
private FileSystemException localExceptionOf( String path, String message )
|
||||
{
|
||||
if( !location.isEmpty() ) path = path.isEmpty() ? location : location + "/" + path;
|
||||
return exceptionOf( path, message );
|
||||
}
|
||||
|
||||
private static FileSystemException exceptionOf( String path, String message )
|
||||
{
|
||||
return new FileSystemException( "/" + path + ": " + message );
|
||||
}
|
||||
}
|
@ -11,43 +11,42 @@ import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.List;
|
||||
|
||||
public class SubMount implements IMount
|
||||
{
|
||||
private IMount m_parent;
|
||||
private String m_subPath;
|
||||
private IMount parent;
|
||||
private String subPath;
|
||||
|
||||
public SubMount( IMount parent, String subPath )
|
||||
{
|
||||
m_parent = parent;
|
||||
m_subPath = subPath;
|
||||
this.parent = parent;
|
||||
this.subPath = subPath;
|
||||
}
|
||||
|
||||
// IMount implementation
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.exists( getFullPath( path ) );
|
||||
return parent.exists( getFullPath( path ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.isDirectory( getFullPath( path ) );
|
||||
return parent.isDirectory( getFullPath( path ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
m_parent.list( getFullPath( path ), contents );
|
||||
parent.list( getFullPath( path ), contents );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.getSize( getFullPath( path ) );
|
||||
return parent.getSize( getFullPath( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@ -55,25 +54,25 @@ public class SubMount implements IMount
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.openForRead( getFullPath( path ) );
|
||||
return parent.openForRead( getFullPath( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.openChannelForRead( getFullPath( path ) );
|
||||
return parent.openChannelForRead( getFullPath( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return parent.getAttributes( getFullPath( path ) );
|
||||
}
|
||||
|
||||
private String getFullPath( String path )
|
||||
{
|
||||
if( path.isEmpty() )
|
||||
{
|
||||
return m_subPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_subPath + "/" + path;
|
||||
}
|
||||
return path.isEmpty() ? subPath : subPath + "/" + path;
|
||||
}
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
m_globals.load( state, new MathLib() );
|
||||
m_globals.load( state, new CoroutineLib() );
|
||||
m_globals.load( state, new Bit32Lib() );
|
||||
m_globals.load( state, new Utf8Lib() );
|
||||
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
|
||||
|
||||
// Remove globals we don't want to expose
|
||||
|
@ -5,8 +5,9 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.monitor;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.common.ClientTerminal;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
@ -15,7 +16,7 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
public class ClientMonitor extends ClientTerminal
|
||||
public final class ClientMonitor extends ClientTerminal
|
||||
{
|
||||
private static final Set<ClientMonitor> allMonitors = new HashSet<>();
|
||||
|
||||
@ -23,7 +24,8 @@ public class ClientMonitor extends ClientTerminal
|
||||
|
||||
public long lastRenderFrame = -1;
|
||||
public BlockPos lastRenderPos = null;
|
||||
public int[] renderDisplayLists = null;
|
||||
|
||||
public VertexBuffer buffer;
|
||||
|
||||
public ClientMonitor( boolean colour, TileMonitor origin )
|
||||
{
|
||||
@ -36,41 +38,59 @@ public class ClientMonitor extends ClientTerminal
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the appropriate buffer if needed.
|
||||
*
|
||||
* @param renderer The renderer to use. This can be fetched from {@link MonitorRenderer#current()}.
|
||||
* @return If a buffer was created. This will return {@code false} if we already have an appropriate buffer,
|
||||
* or this mode does not require one.
|
||||
*/
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void createLists()
|
||||
public boolean createBuffer( MonitorRenderer renderer )
|
||||
{
|
||||
if( renderDisplayLists == null )
|
||||
switch( renderer )
|
||||
{
|
||||
renderDisplayLists = new int[3];
|
||||
case VBO:
|
||||
if( buffer != null ) return false;
|
||||
|
||||
for( int i = 0; i < renderDisplayLists.length; i++ )
|
||||
{
|
||||
renderDisplayLists[i] = GlStateManager.genLists( 1 );
|
||||
}
|
||||
deleteBuffers();
|
||||
buffer = new VertexBuffer( FixedWidthFontRenderer.POSITION_COLOR_TEX );
|
||||
addMonitor();
|
||||
return true;
|
||||
|
||||
synchronized( allMonitors )
|
||||
{
|
||||
allMonitors.add( this );
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void addMonitor()
|
||||
{
|
||||
synchronized( allMonitors )
|
||||
{
|
||||
allMonitors.add( this );
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteBuffers()
|
||||
{
|
||||
if( buffer != null )
|
||||
{
|
||||
buffer.deleteGlBuffers();
|
||||
buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void destroy()
|
||||
{
|
||||
if( renderDisplayLists != null )
|
||||
if( buffer != null )
|
||||
{
|
||||
synchronized( allMonitors )
|
||||
{
|
||||
allMonitors.remove( this );
|
||||
}
|
||||
|
||||
for( int list : renderDisplayLists )
|
||||
{
|
||||
GlStateManager.deleteLists( list, 1 );
|
||||
}
|
||||
|
||||
renderDisplayLists = null;
|
||||
deleteBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,14 +102,7 @@ public class ClientMonitor extends ClientTerminal
|
||||
for( Iterator<ClientMonitor> iterator = allMonitors.iterator(); iterator.hasNext(); )
|
||||
{
|
||||
ClientMonitor monitor = iterator.next();
|
||||
if( monitor.renderDisplayLists != null )
|
||||
{
|
||||
for( int list : monitor.renderDisplayLists )
|
||||
{
|
||||
GlStateManager.deleteLists( list, 1 );
|
||||
}
|
||||
monitor.renderDisplayLists = null;
|
||||
}
|
||||
monitor.deleteBuffers();
|
||||
|
||||
iterator.remove();
|
||||
}
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.peripheral.monitor;
|
||||
|
||||
import com.mojang.blaze3d.platform.GLX;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The render type to use for monitors.
|
||||
*
|
||||
* @see TileEntityMonitorRenderer
|
||||
* @see ClientMonitor
|
||||
*/
|
||||
public enum MonitorRenderer
|
||||
{
|
||||
/**
|
||||
* Determine the best monitor backend.
|
||||
*/
|
||||
BEST,
|
||||
|
||||
/**
|
||||
* Render using VBOs.
|
||||
*
|
||||
* @see net.minecraft.client.renderer.vertex.VertexBuffer
|
||||
*/
|
||||
VBO;
|
||||
|
||||
private static final MonitorRenderer[] VALUES = values();
|
||||
public static final String[] NAMES;
|
||||
|
||||
private final String displayName = "gui.computercraft:config.peripheral.monitor_renderer." + name().toLowerCase( Locale.ROOT );
|
||||
|
||||
static
|
||||
{
|
||||
NAMES = new String[VALUES.length];
|
||||
for( int i = 0; i < VALUES.length; i++ ) NAMES[i] = VALUES[i].displayName();
|
||||
}
|
||||
|
||||
public String displayName()
|
||||
{
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static MonitorRenderer ofString( String name )
|
||||
{
|
||||
for( MonitorRenderer backend : VALUES )
|
||||
{
|
||||
if( backend.displayName.equalsIgnoreCase( name ) || backend.name().equalsIgnoreCase( name ) )
|
||||
{
|
||||
return backend;
|
||||
}
|
||||
}
|
||||
|
||||
ComputerCraft.log.warn( "Unknown monitor renderer {}. Falling back to default.", name );
|
||||
return BEST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current renderer to use.
|
||||
*
|
||||
* @return The current renderer. Will not return {@link MonitorRenderer#BEST}.
|
||||
*/
|
||||
@Nonnull
|
||||
public static MonitorRenderer current()
|
||||
{
|
||||
MonitorRenderer current = ComputerCraft.monitorRenderer;
|
||||
switch( current )
|
||||
{
|
||||
case BEST:
|
||||
return best();
|
||||
case VBO:
|
||||
if( !GLX.useVbo() )
|
||||
{
|
||||
ComputerCraft.log.warn( "VBOs are not supported on your graphics card. Falling back to default." );
|
||||
ComputerCraft.monitorRenderer = BEST;
|
||||
return best();
|
||||
}
|
||||
|
||||
return VBO;
|
||||
default:
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
private static MonitorRenderer best()
|
||||
{
|
||||
return VBO;
|
||||
}
|
||||
}
|
@ -48,13 +48,13 @@ public class Palette
|
||||
{
|
||||
if( i >= 0 && i < colours.length )
|
||||
{
|
||||
setColour( i, Colour.values()[i] );
|
||||
setColour( i, Colour.VALUES[i] );
|
||||
}
|
||||
}
|
||||
|
||||
public void resetColours()
|
||||
{
|
||||
for( int i = 0; i < Colour.values().length; i++ )
|
||||
for( int i = 0; i < Colour.VALUES.length; i++ )
|
||||
{
|
||||
resetColour( i );
|
||||
}
|
||||
|
48
src/main/resources/assets/computercraft/lang/da_dk.lang
Normal file
48
src/main/resources/assets/computercraft/lang/da_dk.lang
Normal file
@ -0,0 +1,48 @@
|
||||
tile.computercraft:computer.name=Computer
|
||||
tile.computercraft:advanced_computer.name=Avanceret Computer
|
||||
tile.computercraft:drive.name=Diskdrev
|
||||
tile.computercraft:printer.name=Printer
|
||||
tile.computercraft:monitor.name=Skærm
|
||||
tile.computercraft:advanced_monitor.name=Avanceret Skærm
|
||||
tile.computercraft:wireless_modem.name=Trådløst Modem
|
||||
tile.computercraft:wired_modem.name=Kablet Modem
|
||||
tile.computercraft:cable.name=Netværkskabel
|
||||
tile.computercraft:command_computer.name=Kommandocomputer
|
||||
tile.computercraft:advanced_modem.name=Endermodem
|
||||
tile.computercraft:speaker.name=Højttaler
|
||||
|
||||
tile.computercraft:turtle.name=Turtle
|
||||
tile.computercraft:turtle.upgraded.name=%s Turtle
|
||||
tile.computercraft:turtle.upgraded_twice.name=%s %s Turtle
|
||||
tile.computercraft:advanced_turtle.name=Avanceret Turtle
|
||||
tile.computercraft:advanced_turtle.upgraded.name=Avanceret %s Turtle
|
||||
tile.computercraft:advanced_turtle.upgraded_twice.name=Avanceret %s %s Turtle
|
||||
|
||||
item.computercraft:disk.name=Floppydisk
|
||||
item.computercraft:treasure_disk.name=Floppydisk
|
||||
item.computercraft:page.name=Printet Side
|
||||
item.computercraft:pages.name=Printede Sider
|
||||
item.computercraft:book.name=Printet Bog
|
||||
|
||||
item.computercraft:pocket_computer.name=Lommecomputer
|
||||
item.computercraft:pocket_computer.upgraded.name=%s Lommecomputer
|
||||
item.computercraft:advanced_pocket_computer.name=Avanceret Lommecomputer
|
||||
item.computercraft:advanced_pocket_computer.upgraded.name=Avanceret %s Lommecomputer
|
||||
|
||||
upgrade.minecraft:diamond_sword.adjective=Kæmpende
|
||||
upgrade.minecraft:diamond_shovel.adjective=Gravende
|
||||
upgrade.minecraft:diamond_pickaxe.adjective=Brydende
|
||||
upgrade.minecraft:diamond_axe.adjective=Fældende
|
||||
upgrade.minecraft:diamond_hoe.adjective=Dyrkende
|
||||
upgrade.computercraft:wireless_modem.adjective=Trådløs
|
||||
upgrade.minecraft:crafting_table.adjective=Fremstillende
|
||||
upgrade.computercraft:advanced_modem.adjective=Endertrådløs
|
||||
upgrade.computercraft:speaker.adjective=Larmende
|
||||
|
||||
chat.computercraft.wired_modem.peripheral_connected=Perifer enhed "%s" koblet til netværk
|
||||
chat.computercraft.wired_modem.peripheral_disconnected=Perifer enhed "%s" koblet fra netværk
|
||||
|
||||
# Misc tooltips
|
||||
gui.computercraft.tooltip.copy=Kopier til udklipsholder
|
||||
gui.computercraft.tooltip.computer_id=(Computer-ID: %s)
|
||||
gui.computercraft.tooltip.disk_id=(Disk-ID: %s)
|
195
src/main/resources/assets/computercraft/lang/ko_kr.lang
Normal file
195
src/main/resources/assets/computercraft/lang/ko_kr.lang
Normal file
@ -0,0 +1,195 @@
|
||||
itemGroup.computercraft=컴퓨터크래프트
|
||||
|
||||
tile.computercraft:computer.name=컴퓨터
|
||||
tile.computercraft:advanced_computer.name=고급 컴퓨터
|
||||
tile.computercraft:drive.name=디스크 드라이브
|
||||
tile.computercraft:printer.name=프린터
|
||||
tile.computercraft:monitor.name=모니터
|
||||
tile.computercraft:advanced_monitor.name=고급 모니터
|
||||
tile.computercraft:wireless_modem.name=무선 모뎀
|
||||
tile.computercraft:wired_modem.name=유선 모뎀
|
||||
tile.computercraft:cable.name=네트워크 케이블
|
||||
tile.computercraft:command_computer.name=명령 컴퓨터
|
||||
tile.computercraft:advanced_modem.name=엔더 모뎀
|
||||
tile.computercraft:speaker.name=스피커
|
||||
|
||||
tile.computercraft:turtle.name=터틀
|
||||
tile.computercraft:turtle.upgraded.name=%s 터틀
|
||||
tile.computercraft:turtle.upgraded_twice.name=%s %s 터틀
|
||||
tile.computercraft:advanced_turtle.name=고급 터틀
|
||||
tile.computercraft:advanced_turtle.upgraded.name=고급 %s 터틀
|
||||
tile.computercraft:advanced_turtle.upgraded_twice.name=고급 %s %s 터틀
|
||||
|
||||
item.computercraft:disk.name=플로피 디스크
|
||||
item.computercraft:treasure_disk.name=플로피 디스크
|
||||
item.computercraft:page.name=인쇄된 페이지
|
||||
item.computercraft:pages.name=인쇄된 페이지 모음
|
||||
item.computercraft:book.name=인쇄된 책
|
||||
|
||||
item.computercraft:pocket_computer.name=포켓 컴퓨터
|
||||
item.computercraft:pocket_computer.upgraded.name=%s 포켓 컴퓨터
|
||||
item.computercraft:advanced_pocket_computer.name=고급 포켓 컴퓨터
|
||||
item.computercraft:advanced_pocket_computer.upgraded.name=고급 %s 포켓 컴퓨터
|
||||
|
||||
upgrade.minecraft:diamond_sword.adjective=난투
|
||||
upgrade.minecraft:diamond_shovel.adjective=굴착
|
||||
upgrade.minecraft:diamond_pickaxe.adjective=채굴
|
||||
upgrade.minecraft:diamond_axe.adjective=벌목
|
||||
upgrade.minecraft:diamond_hoe.adjective=농업
|
||||
upgrade.computercraft:wireless_modem.adjective=무선
|
||||
upgrade.minecraft:crafting_table.adjective=조합
|
||||
upgrade.computercraft:advanced_modem.adjective=엔더
|
||||
upgrade.computercraft:speaker.adjective=소음
|
||||
|
||||
chat.computercraft.wired_modem.peripheral_connected=주변 "%s"이 네트워크에 연결되었습니다.
|
||||
chat.computercraft.wired_modem.peripheral_disconnected=주변 "%s"이 네트워크로부터 분리되었습니다.
|
||||
|
||||
# Command descriptions, usage and any additional messages.
|
||||
commands.computercraft.synopsis=컴퓨터를 제어하기 위한 다양한 명령어
|
||||
commands.computercraft.desc=/computercraft 명령어는 컴퓨터를 제어하고 상호작용하기 위한 다양한 디버깅 및 관리자 도구를 제공합니다.
|
||||
|
||||
commands.computercraft.help.synopsis=특정 명령어에 대한 도움말을 제공하기
|
||||
commands.computercraft.help.desc=
|
||||
commands.computercraft.help.usage=[command]
|
||||
commands.computercraft.help.no_children=%s에는 하위 명령어가 없습니다.
|
||||
commands.computercraft.help.no_command='%s'라는 명령어가 없습니다.
|
||||
|
||||
commands.computercraft.dump.synopsis=컴퓨터의 상태를 보여주기
|
||||
commands.computercraft.dump.desc=모든 시스템의 상태 또는 한 시스템에 대한 특정 정보를 표시합니다. 컴퓨터의 인스턴스 ID(예: 123)나 컴퓨터 ID(예: #123) 또는 라벨(예: "@My Computer")을 지정할 수 있습니다.
|
||||
commands.computercraft.dump.usage=[id]
|
||||
commands.computercraft.dump.action=이 컴퓨터에 대한 추가 정보를 봅니다.
|
||||
|
||||
commands.computercraft.shutdown.synopsis=시스템을 원격으로 종료하기
|
||||
commands.computercraft.shutdown.desc=나열된 시스템 또는 지정된 시스템이 없는 경우 모두 종료합니다. 컴퓨터의 인스턴스 ID(예: 123)나 컴퓨터 ID(예: #123) 또는 라벨(예: "@My Computer")을 지정할 수 있습니다.
|
||||
commands.computercraft.shutdown.usage=[ids...]
|
||||
commands.computercraft.shutdown.done=%s/%s 컴퓨터 시스템 종료
|
||||
|
||||
commands.computercraft.turn_on.synopsis=시스템을 원격으로 실행하기
|
||||
commands.computercraft.turn_on.desc=나열된 컴퓨터를 실행합니다. 컴퓨터의 인스턴스 ID(예: 123)나 컴퓨터 ID(예: #123) 또는 라벨(예: "@My Computer")을 지정할 수 있습니다.
|
||||
commands.computercraft.turn_on.usage=[ids...]
|
||||
commands.computercraft.turn_on.done=%s/%s 컴퓨터 시스템 실행
|
||||
|
||||
commands.computercraft.tp.synopsis=특정 컴퓨터로 순간이동하기
|
||||
commands.computercraft.tp.desc=컴퓨터의 위치로 순간이동합니다. 컴퓨터의 인스턴스 ID(예: 123) 또는 컴퓨터 ID(예: #123)를 지정할 수 있습니다.
|
||||
commands.computercraft.tp.usage=<id>
|
||||
commands.computercraft.tp.action=이 컴퓨터로 순간이동하기
|
||||
commands.computercraft.tp.not_entity=비플레이어한테 터미널을 열 수 없습니다.
|
||||
commands.computercraft.tp.not_there=월드에서 컴퓨터를 위치시킬 수 없습니다.
|
||||
|
||||
commands.computercraft.view.synopsis=컴퓨터의 터미널을 보기
|
||||
commands.computercraft.view.desc=컴퓨터의 원격 제어를 허용하는 컴퓨터의 터미널을 엽니다. 이것은 터틀의 인벤토리에 대한 접근을 제공하지 않습니다. 컴퓨터의 인스턴스 ID(예: 123) 또는 컴퓨터 ID(예: #123)를 지정할 수 있습니다.
|
||||
commands.computercraft.view.usage=<id>
|
||||
commands.computercraft.view.action=이 컴퓨터를 봅니다.
|
||||
commands.computercraft.view.not_player=비플레이어한테 터미널을 열 수 없습니다.
|
||||
|
||||
commands.computercraft.track.synopsis=컴퓨터의 실행 시간을 추적하기
|
||||
commands.computercraft.track.desc=컴퓨터가 실행되는 기간과 처리되는 이벤트 수를 추적합니다. 이는 /forge 트랙과 유사한 방법으로 정보를 제공하며 지연 로그에 유용할 수 있습니다.
|
||||
|
||||
commands.computercraft.track.start.synopsis=모든 컴퓨터의 추적을 시작하기
|
||||
commands.computercraft.track.start.desc=모든 컴퓨터의 이벤트 및 실행 시간 추적을 시작합니다. 이는 이전 실행의 결과를 폐기할 것입니다.
|
||||
commands.computercraft.track.start.usage=
|
||||
commands.computercraft.track.start.stop=%s을(를) 실행하여 추적을 중지하고 결과를 확인합니다.
|
||||
|
||||
commands.computercraft.track.stop.synopsis=모든 컴퓨터의 추적을 중지하기
|
||||
commands.computercraft.track.stop.desc=모든 컴퓨터의 이벤트 및 실행 시간 추적을 중지합니다.
|
||||
commands.computercraft.track.stop.usage=
|
||||
commands.computercraft.track.stop.action=추적을 중지하려면 클릭하세요.
|
||||
commands.computercraft.track.stop.not_enabled=현재 추적하는 컴퓨터가 없습니다.
|
||||
|
||||
commands.computercraft.track.dump.synopsis=최신 추적 결과를 덤프하기
|
||||
commands.computercraft.track.dump.desc=최신 컴퓨터 추적의 결과를 덤프합니다.
|
||||
commands.computercraft.track.dump.usage=[kind]
|
||||
commands.computercraft.track.dump.no_timings=사용가능한 시간이 없습니다.
|
||||
commands.computercraft.track.dump.no_field=알 수 없는 필드 '%s'
|
||||
commands.computercraft.track.dump.computer=컴퓨터
|
||||
|
||||
commands.computercraft.reload.synopsis=컴퓨터크래프트 구성파일을 리로드하기
|
||||
commands.computercraft.reload.desc=컴퓨터크래프트 구성파일을 리로드합니다.
|
||||
commands.computercraft.reload.usage=
|
||||
commands.computercraft.reload.done=리로드된 구성
|
||||
|
||||
commands.computercraft.queue.synopsis=computer_command 이벤트를 명령 컴퓨터에 보내기
|
||||
commands.computercraft.queue.desc=computer_command 이벤트를 명령 컴퓨터로 전송하여 추가 인수를 전달합니다. 이는 대부분 지도 제작자를 위해 설계되었으며, 보다 컴퓨터 친화적인 버전의 /trigger 역할을 합니다. 어떤 플레이어든 명령을 실행할 수 있으며, 이는 텍스트 구성 요소의 클릭 이벤트를 통해 수행될 가능성이 가장 높습니다.
|
||||
commands.computercraft.queue.usage=<id> [args...]
|
||||
|
||||
commands.computercraft.generic.no_position=<no pos>
|
||||
commands.computercraft.generic.position=%s, %s, %s
|
||||
commands.computercraft.generic.yes=Y
|
||||
commands.computercraft.generic.no=N
|
||||
commands.computercraft.generic.exception=처리되지 않은 예외 (%s)
|
||||
commands.computercraft.generic.additional_rows=%d개의 추가 행...
|
||||
|
||||
commands.computercraft.argument.no_matching='%s'와 일치하는 컴퓨터가 없습니다.
|
||||
commands.computercraft.argument.many_matching='%s'와 일치하는 여러 컴퓨터 (인스턴스 %s)
|
||||
commands.computercraft.argument.not_number='%s'는 숫자가 아닙니다.
|
||||
|
||||
# Names for the various tracking fields.
|
||||
tracking_field.computercraft.tasks.name=작업
|
||||
tracking_field.computercraft.total.name=전체 시간
|
||||
tracking_field.computercraft.average.name=평균 시간
|
||||
tracking_field.computercraft.max.name=최대 시간
|
||||
|
||||
tracking_field.computercraft.server_count.name=서버 작업 수
|
||||
tracking_field.computercraft.server_time.name=서버 작업 시간
|
||||
|
||||
tracking_field.computercraft.peripheral.name=주변 호출
|
||||
tracking_field.computercraft.fs.name=파일시스템 작업
|
||||
tracking_field.computercraft.turtle.name=터틀 작업
|
||||
|
||||
tracking_field.computercraft.http.name=HTTP 요청
|
||||
tracking_field.computercraft.http_upload.name=HTTP 업로드
|
||||
tracking_field.computercraft.http_download.name=HTTT 다운로드
|
||||
|
||||
tracking_field.computercraft.websocket_incoming.name=웹소켓 수신
|
||||
tracking_field.computercraft.websocket_outgoing.name=웹소켓 송신
|
||||
|
||||
tracking_field.computercraft.coroutines_created.name=코루틴 생성됨
|
||||
tracking_field.computercraft.coroutines_dead.name=코루틴 처리됨
|
||||
|
||||
# Misc tooltips
|
||||
gui.computercraft.tooltip.copy=클립보드에 복사
|
||||
gui.computercraft.tooltip.computer_id=(컴퓨터 ID: %s)
|
||||
gui.computercraft.tooltip.disk_id=(디스크 ID: %s)
|
||||
|
||||
# Config options
|
||||
gui.computercraft:config.computer_space_limit=컴퓨터 공간 제한 (바이트)
|
||||
gui.computercraft:config.floppy_space_limit=플로피 디스크 공간 제한 (바이트)
|
||||
gui.computercraft:config.maximum_open_files=컴퓨터당 최대 파일 열기
|
||||
gui.computercraft:config.disable_lua51_features=Lua 5.1 기능 미사용
|
||||
gui.computercraft:config.default_computer_settings=기본 컴퓨터 설정
|
||||
gui.computercraft:config.debug_enabled=디버그 라이브러리 사용
|
||||
gui.computercraft:config.log_computer_errors=컴퓨터 오류 로그
|
||||
|
||||
gui.computercraft:config.execution=실행
|
||||
gui.computercraft:config.execution.computer_threads=컴퓨터 쓰레드
|
||||
gui.computercraft:config.execution.max_main_global_time=전역 시간 당 서버 제한
|
||||
gui.computercraft:config.execution.max_main_computer_time=컴퓨터 시간 당 서버 제한
|
||||
|
||||
gui.computercraft:config.http=HTTP
|
||||
gui.computercraft:config.http.enabled=HTTP API 사용하기
|
||||
gui.computercraft:config.http.websocket_enabled=웹소켓 사용
|
||||
gui.computercraft:config.http.allowed_domains=허용된 도메인
|
||||
gui.computercraft:config.http.blocked_domains=차단된 도메인
|
||||
|
||||
gui.computercraft:config.http.timeout=타임아웃
|
||||
gui.computercraft:config.http.max_requests=최대 동시 요청 수
|
||||
gui.computercraft:config.http.max_download=최대 응답 크기
|
||||
gui.computercraft:config.http.max_upload=최대 요청 크기
|
||||
gui.computercraft:config.http.max_websockets=최대 동시 웹소켓 수
|
||||
gui.computercraft:config.http.max_websocket_message=최대 웹 포켓 메시지 크기
|
||||
|
||||
gui.computercraft:config.peripheral=주변
|
||||
gui.computercraft:config.peripheral.command_block_enabled=명령 블록 주변 장치 사용
|
||||
gui.computercraft:config.peripheral.modem_range=모뎀 범위(기본값)
|
||||
gui.computercraft:config.peripheral.modem_high_altitude_range=모뎀 범위(높은 고도)
|
||||
gui.computercraft:config.peripheral.modem_range_during_storm=모뎀 범위(나쁜 날씨)
|
||||
gui.computercraft:config.peripheral.modem_high_altitude_range_during_storm=모뎀 범위(높은 고도, 나쁜 날씨)
|
||||
gui.computercraft:config.peripheral.max_notes_per_tick=컴퓨터가 한 번에 재생할 수 있는 최대 소리 수
|
||||
|
||||
gui.computercraft:config.turtle=터틀
|
||||
gui.computercraft:config.turtle.need_fuel=연료 사용
|
||||
gui.computercraft:config.turtle.normal_fuel_limit=터틀 연료 제한
|
||||
gui.computercraft:config.turtle.advanced_fuel_limit=고급 터틀 연료 제한
|
||||
gui.computercraft:config.turtle.obey_block_protection=터틀이 블록 보호에 따르기
|
||||
gui.computercraft:config.turtle.can_push=터틀이 엔티티 밀어내기
|
||||
gui.computercraft:config.turtle.disabled_actions=터틀 액션 미사용
|
@ -10,7 +10,7 @@
|
||||
"faces": {
|
||||
"down": { "uv": [ 2, 13, 14, 16 ], "texture": "#front" },
|
||||
"up": { "uv": [ 2, 0, 14, 3 ], "texture": "#front" },
|
||||
"north": { "uv": [ 2, 2, 14, 14 ], "texture": "#back" },
|
||||
"north": { "uv": [ 2, 2, 14, 14 ], "texture": "#back", "cullface": "north" },
|
||||
"south": { "uv": [ 2, 2, 14, 14 ], "texture": "#front" },
|
||||
"west": { "uv": [ 0, 2, 3, 14 ], "texture": "#front" },
|
||||
"east": { "uv": [ 13, 2, 16, 14 ], "texture": "#front" }
|
||||
|
@ -8,23 +8,23 @@
|
||||
"from": [ 2, 2, 2 ],
|
||||
"to": [ 14, 14, 13 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 2.75, 0, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 5.75, 0, 8.75, 2.75 ], "texture": "#texture" },
|
||||
"north": { "uv": [ 8.5, 5.75, 11.5, 2.75 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 2.75, 5.75, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 0, 5.75, 2.75, 2.75 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 5.75, 5.75, 8.5, 2.75 ], "texture": "#texture" }
|
||||
"down": { "uv": [ 5.75, 2.75, 2.75, 0 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 8.75, 0, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"north": { "uv": [ 11.5, 5.75, 8.5, 2.75 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 5.75, 5.75, 2.75, 2.75 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 8.5, 5.75, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 2.75, 5.75, 0, 2.75 ], "texture": "#texture" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 3, 6, 13 ],
|
||||
"to": [ 13, 13, 15 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 9.25, 0, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 11.75, 0, 14.25, 0.5 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 9.25, 2.25, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 8.75, 2.25, 9.25, 0.5 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 11.75, 2.25, 12.25, 0.5 ], "texture": "#texture" }
|
||||
"down": { "uv": [ 11.75, 0.5, 9.25, 0 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 14.25, 0, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 11.75, 2.25, 9.25, 0.5 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 12.25, 2.25, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 9.25, 2.25, 8.75, 0.5 ], "texture": "#texture" }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -8,46 +8,46 @@
|
||||
"from": [ 2, 2, 2 ],
|
||||
"to": [ 14, 14, 13 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 2.75, 5.75, 5.75, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"up": { "uv": [ 5.75, 5.75, 8.75, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"north": { "uv": [ 8.5, 11.5, 11.5, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"south": { "uv": [ 2.75, 11.5, 5.75, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"west": { "uv": [ 0, 11.5, 2.75, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"east": { "uv": [ 5.75, 11.5, 8.5, 8.555 ], "texture": "#texture", "tintindex": 0 }
|
||||
"down": { "uv": [ 5.75, 8.5, 2.75, 5.75 ], "texture": "#texture", "tintindex": 0 },
|
||||
"up": { "uv": [ 8.75, 5.75, 5.75, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"north": { "uv": [ 11.5, 11.5, 8.5, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"south": { "uv": [ 5.75, 11.5, 2.75, 8.5 ], "texture": "#texture", "tintindex": 0 },
|
||||
"west": { "uv": [ 8.5, 11.5, 5.75, 8.555 ], "texture": "#texture", "tintindex": 0 },
|
||||
"east": { "uv": [ 2.75, 11.5, 0, 8.5 ], "texture": "#texture", "tintindex": 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 3, 6, 13 ],
|
||||
"to": [ 13, 13, 15 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 9.25, 5.75, 11.75, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"up": { "uv": [ 11.75, 5.75, 14.25, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"south": { "uv": [ 9.25, 8, 11.75, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"west": { "uv": [ 8.75, 8, 9.25, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"east": { "uv": [ 11.75, 8, 12.25, 6.25 ], "texture": "#texture", "tintindex": 0 }
|
||||
"down": { "uv": [ 11.75, 6.25, 9.25, 5.75 ], "texture": "#texture", "tintindex": 0 },
|
||||
"up": { "uv": [ 14.25, 5.75, 11.75, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"south": { "uv": [ 11.75, 8, 9.25, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"west": { "uv": [ 12.25, 8, 11.75, 6.25 ], "texture": "#texture", "tintindex": 0 },
|
||||
"east": { "uv": [ 9.25, 8, 8.75, 6.25 ], "texture": "#texture", "tintindex": 0 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 2, 2, 2 ],
|
||||
"to": [ 14, 14, 13 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 2.75, 0, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 5.75, 0, 8.75, 2.75 ], "texture": "#texture" },
|
||||
"north": { "uv": [ 8.5, 5.75, 11.5, 2.75 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 2.75, 5.75, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 0, 5.75, 2.75, 2.75 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 5.75, 5.75, 8.5, 2.75 ], "texture": "#texture" }
|
||||
"down": { "uv": [ 5.75, 2.75, 2.75, 0 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 8.75, 0, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"north": { "uv": [ 11.5, 5.75, 8.5, 2.75 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 5.75, 5.75, 2.75, 2.75 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 8.5, 5.75, 5.75, 2.75 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 2.75, 5.75, 0, 2.75 ], "texture": "#texture" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 3, 6, 13 ],
|
||||
"to": [ 13, 13, 15 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 9.25, 0, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 11.75, 0, 14.25, 0.5 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 9.25, 2.25, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 8.75, 2.25, 9.25, 0.5 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 11.75, 2.25, 12.25, 0.5 ], "texture": "#texture" }
|
||||
"down": { "uv": [ 11.75, 0.5, 9.25, 0 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 14.25, 0, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 11.75, 2.25, 9.25, 0.5 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 12.25, 2.25, 11.75, 0.5 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 9.25, 2.25, 8.75, 0.5 ], "texture": "#texture" }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -8,8 +8,8 @@
|
||||
"from": [ 0.5, 4.5, 3.5 ],
|
||||
"to": [ 2, 12.5, 11.5 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 13, 2, 16, 14 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 13, 2, 16, 14 ], "texture": "#texture" },
|
||||
"down": { "uv": [ 2, 14, 14, 16 ], "texture": "#texture", "rotation": 270 },
|
||||
"up": { "uv": [ 2, 0, 14, 3 ], "texture": "#texture", "rotation": 90 },
|
||||
"north": { "uv": [ 0, 2, 3, 14 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 13, 2, 16, 14 ], "texture": "#texture" },
|
||||
"west": { "uv": [ 2, 2, 14, 14 ], "texture": "#texture" }
|
||||
|
@ -8,8 +8,8 @@
|
||||
"from": [ 14, 4.5, 3.5 ],
|
||||
"to": [ 15.5, 12.5, 11.5 ],
|
||||
"faces": {
|
||||
"down": { "uv": [ 0, 2, 3, 14 ], "texture": "#texture" },
|
||||
"up": { "uv": [ 0, 2, 3, 14 ], "texture": "#texture" },
|
||||
"down": { "uv": [ 2, 14, 14, 16 ], "texture": "#texture", "rotation": 90 },
|
||||
"up": { "uv": [ 2, 0, 14, 3 ], "texture": "#texture", "rotation": 270 },
|
||||
"north": { "uv": [ 13, 2, 16, 14 ], "texture": "#texture" },
|
||||
"south": { "uv": [ 0, 2, 3, 14 ], "texture": "#texture" },
|
||||
"east": { "uv": [ 2, 2, 14, 14 ], "texture": "#texture" }
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 123 B |
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.8 KiB |
@ -21,7 +21,7 @@ if _VERSION == "Lua 5.1" then
|
||||
local nativeloadstring = loadstring
|
||||
local nativesetfenv = setfenv
|
||||
|
||||
--- Historically load/loadstring would handle the chunk name as if it has
|
||||
-- Historically load/loadstring would handle the chunk name as if it has
|
||||
-- been prefixed with "=". We emulate that behaviour here.
|
||||
local function prefix(chunkname)
|
||||
if type(chunkname) ~= "string" then return chunkname end
|
||||
@ -33,45 +33,43 @@ if _VERSION == "Lua 5.1" then
|
||||
end
|
||||
end
|
||||
|
||||
function load( x, name, mode, env )
|
||||
function load(x, name, mode, env)
|
||||
expect(1, x, "function", "string")
|
||||
expect(2, name, "string", "nil")
|
||||
expect(3, mode, "string", "nil")
|
||||
expect(4, env, "table", "nil")
|
||||
|
||||
local ok, p1, p2 = pcall( function()
|
||||
local ok, p1, p2 = pcall(function()
|
||||
if type(x) == "string" then
|
||||
local result, err = nativeloadstring( x, name )
|
||||
local result, err = nativeloadstring(x, name)
|
||||
if result then
|
||||
if env then
|
||||
env._ENV = env
|
||||
nativesetfenv( result, env )
|
||||
nativesetfenv(result, env)
|
||||
end
|
||||
return result
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
else
|
||||
local result, err = nativeload( x, name )
|
||||
local result, err = nativeload(x, name)
|
||||
if result then
|
||||
if env then
|
||||
env._ENV = env
|
||||
nativesetfenv( result, env )
|
||||
nativesetfenv(result, env)
|
||||
end
|
||||
return result
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end )
|
||||
end)
|
||||
if ok then
|
||||
return p1, p2
|
||||
else
|
||||
error( p1, 2 )
|
||||
error(p1, 2)
|
||||
end
|
||||
end
|
||||
table.unpack = unpack
|
||||
table.pack = function( ... ) return { n = select( "#", ... ), ... } end
|
||||
|
||||
if _CC_DISABLE_LUA51_FEATURES then
|
||||
-- Remove the Lua 5.1 features that will be removed when we update to Lua 5.2, for compatibility testing.
|
||||
@ -83,7 +81,7 @@ if _VERSION == "Lua 5.1" then
|
||||
math.log10 = nil
|
||||
table.maxn = nil
|
||||
else
|
||||
loadstring = function(string, chunkname) return nativeloadstring(string, prefix( chunkname )) end
|
||||
loadstring = function(string, chunkname) return nativeloadstring(string, prefix(chunkname)) end
|
||||
|
||||
-- Inject a stub for the old bit library
|
||||
_G.bit = {
|
||||
@ -98,97 +96,33 @@ if _VERSION == "Lua 5.1" then
|
||||
end
|
||||
end
|
||||
|
||||
if _VERSION == "Lua 5.3" and not bit32 then
|
||||
-- If we're on Lua 5.3, install the bit32 api from Lua 5.2
|
||||
-- (Loaded from a string so this file will still parse on <5.3 lua)
|
||||
load( [[
|
||||
bit32 = {}
|
||||
|
||||
function bit32.arshift( n, bits )
|
||||
if type(n) ~= "number" or type(bits) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return n >> bits
|
||||
end
|
||||
|
||||
function bit32.band( m, n )
|
||||
if type(m) ~= "number" or type(n) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return m & n
|
||||
end
|
||||
|
||||
function bit32.bnot( n )
|
||||
if type(n) ~= "number" then
|
||||
error( "Expected number", 2 )
|
||||
end
|
||||
return ~n
|
||||
end
|
||||
|
||||
function bit32.bor( m, n )
|
||||
if type(m) ~= "number" or type(n) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return m | n
|
||||
end
|
||||
|
||||
function bit32.btest( m, n )
|
||||
if type(m) ~= "number" or type(n) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return (m & n) ~= 0
|
||||
end
|
||||
|
||||
function bit32.bxor( m, n )
|
||||
if type(m) ~= "number" or type(n) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return m ~ n
|
||||
end
|
||||
|
||||
function bit32.lshift( n, bits )
|
||||
if type(n) ~= "number" or type(bits) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return n << bits
|
||||
end
|
||||
|
||||
function bit32.rshift( n, bits )
|
||||
if type(n) ~= "number" or type(bits) ~= "number" then
|
||||
error( "Expected number, number", 2 )
|
||||
end
|
||||
return n >> bits
|
||||
end
|
||||
]] )()
|
||||
end
|
||||
|
||||
-- Install lua parts of the os api
|
||||
function os.version()
|
||||
return "CraftOS 1.8"
|
||||
end
|
||||
|
||||
function os.pullEventRaw( sFilter )
|
||||
return coroutine.yield( sFilter )
|
||||
function os.pullEventRaw(sFilter)
|
||||
return coroutine.yield(sFilter)
|
||||
end
|
||||
|
||||
function os.pullEvent( sFilter )
|
||||
local eventData = table.pack( os.pullEventRaw( sFilter ) )
|
||||
function os.pullEvent(sFilter)
|
||||
local eventData = table.pack(os.pullEventRaw(sFilter))
|
||||
if eventData[1] == "terminate" then
|
||||
error( "Terminated", 0 )
|
||||
error("Terminated", 0)
|
||||
end
|
||||
return table.unpack( eventData, 1, eventData.n )
|
||||
return table.unpack(eventData, 1, eventData.n)
|
||||
end
|
||||
|
||||
-- Install globals
|
||||
function sleep( nTime )
|
||||
function sleep(nTime)
|
||||
expect(1, nTime, "number", "nil")
|
||||
local timer = os.startTimer( nTime or 0 )
|
||||
local timer = os.startTimer(nTime or 0)
|
||||
repeat
|
||||
local _, param = os.pullEvent( "timer" )
|
||||
local _, param = os.pullEvent("timer")
|
||||
until param == timer
|
||||
end
|
||||
|
||||
function write( sText )
|
||||
function write(sText)
|
||||
expect(1, sText, "string", "number")
|
||||
|
||||
local w, h = term.getSize()
|
||||
@ -209,32 +143,32 @@ function write( sText )
|
||||
-- Print the line with proper word wrapping
|
||||
sText = tostring(sText)
|
||||
while #sText > 0 do
|
||||
local whitespace = string.match( sText, "^[ \t]+" )
|
||||
local whitespace = string.match(sText, "^[ \t]+")
|
||||
if whitespace then
|
||||
-- Print whitespace
|
||||
term.write( whitespace )
|
||||
term.write(whitespace)
|
||||
x, y = term.getCursorPos()
|
||||
sText = string.sub( sText, #whitespace + 1 )
|
||||
sText = string.sub(sText, #whitespace + 1)
|
||||
end
|
||||
|
||||
local newline = string.match( sText, "^\n" )
|
||||
local newline = string.match(sText, "^\n")
|
||||
if newline then
|
||||
-- Print newlines
|
||||
newLine()
|
||||
sText = string.sub( sText, 2 )
|
||||
sText = string.sub(sText, 2)
|
||||
end
|
||||
|
||||
local text = string.match( sText, "^[^ \t\n]+" )
|
||||
local text = string.match(sText, "^[^ \t\n]+")
|
||||
if text then
|
||||
sText = string.sub( sText, #text + 1 )
|
||||
sText = string.sub(sText, #text + 1)
|
||||
if #text > w then
|
||||
-- Print a multiline word
|
||||
while #text > 0 do
|
||||
if x > w then
|
||||
newLine()
|
||||
end
|
||||
term.write( text )
|
||||
text = string.sub( text, w - x + 2 )
|
||||
term.write(text)
|
||||
text = string.sub(text, w - x + 2)
|
||||
x, y = term.getCursorPos()
|
||||
end
|
||||
else
|
||||
@ -242,7 +176,7 @@ function write( sText )
|
||||
if x + #text - 1 > w then
|
||||
newLine()
|
||||
end
|
||||
term.write( text )
|
||||
term.write(text)
|
||||
x, y = term.getCursorPos()
|
||||
end
|
||||
end
|
||||
@ -251,42 +185,42 @@ function write( sText )
|
||||
return nLinesPrinted
|
||||
end
|
||||
|
||||
function print( ... )
|
||||
function print(...)
|
||||
local nLinesPrinted = 0
|
||||
local nLimit = select("#", ... )
|
||||
local nLimit = select("#", ...)
|
||||
for n = 1, nLimit do
|
||||
local s = tostring( select( n, ... ) )
|
||||
local s = tostring(select(n, ...))
|
||||
if n < nLimit then
|
||||
s = s .. "\t"
|
||||
end
|
||||
nLinesPrinted = nLinesPrinted + write( s )
|
||||
nLinesPrinted = nLinesPrinted + write(s)
|
||||
end
|
||||
nLinesPrinted = nLinesPrinted + write( "\n" )
|
||||
nLinesPrinted = nLinesPrinted + write("\n")
|
||||
return nLinesPrinted
|
||||
end
|
||||
|
||||
function printError( ... )
|
||||
function printError(...)
|
||||
local oldColour
|
||||
if term.isColour() then
|
||||
oldColour = term.getTextColour()
|
||||
term.setTextColour( colors.red )
|
||||
term.setTextColour(colors.red)
|
||||
end
|
||||
print( ... )
|
||||
print(...)
|
||||
if term.isColour() then
|
||||
term.setTextColour( oldColour )
|
||||
term.setTextColour(oldColour)
|
||||
end
|
||||
end
|
||||
|
||||
function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
function read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
|
||||
expect(1, _sReplaceChar, "string", "nil")
|
||||
expect(2, _tHistory, "table", "nil")
|
||||
expect(3, _fnComplete, "function", "nil")
|
||||
expect(4, _sDefault, "string", "nil")
|
||||
|
||||
term.setCursorBlink( true )
|
||||
term.setCursorBlink(true)
|
||||
|
||||
local sLine
|
||||
if type( _sDefault ) == "string" then
|
||||
if type(_sDefault) == "string" then
|
||||
sLine = _sDefault
|
||||
else
|
||||
sLine = ""
|
||||
@ -294,14 +228,14 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
local nHistoryPos
|
||||
local nPos, nScroll = #sLine, 0
|
||||
if _sReplaceChar then
|
||||
_sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
|
||||
_sReplaceChar = string.sub(_sReplaceChar, 1, 1)
|
||||
end
|
||||
|
||||
local tCompletions
|
||||
local nCompletion
|
||||
local function recomplete()
|
||||
if _fnComplete and nPos == #sLine then
|
||||
tCompletions = _fnComplete( sLine )
|
||||
tCompletions = _fnComplete(sLine)
|
||||
if tCompletions and #tCompletions > 0 then
|
||||
nCompletion = 1
|
||||
else
|
||||
@ -321,7 +255,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
local w = term.getSize()
|
||||
local sx = term.getCursorPos()
|
||||
|
||||
local function redraw( _bClear )
|
||||
local function redraw(_bClear)
|
||||
local cursor_pos = nPos - nScroll
|
||||
if sx + cursor_pos >= w then
|
||||
-- We've moved beyond the RHS, ensure we're on the edge.
|
||||
@ -332,39 +266,39 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
end
|
||||
|
||||
local _, cy = term.getCursorPos()
|
||||
term.setCursorPos( sx, cy )
|
||||
term.setCursorPos(sx, cy)
|
||||
local sReplace = _bClear and " " or _sReplaceChar
|
||||
if sReplace then
|
||||
term.write( string.rep( sReplace, math.max( #sLine - nScroll, 0 ) ) )
|
||||
term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0)))
|
||||
else
|
||||
term.write( string.sub( sLine, nScroll + 1 ) )
|
||||
term.write(string.sub(sLine, nScroll + 1))
|
||||
end
|
||||
|
||||
if nCompletion then
|
||||
local sCompletion = tCompletions[ nCompletion ]
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
local oldText, oldBg
|
||||
if not _bClear then
|
||||
oldText = term.getTextColor()
|
||||
oldBg = term.getBackgroundColor()
|
||||
term.setTextColor( colors.white )
|
||||
term.setBackgroundColor( colors.gray )
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.gray)
|
||||
end
|
||||
if sReplace then
|
||||
term.write( string.rep( sReplace, #sCompletion ) )
|
||||
term.write(string.rep(sReplace, #sCompletion))
|
||||
else
|
||||
term.write( sCompletion )
|
||||
term.write(sCompletion)
|
||||
end
|
||||
if not _bClear then
|
||||
term.setTextColor( oldText )
|
||||
term.setBackgroundColor( oldBg )
|
||||
term.setTextColor(oldText)
|
||||
term.setBackgroundColor(oldBg)
|
||||
end
|
||||
end
|
||||
|
||||
term.setCursorPos( sx + nPos - nScroll, cy )
|
||||
term.setCursorPos(sx + nPos - nScroll, cy)
|
||||
end
|
||||
|
||||
local function clear()
|
||||
redraw( true )
|
||||
redraw(true)
|
||||
end
|
||||
|
||||
recomplete()
|
||||
@ -376,7 +310,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
clear()
|
||||
|
||||
-- Find the common prefix of all the other suggestions which start with the same letter as the current one
|
||||
local sCompletion = tCompletions[ nCompletion ]
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
sLine = sLine .. sCompletion
|
||||
nPos = #sLine
|
||||
|
||||
@ -390,7 +324,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
if sEvent == "char" then
|
||||
-- Typed key
|
||||
clear()
|
||||
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
||||
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos + 1
|
||||
recomplete()
|
||||
redraw()
|
||||
@ -398,7 +332,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
elseif sEvent == "paste" then
|
||||
-- Pasted text
|
||||
clear()
|
||||
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
|
||||
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos + #param
|
||||
recomplete()
|
||||
redraw()
|
||||
@ -489,7 +423,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
-- Backspace
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
|
||||
sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos - 1
|
||||
if nScroll > 0 then nScroll = nScroll - 1 end
|
||||
recomplete()
|
||||
@ -509,7 +443,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
-- Delete
|
||||
if nPos < #sLine then
|
||||
clear()
|
||||
sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
|
||||
sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
@ -546,14 +480,14 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
end
|
||||
|
||||
local _, cy = term.getCursorPos()
|
||||
term.setCursorBlink( false )
|
||||
term.setCursorPos( w + 1, cy )
|
||||
term.setCursorBlink(false)
|
||||
term.setCursorPos(w + 1, cy)
|
||||
print()
|
||||
|
||||
return sLine
|
||||
end
|
||||
|
||||
function loadfile( filename, mode, env )
|
||||
function loadfile(filename, mode, env)
|
||||
-- Support the previous `loadfile(filename, env)` form instead.
|
||||
if type(mode) == "table" and env == nil then
|
||||
mode, env = nil, mode
|
||||
@ -563,81 +497,81 @@ function loadfile( filename, mode, env )
|
||||
expect(2, mode, "string", "nil")
|
||||
expect(3, env, "table", "nil")
|
||||
|
||||
local file = fs.open( filename, "r" )
|
||||
local file = fs.open(filename, "r")
|
||||
if not file then return nil, "File not found" end
|
||||
|
||||
local func, err = load( file.readAll(), "@" .. fs.getName( filename ), mode, env )
|
||||
local func, err = load(file.readAll(), "@" .. fs.getName(filename), mode, env)
|
||||
file.close()
|
||||
return func, err
|
||||
end
|
||||
|
||||
function dofile( _sFile )
|
||||
function dofile(_sFile)
|
||||
expect(1, _sFile, "string")
|
||||
|
||||
local fnFile, e = loadfile( _sFile, nil, _G )
|
||||
local fnFile, e = loadfile(_sFile, nil, _G)
|
||||
if fnFile then
|
||||
return fnFile()
|
||||
else
|
||||
error( e, 2 )
|
||||
error(e, 2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Install the rest of the OS api
|
||||
function os.run( _tEnv, _sPath, ... )
|
||||
function os.run(_tEnv, _sPath, ...)
|
||||
expect(1, _tEnv, "table")
|
||||
expect(2, _sPath, "string")
|
||||
|
||||
local tArgs = table.pack( ... )
|
||||
local tArgs = table.pack(...)
|
||||
local tEnv = _tEnv
|
||||
setmetatable( tEnv, { __index = _G } )
|
||||
local fnFile, err = loadfile( _sPath, nil, tEnv )
|
||||
setmetatable(tEnv, { __index = _G })
|
||||
local fnFile, err = loadfile(_sPath, nil, tEnv)
|
||||
if fnFile then
|
||||
local ok, err = pcall( function()
|
||||
fnFile( table.unpack( tArgs, 1, tArgs.n ) )
|
||||
end )
|
||||
local ok, err = pcall(function()
|
||||
fnFile(table.unpack(tArgs, 1, tArgs.n))
|
||||
end)
|
||||
if not ok then
|
||||
if err and err ~= "" then
|
||||
printError( err )
|
||||
printError(err)
|
||||
end
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
if err and err ~= "" then
|
||||
printError( err )
|
||||
printError(err)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local tAPIsLoading = {}
|
||||
function os.loadAPI( _sPath )
|
||||
function os.loadAPI(_sPath)
|
||||
expect(1, _sPath, "string")
|
||||
local sName = fs.getName( _sPath )
|
||||
local sName = fs.getName(_sPath)
|
||||
if sName:sub(-4) == ".lua" then
|
||||
sName = sName:sub(1, -5)
|
||||
end
|
||||
if tAPIsLoading[sName] == true then
|
||||
printError( "API " .. sName .. " is already being loaded" )
|
||||
printError("API " .. sName .. " is already being loaded")
|
||||
return false
|
||||
end
|
||||
tAPIsLoading[sName] = true
|
||||
|
||||
local tEnv = {}
|
||||
setmetatable( tEnv, { __index = _G } )
|
||||
local fnAPI, err = loadfile( _sPath, nil, tEnv )
|
||||
setmetatable(tEnv, { __index = _G })
|
||||
local fnAPI, err = loadfile(_sPath, nil, tEnv)
|
||||
if fnAPI then
|
||||
local ok, err = pcall( fnAPI )
|
||||
local ok, err = pcall(fnAPI)
|
||||
if not ok then
|
||||
tAPIsLoading[sName] = nil
|
||||
return error( "Failed to load API " .. sName .. " due to " .. err, 1 )
|
||||
return error("Failed to load API " .. sName .. " due to " .. err, 1)
|
||||
end
|
||||
else
|
||||
tAPIsLoading[sName] = nil
|
||||
return error( "Failed to load API " .. sName .. " due to " .. err, 1 )
|
||||
return error("Failed to load API " .. sName .. " due to " .. err, 1)
|
||||
end
|
||||
|
||||
local tAPI = {}
|
||||
for k, v in pairs( tEnv ) do
|
||||
for k, v in pairs(tEnv) do
|
||||
if k ~= "_ENV" then
|
||||
tAPI[k] = v
|
||||
end
|
||||
@ -648,15 +582,15 @@ function os.loadAPI( _sPath )
|
||||
return true
|
||||
end
|
||||
|
||||
function os.unloadAPI( _sName )
|
||||
function os.unloadAPI(_sName)
|
||||
expect(1, _sName, "string")
|
||||
if _sName ~= "_G" and type(_G[_sName]) == "table" then
|
||||
_G[_sName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function os.sleep( nTime )
|
||||
sleep( nTime )
|
||||
function os.sleep(nTime)
|
||||
sleep(nTime)
|
||||
end
|
||||
|
||||
local nativeShutdown = os.shutdown
|
||||
@ -685,7 +619,7 @@ if http then
|
||||
PATCH = true, TRACE = true,
|
||||
}
|
||||
|
||||
local function checkKey( options, key, ty, opt )
|
||||
local function checkKey(options, key, ty, opt)
|
||||
local value = options[key]
|
||||
local valueTy = type(value)
|
||||
|
||||
@ -694,24 +628,24 @@ if http then
|
||||
end
|
||||
end
|
||||
|
||||
local function checkOptions( options, body )
|
||||
checkKey( options, "url", "string")
|
||||
local function checkOptions(options, body)
|
||||
checkKey(options, "url", "string")
|
||||
if body == false then
|
||||
checkKey( options, "body", "nil" )
|
||||
checkKey(options, "body", "nil")
|
||||
else
|
||||
checkKey( options, "body", "string", not body )
|
||||
checkKey(options, "body", "string", not body)
|
||||
end
|
||||
checkKey( options, "headers", "table", true )
|
||||
checkKey( options, "method", "string", true )
|
||||
checkKey( options, "redirect", "boolean", true )
|
||||
checkKey(options, "headers", "table", true)
|
||||
checkKey(options, "method", "string", true)
|
||||
checkKey(options, "redirect", "boolean", true)
|
||||
|
||||
if options.method and not methods[options.method] then
|
||||
error( "Unsupported HTTP method", 3 )
|
||||
error("Unsupported HTTP method", 3)
|
||||
end
|
||||
end
|
||||
|
||||
local function wrapRequest( _url, ... )
|
||||
local ok, err = nativeHTTPRequest( ... )
|
||||
local function wrapRequest(_url, ...)
|
||||
local ok, err = nativeHTTPRequest(...)
|
||||
if ok then
|
||||
while true do
|
||||
local event, param1, param2, param3 = os.pullEvent()
|
||||
@ -725,35 +659,35 @@ if http then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
http.get = function( _url, _headers, _binary)
|
||||
if type( _url ) == "table" then
|
||||
checkOptions( _url, false )
|
||||
return wrapRequest( _url.url, _url )
|
||||
http.get = function(_url, _headers, _binary)
|
||||
if type(_url) == "table" then
|
||||
checkOptions(_url, false)
|
||||
return wrapRequest(_url.url, _url)
|
||||
end
|
||||
|
||||
expect(1, _url, "string")
|
||||
expect(2, _headers, "table", "nil")
|
||||
expect(3, _binary, "boolean", "nil")
|
||||
return wrapRequest( _url, _url, nil, _headers, _binary )
|
||||
return wrapRequest(_url, _url, nil, _headers, _binary)
|
||||
end
|
||||
|
||||
http.post = function( _url, _post, _headers, _binary)
|
||||
if type( _url ) == "table" then
|
||||
checkOptions( _url, true )
|
||||
return wrapRequest( _url.url, _url )
|
||||
http.post = function(_url, _post, _headers, _binary)
|
||||
if type(_url) == "table" then
|
||||
checkOptions(_url, true)
|
||||
return wrapRequest(_url.url, _url)
|
||||
end
|
||||
|
||||
expect(1, _url, "string")
|
||||
expect(2, _post, "string")
|
||||
expect(3, _headers, "table", "nil")
|
||||
expect(4, _binary, "boolean", "nil")
|
||||
return wrapRequest( _url, _url, _post, _headers, _binary )
|
||||
return wrapRequest(_url, _url, _post, _headers, _binary)
|
||||
end
|
||||
|
||||
http.request = function( _url, _post, _headers, _binary )
|
||||
http.request = function(_url, _post, _headers, _binary)
|
||||
local url
|
||||
if type( _url ) == "table" then
|
||||
checkOptions( _url )
|
||||
if type(_url) == "table" then
|
||||
checkOptions(_url)
|
||||
url = _url.url
|
||||
else
|
||||
expect(1, _url, "string")
|
||||
@ -763,32 +697,32 @@ if http then
|
||||
url = _url.url
|
||||
end
|
||||
|
||||
local ok, err = nativeHTTPRequest( _url, _post, _headers, _binary )
|
||||
local ok, err = nativeHTTPRequest(_url, _post, _headers, _binary)
|
||||
if not ok then
|
||||
os.queueEvent( "http_failure", url, err )
|
||||
os.queueEvent("http_failure", url, err)
|
||||
end
|
||||
return ok, err
|
||||
end
|
||||
|
||||
local nativeCheckURL = http.checkURL
|
||||
http.checkURLAsync = nativeCheckURL
|
||||
http.checkURL = function( _url )
|
||||
local ok, err = nativeCheckURL( _url )
|
||||
http.checkURL = function(_url)
|
||||
local ok, err = nativeCheckURL(_url)
|
||||
if not ok then return ok, err end
|
||||
|
||||
while true do
|
||||
local _, url, ok, err = os.pullEvent( "http_check" )
|
||||
local _, url, ok, err = os.pullEvent("http_check")
|
||||
if url == _url then return ok, err end
|
||||
end
|
||||
end
|
||||
|
||||
local nativeWebsocket = http.websocket
|
||||
http.websocketAsync = nativeWebsocket
|
||||
http.websocket = function( _url, _headers )
|
||||
http.websocket = function(_url, _headers)
|
||||
expect(1, _url, "string")
|
||||
expect(2, _headers, "table", "nil")
|
||||
|
||||
local ok, err = nativeWebsocket( _url, _headers )
|
||||
local ok, err = nativeWebsocket(_url, _headers)
|
||||
if not ok then return ok, err end
|
||||
|
||||
while true do
|
||||
@ -804,7 +738,7 @@ end
|
||||
|
||||
-- Install the lua part of the FS api
|
||||
local tEmpty = {}
|
||||
function fs.complete( sPath, sLocation, bIncludeFiles, bIncludeDirs )
|
||||
function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
|
||||
expect(1, sPath, "string")
|
||||
expect(2, sLocation, "string")
|
||||
expect(3, bIncludeFiles, "boolean", "nil")
|
||||
@ -814,49 +748,49 @@ function fs.complete( sPath, sLocation, bIncludeFiles, bIncludeDirs )
|
||||
bIncludeDirs = bIncludeDirs ~= false
|
||||
local sDir = sLocation
|
||||
local nStart = 1
|
||||
local nSlash = string.find( sPath, "[/\\]", nStart )
|
||||
local nSlash = string.find(sPath, "[/\\]", nStart)
|
||||
if nSlash == 1 then
|
||||
sDir = ""
|
||||
nStart = 2
|
||||
end
|
||||
local sName
|
||||
while not sName do
|
||||
local nSlash = string.find( sPath, "[/\\]", nStart )
|
||||
local nSlash = string.find(sPath, "[/\\]", nStart)
|
||||
if nSlash then
|
||||
local sPart = string.sub( sPath, nStart, nSlash - 1 )
|
||||
sDir = fs.combine( sDir, sPart )
|
||||
local sPart = string.sub(sPath, nStart, nSlash - 1)
|
||||
sDir = fs.combine(sDir, sPart)
|
||||
nStart = nSlash + 1
|
||||
else
|
||||
sName = string.sub( sPath, nStart )
|
||||
sName = string.sub(sPath, nStart)
|
||||
end
|
||||
end
|
||||
|
||||
if fs.isDir( sDir ) then
|
||||
if fs.isDir(sDir) then
|
||||
local tResults = {}
|
||||
if bIncludeDirs and sPath == "" then
|
||||
table.insert( tResults, "." )
|
||||
table.insert(tResults, ".")
|
||||
end
|
||||
if sDir ~= "" then
|
||||
if sPath == "" then
|
||||
table.insert( tResults, bIncludeDirs and ".." or "../" )
|
||||
table.insert(tResults, bIncludeDirs and ".." or "../")
|
||||
elseif sPath == "." then
|
||||
table.insert( tResults, bIncludeDirs and "." or "./" )
|
||||
table.insert(tResults, bIncludeDirs and "." or "./")
|
||||
end
|
||||
end
|
||||
local tFiles = fs.list( sDir )
|
||||
local tFiles = fs.list(sDir)
|
||||
for n = 1, #tFiles do
|
||||
local sFile = tFiles[n]
|
||||
if #sFile >= #sName and string.sub( sFile, 1, #sName ) == sName then
|
||||
local bIsDir = fs.isDir( fs.combine( sDir, sFile ) )
|
||||
local sResult = string.sub( sFile, #sName + 1 )
|
||||
if #sFile >= #sName and string.sub(sFile, 1, #sName) == sName then
|
||||
local bIsDir = fs.isDir(fs.combine(sDir, sFile))
|
||||
local sResult = string.sub(sFile, #sName + 1)
|
||||
if bIsDir then
|
||||
table.insert( tResults, sResult .. "/" )
|
||||
table.insert(tResults, sResult .. "/")
|
||||
if bIncludeDirs and #sResult > 0 then
|
||||
table.insert( tResults, sResult )
|
||||
table.insert(tResults, sResult)
|
||||
end
|
||||
else
|
||||
if bIncludeFiles and #sResult > 0 then
|
||||
table.insert( tResults, sResult )
|
||||
table.insert(tResults, sResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -868,26 +802,26 @@ end
|
||||
|
||||
-- Load APIs
|
||||
local bAPIError = false
|
||||
local tApis = fs.list( "rom/apis" )
|
||||
for _, sFile in ipairs( tApis ) do
|
||||
if string.sub( sFile, 1, 1 ) ~= "." then
|
||||
local sPath = fs.combine( "rom/apis", sFile )
|
||||
if not fs.isDir( sPath ) then
|
||||
if not os.loadAPI( sPath ) then
|
||||
local tApis = fs.list("rom/apis")
|
||||
for _, sFile in ipairs(tApis) do
|
||||
if string.sub(sFile, 1, 1) ~= "." then
|
||||
local sPath = fs.combine("rom/apis", sFile)
|
||||
if not fs.isDir(sPath) then
|
||||
if not os.loadAPI(sPath) then
|
||||
bAPIError = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if turtle and fs.isDir( "rom/apis/turtle" ) then
|
||||
if turtle and fs.isDir("rom/apis/turtle") then
|
||||
-- Load turtle APIs
|
||||
local tApis = fs.list( "rom/apis/turtle" )
|
||||
for _, sFile in ipairs( tApis ) do
|
||||
if string.sub( sFile, 1, 1 ) ~= "." then
|
||||
local sPath = fs.combine( "rom/apis/turtle", sFile )
|
||||
if not fs.isDir( sPath ) then
|
||||
if not os.loadAPI( sPath ) then
|
||||
local tApis = fs.list("rom/apis/turtle")
|
||||
for _, sFile in ipairs(tApis) do
|
||||
if string.sub(sFile, 1, 1) ~= "." then
|
||||
local sPath = fs.combine("rom/apis/turtle", sFile)
|
||||
if not fs.isDir(sPath) then
|
||||
if not os.loadAPI(sPath) then
|
||||
bAPIError = true
|
||||
end
|
||||
end
|
||||
@ -895,14 +829,14 @@ if turtle and fs.isDir( "rom/apis/turtle" ) then
|
||||
end
|
||||
end
|
||||
|
||||
if pocket and fs.isDir( "rom/apis/pocket" ) then
|
||||
if pocket and fs.isDir("rom/apis/pocket") then
|
||||
-- Load pocket APIs
|
||||
local tApis = fs.list( "rom/apis/pocket" )
|
||||
for _, sFile in ipairs( tApis ) do
|
||||
if string.sub( sFile, 1, 1 ) ~= "." then
|
||||
local sPath = fs.combine( "rom/apis/pocket", sFile )
|
||||
if not fs.isDir( sPath ) then
|
||||
if not os.loadAPI( sPath ) then
|
||||
local tApis = fs.list("rom/apis/pocket")
|
||||
for _, sFile in ipairs(tApis) do
|
||||
if string.sub(sFile, 1, 1) ~= "." then
|
||||
local sPath = fs.combine("rom/apis/pocket", sFile)
|
||||
if not fs.isDir(sPath) then
|
||||
if not os.loadAPI(sPath) then
|
||||
bAPIError = true
|
||||
end
|
||||
end
|
||||
@ -910,18 +844,18 @@ if pocket and fs.isDir( "rom/apis/pocket" ) then
|
||||
end
|
||||
end
|
||||
|
||||
if commands and fs.isDir( "rom/apis/command" ) then
|
||||
if commands and fs.isDir("rom/apis/command") then
|
||||
-- Load command APIs
|
||||
if os.loadAPI( "rom/apis/command/commands.lua" ) then
|
||||
if os.loadAPI("rom/apis/command/commands.lua") then
|
||||
-- Add a special case-insensitive metatable to the commands api
|
||||
local tCaseInsensitiveMetatable = {
|
||||
__index = function( table, key )
|
||||
local value = rawget( table, key )
|
||||
__index = function(table, key)
|
||||
local value = rawget(table, key)
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
if type(key) == "string" then
|
||||
local value = rawget( table, string.lower(key) )
|
||||
local value = rawget(table, string.lower(key))
|
||||
if value ~= nil then
|
||||
return value
|
||||
end
|
||||
@ -929,8 +863,8 @@ if commands and fs.isDir( "rom/apis/command" ) then
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
setmetatable( commands, tCaseInsensitiveMetatable )
|
||||
setmetatable( commands.async, tCaseInsensitiveMetatable )
|
||||
setmetatable(commands, tCaseInsensitiveMetatable)
|
||||
setmetatable(commands.async, tCaseInsensitiveMetatable)
|
||||
|
||||
-- Add global "exec" function
|
||||
exec = commands.exec
|
||||
@ -940,29 +874,77 @@ if commands and fs.isDir( "rom/apis/command" ) then
|
||||
end
|
||||
|
||||
if bAPIError then
|
||||
print( "Press any key to continue" )
|
||||
os.pullEvent( "key" )
|
||||
print("Press any key to continue")
|
||||
os.pullEvent("key")
|
||||
term.clear()
|
||||
term.setCursorPos( 1, 1 )
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
-- Set default settings
|
||||
settings.set( "shell.allow_startup", true )
|
||||
settings.set( "shell.allow_disk_startup", commands == nil )
|
||||
settings.set( "shell.autocomplete", true )
|
||||
settings.set( "edit.autocomplete", true )
|
||||
settings.set( "edit.default_extension", "lua" )
|
||||
settings.set( "paint.default_extension", "nfp" )
|
||||
settings.set( "lua.autocomplete", true )
|
||||
settings.set( "list.show_hidden", false )
|
||||
settings.set( "motd.enable", false )
|
||||
settings.set( "motd.path", "/rom/motd.txt:/motd.txt" )
|
||||
settings.define("shell.allow_startup", {
|
||||
default = true,
|
||||
description = "Run startup files when the computer turns on.",
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("shell.allow_disk_startup", {
|
||||
default = commands == nil,
|
||||
description = "Run startup files from disk drives when the computer turns on.",
|
||||
type = "boolean",
|
||||
})
|
||||
|
||||
settings.define("shell.autocomplete", {
|
||||
default = true,
|
||||
description = "Autocomplete program and arguments in the shell.",
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("edit.autocomplete", {
|
||||
default = true,
|
||||
description = "Autocomplete API and function names in the editor.",
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("lua.autocomplete", {
|
||||
default = true,
|
||||
description = "Autocomplete API and function names in the Lua REPL.",
|
||||
type = "boolean",
|
||||
})
|
||||
|
||||
settings.define("edit.default_extension", {
|
||||
default = "lua",
|
||||
description = [[The file extension the editor will use if none is given. Set to "" to disable.]],
|
||||
type = "string",
|
||||
})
|
||||
settings.define("paint.default_extension", {
|
||||
default = "nfp",
|
||||
description = [[The file extension the paint program will use if none is given. Set to "" to disable.]],
|
||||
type = "string",
|
||||
})
|
||||
|
||||
settings.define("list.show_hidden", {
|
||||
default = false,
|
||||
description = [[Show hidden files (those starting with "." in the Lua REPL)]],
|
||||
type = "boolean",
|
||||
})
|
||||
|
||||
settings.define("motd.enable", {
|
||||
default = false,
|
||||
description = "Display a random message when the computer starts up.",
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("motd.path", {
|
||||
default = "/rom/motd.txt:/motd.txt",
|
||||
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]],
|
||||
type = "string",
|
||||
})
|
||||
if term.isColour() then
|
||||
settings.set( "bios.use_multishell", true )
|
||||
settings.define("bios.use_multishell", {
|
||||
default = true,
|
||||
description = [[Allow running multiple programs at once, through the use of the "fg" and "bg" programs.]],
|
||||
type = "boolean",
|
||||
})
|
||||
end
|
||||
if _CC_DEFAULT_SETTINGS then
|
||||
for sPair in string.gmatch( _CC_DEFAULT_SETTINGS, "[^,]+" ) do
|
||||
local sName, sValue = string.match( sPair, "([^=]*)=(.*)" )
|
||||
for sPair in string.gmatch(_CC_DEFAULT_SETTINGS, "[^,]+") do
|
||||
local sName, sValue = string.match(sPair, "([^=]*)=(.*)")
|
||||
if sName and sValue then
|
||||
local value
|
||||
if sValue == "true" then
|
||||
@ -977,46 +959,43 @@ if _CC_DEFAULT_SETTINGS then
|
||||
value = sValue
|
||||
end
|
||||
if value ~= nil then
|
||||
settings.set( sName, value )
|
||||
settings.set(sName, value)
|
||||
else
|
||||
settings.unset( sName )
|
||||
settings.unset(sName)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Load user settings
|
||||
if fs.exists( ".settings" ) then
|
||||
settings.load( ".settings" )
|
||||
if fs.exists(".settings") then
|
||||
settings.load(".settings")
|
||||
end
|
||||
|
||||
-- Run the shell
|
||||
local ok, err = pcall( function()
|
||||
parallel.waitForAny(
|
||||
function()
|
||||
local sShell
|
||||
if term.isColour() and settings.get( "bios.use_multishell" ) then
|
||||
sShell = "rom/programs/advanced/multishell.lua"
|
||||
else
|
||||
sShell = "rom/programs/shell.lua"
|
||||
end
|
||||
os.run( {}, sShell )
|
||||
os.run( {}, "rom/programs/shutdown.lua" )
|
||||
end,
|
||||
function()
|
||||
rednet.run()
|
||||
end )
|
||||
end )
|
||||
local ok, err = pcall(parallel.waitForAny,
|
||||
function()
|
||||
local sShell
|
||||
if term.isColour() and settings.get("bios.use_multishell") then
|
||||
sShell = "rom/programs/advanced/multishell.lua"
|
||||
else
|
||||
sShell = "rom/programs/shell.lua"
|
||||
end
|
||||
os.run({}, sShell)
|
||||
os.run({}, "rom/programs/shutdown.lua")
|
||||
end,
|
||||
rednet.run
|
||||
)
|
||||
|
||||
-- If the shell errored, let the user read it.
|
||||
term.redirect( term.native() )
|
||||
term.redirect(term.native())
|
||||
if not ok then
|
||||
printError( err )
|
||||
pcall( function()
|
||||
term.setCursorBlink( false )
|
||||
print( "Press any key to continue" )
|
||||
os.pullEvent( "key" )
|
||||
end )
|
||||
printError(err)
|
||||
pcall(function()
|
||||
term.setCursorBlink(false)
|
||||
print("Press any key to continue")
|
||||
os.pullEvent("key")
|
||||
end)
|
||||
end
|
||||
|
||||
-- End
|
||||
|
@ -1,24 +1,92 @@
|
||||
--- The Colors API allows you to manipulate sets of colors.
|
||||
--
|
||||
-- This is useful in conjunction with Bundled Cables from the RedPower mod,
|
||||
-- RedNet Cables from the MineFactory Reloaded mod, and colors on Advanced
|
||||
-- Computers and Advanced Monitors.
|
||||
--
|
||||
-- For the non-American English version just replace @{colors} with @{colours}
|
||||
-- and it will use the other API, colours which is exactly the same, except in
|
||||
-- British English (e.g. @{colors.gray} is spelt @{colours.grey}).
|
||||
--
|
||||
-- @see colours
|
||||
-- @module colors
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
-- Colors
|
||||
white = 1
|
||||
orange = 2
|
||||
magenta = 4
|
||||
lightBlue = 8
|
||||
yellow = 16
|
||||
lime = 32
|
||||
pink = 64
|
||||
gray = 128
|
||||
lightGray = 256
|
||||
cyan = 512
|
||||
purple = 1024
|
||||
blue = 2048
|
||||
brown = 4096
|
||||
green = 8192
|
||||
red = 16384
|
||||
black = 32768
|
||||
--- White: Written as `0` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #F0F0F0.
|
||||
white = 0x1
|
||||
|
||||
function combine( ... )
|
||||
--- Orange: Written as `1` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #F2B233.
|
||||
orange = 0x2
|
||||
|
||||
--- Magenta: Written as `2` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #E57FD8.
|
||||
magenta = 0x4
|
||||
|
||||
--- Light blue: Written as `3` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #99B2F2.
|
||||
lightBlue = 0x8
|
||||
|
||||
--- Yellow: Written as `4` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #DEDE6C.
|
||||
yellow = 0x10
|
||||
|
||||
--- Lime: Written as `5` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #7FCC19.
|
||||
lime = 0x20
|
||||
|
||||
--- Pink. Written as `6` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #F2B2CC.
|
||||
pink = 0x40
|
||||
|
||||
--- Gray: Written as `7` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #4C4C4C.
|
||||
gray = 0x80
|
||||
|
||||
--- Light gray: Written as `8` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #999999.
|
||||
lightGray = 0x100
|
||||
|
||||
--- Cyan: Written as `9` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #4C99B2.
|
||||
cyan = 0x200
|
||||
|
||||
--- Purple: Written as `a` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #B266E5.
|
||||
purple = 0x400
|
||||
|
||||
--- Blue: Written as `b` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #3366CC.
|
||||
blue = 0x800
|
||||
|
||||
--- Brown: Written as `c` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #7F664C.
|
||||
brown = 0x1000
|
||||
|
||||
--- Green: Written as `d` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #57A64E.
|
||||
green = 0x2000
|
||||
|
||||
--- Red: Written as `e` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #CC4C4C.
|
||||
red = 0x4000
|
||||
|
||||
--- Black: Written as `f` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #191919.
|
||||
black = 0x8000
|
||||
|
||||
--- Combines a set of colors (or sets of colors) into a larger set.
|
||||
--
|
||||
-- @tparam number ... The colors to combine.
|
||||
-- @treturn number The union of the color sets given in `...`
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.combine(colors.white, colors.magenta, colours.lightBlue)
|
||||
-- -- => 13
|
||||
-- ```
|
||||
function combine(...)
|
||||
local r = 0
|
||||
for i = 1, select('#', ...) do
|
||||
local c = select(i, ...)
|
||||
@ -28,7 +96,21 @@ function combine( ... )
|
||||
return r
|
||||
end
|
||||
|
||||
function subtract( colors, ... )
|
||||
--- Removes one or more colors (or sets of colors) from an initial set.
|
||||
--
|
||||
-- Each parameter beyond the first may be a single color or may be a set of
|
||||
-- colors (in the latter case, all colors in the set are removed from the
|
||||
-- original set).
|
||||
--
|
||||
-- @tparam number colors The color from which to subtract.
|
||||
-- @tparam number ... The colors to subtract.
|
||||
-- @treturn number The resulting color.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colours.subtract(colours.lime, colours.orange, colours.white)
|
||||
-- -- => 32
|
||||
-- ```
|
||||
function subtract(colors, ...)
|
||||
expect(1, colors, "number")
|
||||
local r = colors
|
||||
for i = 1, select('#', ...) do
|
||||
@ -39,34 +121,91 @@ function subtract( colors, ... )
|
||||
return r
|
||||
end
|
||||
|
||||
function test( colors, color )
|
||||
--- Tests whether `color` is contained within `colors`.
|
||||
--
|
||||
-- @tparam number colors A color, or color set
|
||||
-- @tparam number color A color or set of colors that `colors` should contain.
|
||||
-- @treturn boolean If `colors` contains all colors within `color`.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.test(colors.combine(colors.white, colors.magenta, colours.lightBlue), colors.lightBlue)
|
||||
-- -- => true
|
||||
-- ```
|
||||
function test(colors, color)
|
||||
expect(1, colors, "number")
|
||||
expect(2, color, "number")
|
||||
return bit32.band(colors, color) == color
|
||||
end
|
||||
|
||||
function packRGB( r, g, b )
|
||||
--- Combine a three-colour RGB value into one hexadecimal representation.
|
||||
--
|
||||
-- @tparam number r The red channel, should be between 0 and 1.
|
||||
-- @tparam number g The red channel, should be between 0 and 1.
|
||||
-- @tparam number b The blue channel, should be between 0 and 1.
|
||||
-- @treturn number The combined hexadecimal colour.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0.7, 0.2, 0.6)
|
||||
-- -- => 0xb23399
|
||||
-- ```
|
||||
function packRGB(r, g, b)
|
||||
expect(1, r, "number")
|
||||
expect(2, g, "number")
|
||||
expect(3, b, "number")
|
||||
return
|
||||
bit32.band( r * 255, 0xFF ) * 2 ^ 16 +
|
||||
bit32.band( g * 255, 0xFF ) * 2 ^ 8 +
|
||||
bit32.band( b * 255, 0xFF )
|
||||
bit32.band(r * 255, 0xFF) * 2 ^ 16 +
|
||||
bit32.band(g * 255, 0xFF) * 2 ^ 8 +
|
||||
bit32.band(b * 255, 0xFF)
|
||||
end
|
||||
|
||||
function unpackRGB( rgb )
|
||||
--- Separate a hexadecimal RGB colour into its three constituent channels.
|
||||
--
|
||||
-- @tparam number rgb The combined hexadecimal colour.
|
||||
-- @treturn number The red channel, will be between 0 and 1.
|
||||
-- @treturn number The red channel, will be between 0 and 1.
|
||||
-- @treturn number The blue channel, will be between 0 and 1.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0xb23399)
|
||||
-- -- => 0.7, 0.2, 0.6
|
||||
-- ```
|
||||
-- @see colors.packRGB
|
||||
function unpackRGB(rgb)
|
||||
expect(1, rgb, "number")
|
||||
return
|
||||
bit32.band( bit32.rshift( rgb, 16 ), 0xFF ) / 255,
|
||||
bit32.band( bit32.rshift( rgb, 8 ), 0xFF ) / 255,
|
||||
bit32.band( rgb, 0xFF ) / 255
|
||||
bit32.band(bit32.rshift(rgb, 16), 0xFF) / 255,
|
||||
bit32.band(bit32.rshift(rgb, 8), 0xFF) / 255,
|
||||
bit32.band(rgb, 0xFF) / 255
|
||||
end
|
||||
|
||||
function rgb8( r, g, b )
|
||||
--- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many
|
||||
-- arguments it receives.
|
||||
--
|
||||
-- **Note:** This function is deprecated, and it is recommended you use the
|
||||
-- specific pack/unpack function directly.
|
||||
--
|
||||
-- @tparam[1] number r The red channel, as an argument to @{colors.packRGB}.
|
||||
-- @tparam[1] number g The green channel, as an argument to @{colors.packRGB}.
|
||||
-- @tparam[1] number b The blue channel, as an argument to @{colors.packRGB}.
|
||||
-- @tparam[2] number rgb The combined hexadecimal color, as an argument to @{colors.unpackRGB}.
|
||||
-- @treturn[1] number The combined hexadecimal colour, as returned by @{colors.packRGB}.
|
||||
-- @treturn[2] number The red channel, as returned by @{colors.unpackRGB}
|
||||
-- @treturn[2] number The green channel, as returned by @{colors.unpackRGB}
|
||||
-- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB}
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0xb23399)
|
||||
-- -- => 0.7, 0.2, 0.6
|
||||
-- ```
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0.7, 0.2, 0.6)
|
||||
-- -- => 0xb23399
|
||||
-- ```
|
||||
function rgb8(r, g, b)
|
||||
if g == nil and b == nil then
|
||||
return unpackRGB( r )
|
||||
return unpackRGB(r)
|
||||
else
|
||||
return packRGB( r, g, b )
|
||||
return packRGB(r, g, b)
|
||||
end
|
||||
end
|
||||
|
@ -1,11 +1,23 @@
|
||||
-- Colours (for lovers of british spelling)
|
||||
--- Colours for lovers of British spelling.
|
||||
--
|
||||
-- @see colors
|
||||
-- @module colours
|
||||
|
||||
local colours = _ENV
|
||||
for k, v in pairs(colors) do
|
||||
colours[k] = v
|
||||
end
|
||||
|
||||
--- Grey. Written as `7` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #4C4C4C.
|
||||
--
|
||||
-- @see colors.gray
|
||||
colours.grey = colors.gray
|
||||
colours.gray = nil
|
||||
colours.gray = nil --- @local
|
||||
|
||||
--- Light grey. Written as `8` in paint files and @{term.blit}, has a
|
||||
-- default terminal colour of #999999.
|
||||
--
|
||||
-- @see colors.lightGray
|
||||
colours.lightGrey = colors.lightGray
|
||||
colours.lightGray = nil
|
||||
colours.lightGray = nil --- @local
|
||||
|
@ -1,8 +1,26 @@
|
||||
--- The commands API allows your system to directly execute [Minecraft
|
||||
-- commands][mc] and gather data from the results.
|
||||
--
|
||||
-- While one may use @{commands.exec} directly to execute a command, the
|
||||
-- commands API also provides helper methods to execute every command. For
|
||||
-- instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`.
|
||||
--
|
||||
-- @{commands.async} provides a similar interface to execute asynchronous
|
||||
-- commands. `commands.async.say("Hi!")` is equivalent to
|
||||
-- `commands.execAsync("Hi!")`.
|
||||
--
|
||||
-- [mc]: https://minecraft.gamepedia.com/Commands
|
||||
--
|
||||
-- @module commands
|
||||
|
||||
if not commands then
|
||||
error( "Cannot load command API on normal computer", 2 )
|
||||
end
|
||||
|
||||
--- The builtin commands API, without any generated command helper functions
|
||||
--
|
||||
-- This may be useful if a built-in function (such as @{commands.list}) has been
|
||||
-- overwritten by a command.
|
||||
local native = commands.native or commands
|
||||
|
||||
local function collapseArgs( bJSONIsNBT, ... )
|
||||
|
@ -1,87 +1,171 @@
|
||||
--- The Disk API allows you to interact with disk drives.
|
||||
--
|
||||
-- These functions can operate on locally attached or remote disk drives. To use
|
||||
-- a locally attached drive, specify “side” as one of the six sides
|
||||
-- (e.g. `left`); to use a remote disk drive, specify its name as printed when
|
||||
-- enabling its modem (e.g. `drive_0`).
|
||||
--
|
||||
-- **Note:** All computers (except command computers), turtles and pocket
|
||||
-- computers can be placed within a disk drive to access it's internal storage
|
||||
-- like a disk.
|
||||
--
|
||||
-- @module disk
|
||||
|
||||
local function isDrive( name )
|
||||
if type( name ) ~= "string" then
|
||||
error( "bad argument #1 (expected string, got " .. type( name ) .. ")", 3 )
|
||||
local function isDrive(name)
|
||||
if type(name) ~= "string" then
|
||||
error("bad argument #1 (expected string, got " .. type(name) .. ")", 3)
|
||||
end
|
||||
return peripheral.getType( name ) == "drive"
|
||||
return peripheral.getType(name) == "drive"
|
||||
end
|
||||
|
||||
function isPresent( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "isDiskPresent" )
|
||||
--- Checks whether any item at all is in the disk drive
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn boolean If something is in the disk drive.
|
||||
-- @usage disk.isPresent(false)
|
||||
function isPresent(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "isDiskPresent")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function getLabel( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "getDiskLabel" )
|
||||
--- Get the label of the floppy disk, record, or other media within the given
|
||||
-- disk drive.
|
||||
--
|
||||
-- If there is a computer or turtle within the drive, this will set the label as
|
||||
-- read by `os.getComputerLabel`.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn string|nil The name of the current media, or `nil` if the drive is
|
||||
-- not present or empty.
|
||||
-- @see disk.setLabel
|
||||
function getLabel(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "getDiskLabel")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function setLabel( name, label )
|
||||
if isDrive( name ) then
|
||||
peripheral.call( name, "setDiskLabel", label )
|
||||
--- Set the label of the floppy disk or other media
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @tparam string|nil label The new label of the disk
|
||||
function setLabel(name, label)
|
||||
if isDrive(name) then
|
||||
peripheral.call(name, "setDiskLabel", label)
|
||||
end
|
||||
end
|
||||
|
||||
function hasData( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "hasData" )
|
||||
--- Check whether the current disk provides a mount.
|
||||
--
|
||||
-- This will return true for disks and computers, but not records.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn boolean If the disk is present and provides a mount.
|
||||
-- @see disk.getMountPath
|
||||
function hasData(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "hasData")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function getMountPath( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "getMountPath" )
|
||||
--- Find the directory name on the local computer where the contents of the
|
||||
-- current floppy disk (or other mount) can be found.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn string|nil The mount's directory, or `nil` if the drive does not
|
||||
-- contain a floppy or computer.
|
||||
-- @see disk.hasData
|
||||
function getMountPath(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "getMountPath")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function hasAudio( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "hasAudio" )
|
||||
--- Whether the current disk is a [music disk][disk] as opposed to a floppy disk
|
||||
-- or other item.
|
||||
--
|
||||
-- If this returns true, you will can @{disk.playAudio|play} the record.
|
||||
--
|
||||
-- [disk]: https://minecraft.gamepedia.com/Music_Disc
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn boolean If the disk is present and has audio saved on it.
|
||||
function hasAudio(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "hasAudio")
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function getAudioTitle( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "getAudioTitle" )
|
||||
--- Get the title of the audio track from the music record in the drive.
|
||||
--
|
||||
-- This generally returns the same as @{disk.getLabel} for records.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn string|false|nil The track title, `false` if there is not a music
|
||||
-- record in the drive or `nil` if no drive is present.
|
||||
function getAudioTitle(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "getAudioTitle")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function playAudio( name )
|
||||
if isDrive( name ) then
|
||||
peripheral.call( name, "playAudio" )
|
||||
--- Starts playing the music record in the drive.
|
||||
--
|
||||
-- If any record is already playing on any disk drive, it stops before the
|
||||
-- target drive starts playing. The record stops when it reaches the end of the
|
||||
-- track, when it is removed from the drive, when @{disk.stopAudio} is called, or
|
||||
-- when another record is started.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @usage disk.playAudio("bottom")
|
||||
function playAudio(name)
|
||||
if isDrive(name) then
|
||||
peripheral.call(name, "playAudio")
|
||||
end
|
||||
end
|
||||
|
||||
function stopAudio( name )
|
||||
--- Stops the music record in the drive from playing, if it was started with
|
||||
-- @{disk.playAudio}.
|
||||
--
|
||||
-- @tparam string name The name o the disk drive.
|
||||
function stopAudio(name)
|
||||
if not name then
|
||||
for _, sName in ipairs( peripheral.getNames() ) do
|
||||
stopAudio( sName )
|
||||
for _, sName in ipairs(peripheral.getNames()) do
|
||||
stopAudio(sName)
|
||||
end
|
||||
else
|
||||
if isDrive( name ) then
|
||||
peripheral.call( name, "stopAudio" )
|
||||
if isDrive(name) then
|
||||
peripheral.call(name, "stopAudio")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function eject( name )
|
||||
if isDrive( name ) then
|
||||
peripheral.call( name, "ejectDisk" )
|
||||
--- Ejects any item currently in the drive, spilling it into the world as a loose item.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @usage disk.eject("bottom")
|
||||
function eject(name)
|
||||
if isDrive(name) then
|
||||
peripheral.call(name, "ejectDisk")
|
||||
end
|
||||
end
|
||||
|
||||
function getID( name )
|
||||
if isDrive( name ) then
|
||||
return peripheral.call( name, "getDiskID" )
|
||||
--- Returns a number which uniquely identifies the disk in the drive.
|
||||
--
|
||||
-- Note, unlike @{disk.getLabel}, this does not return anything for other media,
|
||||
-- such as computers or turtles.
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn string|nil The disk ID, or `nil` if the drive does not contain a floppy disk.
|
||||
function getID(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "getDiskID")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
@ -1,21 +1,46 @@
|
||||
--- The GPS API provides a method for turtles and computers to retrieve their
|
||||
-- own locations.
|
||||
--
|
||||
-- It broadcasts a PING message over @{rednet} and wait for responses. In order
|
||||
-- for this system to work, there must be at least 4 computers used as gps hosts
|
||||
-- which will respond and allow trilateration. Three of these hosts should be in
|
||||
-- a plane, and the fourth should be either above or below the other three. The
|
||||
-- three in a plane should not be in a line with each other. You can set up
|
||||
-- hosts using the gps program.
|
||||
--
|
||||
-- **Note**: When entering in the coordinates for the host you need to put in
|
||||
-- the `x`, `y`, and `z` coordinates of the computer, not the modem, as all
|
||||
-- rednet distances are measured from the block the computer is in.
|
||||
--
|
||||
-- Also note that you may choose which axes x, y, or z refers to - so long as
|
||||
-- your systems have the same definition as any GPS servers that're in range, it
|
||||
-- works just the same. For example, you might build a GPS cluster according to
|
||||
-- [this tutorial][1], using z to account for height, or you might use y to
|
||||
-- account for height in the way that Minecraft's debug screen displays.
|
||||
--
|
||||
-- [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/
|
||||
--
|
||||
-- @module gps
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
--- The channel which GPS requests and responses are broadcast on.
|
||||
CHANNEL_GPS = 65534
|
||||
|
||||
local function trilaterate( A, B, C )
|
||||
local function trilaterate(A, B, C)
|
||||
local a2b = B.vPosition - A.vPosition
|
||||
local a2c = C.vPosition - A.vPosition
|
||||
|
||||
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
|
||||
if math.abs(a2b:normalize():dot(a2c:normalize())) > 0.999 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local d = a2b:length()
|
||||
local ex = a2b:normalize( )
|
||||
local i = ex:dot( a2c )
|
||||
local i = ex:dot(a2c)
|
||||
local ey = (a2c - ex * i):normalize()
|
||||
local j = ey:dot( a2c )
|
||||
local ez = ex:cross( ey )
|
||||
local j = ey:dot(a2c)
|
||||
local ez = ex:cross(ey)
|
||||
|
||||
local r1 = A.nDistance
|
||||
local r2 = B.nDistance
|
||||
@ -28,35 +53,44 @@ local function trilaterate( A, B, C )
|
||||
|
||||
local zSquared = r1 * r1 - x * x - y * y
|
||||
if zSquared > 0 then
|
||||
local z = math.sqrt( zSquared )
|
||||
local z = math.sqrt(zSquared)
|
||||
local result1 = result + ez * z
|
||||
local result2 = result - ez * z
|
||||
|
||||
local rounded1, rounded2 = result1:round( 0.01 ), result2:round( 0.01 )
|
||||
local rounded1, rounded2 = result1:round(0.01), result2:round(0.01)
|
||||
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
|
||||
return rounded1, rounded2
|
||||
else
|
||||
return rounded1
|
||||
end
|
||||
end
|
||||
return result:round( 0.01 )
|
||||
return result:round(0.01)
|
||||
|
||||
end
|
||||
|
||||
local function narrow( p1, p2, fix )
|
||||
local dist1 = math.abs( (p1 - fix.vPosition):length() - fix.nDistance )
|
||||
local dist2 = math.abs( (p2 - fix.vPosition):length() - fix.nDistance )
|
||||
local function narrow(p1, p2, fix)
|
||||
local dist1 = math.abs((p1 - fix.vPosition):length() - fix.nDistance)
|
||||
local dist2 = math.abs((p2 - fix.vPosition):length() - fix.nDistance)
|
||||
|
||||
if math.abs(dist1 - dist2) < 0.01 then
|
||||
return p1, p2
|
||||
elseif dist1 < dist2 then
|
||||
return p1:round( 0.01 )
|
||||
return p1:round(0.01)
|
||||
else
|
||||
return p2:round( 0.01 )
|
||||
return p2:round(0.01)
|
||||
end
|
||||
end
|
||||
|
||||
function locate( _nTimeout, _bDebug )
|
||||
--- Tries to retrieve the computer or turtles own location.
|
||||
--
|
||||
-- @tparam[opt] number timeout The maximum time taken to establish our
|
||||
-- position. Defaults to 2 seconds if not specified.
|
||||
-- @tparam[opt] boolean debug Print debugging messages
|
||||
-- @treturn[1] number This computer's `x` position.
|
||||
-- @treturn[1] number This computer's `y` position.
|
||||
-- @treturn[1] number This computer's `z` position.
|
||||
-- @treturn[2] nil If the position could not be established.
|
||||
function locate(_nTimeout, _bDebug)
|
||||
expect(1, _nTimeout, "number", "nil")
|
||||
expect(2, _bDebug, "boolean", "nil")
|
||||
-- Let command computers use their magic fourth-wall-breaking special abilities
|
||||
@ -66,8 +100,8 @@ function locate( _nTimeout, _bDebug )
|
||||
|
||||
-- Find a modem
|
||||
local sModemSide = nil
|
||||
for _, sSide in ipairs( rs.getSides() ) do
|
||||
if peripheral.getType( sSide ) == "modem" and peripheral.call( sSide, "isWireless" ) then
|
||||
for _, sSide in ipairs(rs.getSides()) do
|
||||
if peripheral.getType(sSide) == "modem" and peripheral.call(sSide, "isWireless") then
|
||||
sModemSide = sSide
|
||||
break
|
||||
end
|
||||
@ -75,30 +109,30 @@ function locate( _nTimeout, _bDebug )
|
||||
|
||||
if sModemSide == nil then
|
||||
if _bDebug then
|
||||
print( "No wireless modem attached" )
|
||||
print("No wireless modem attached")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if _bDebug then
|
||||
print( "Finding position..." )
|
||||
print("Finding position...")
|
||||
end
|
||||
|
||||
-- Open GPS channel to listen for ping responses
|
||||
local modem = peripheral.wrap( sModemSide )
|
||||
local modem = peripheral.wrap(sModemSide)
|
||||
local bCloseChannel = false
|
||||
if not modem.isOpen( CHANNEL_GPS ) then
|
||||
modem.open( CHANNEL_GPS )
|
||||
if not modem.isOpen(CHANNEL_GPS) then
|
||||
modem.open(CHANNEL_GPS)
|
||||
bCloseChannel = true
|
||||
end
|
||||
|
||||
-- Send a ping to listening GPS hosts
|
||||
modem.transmit( CHANNEL_GPS, CHANNEL_GPS, "PING" )
|
||||
modem.transmit(CHANNEL_GPS, CHANNEL_GPS, "PING")
|
||||
|
||||
-- Wait for the responses
|
||||
local tFixes = {}
|
||||
local pos1, pos2 = nil, nil
|
||||
local timeout = os.startTimer( _nTimeout or 2 )
|
||||
local timeout = os.startTimer(_nTimeout or 2)
|
||||
while true do
|
||||
local e, p1, p2, p3, p4, p5 = os.pullEvent()
|
||||
if e == "modem_message" then
|
||||
@ -107,19 +141,19 @@ function locate( _nTimeout, _bDebug )
|
||||
if sSide == sModemSide and sChannel == CHANNEL_GPS and sReplyChannel == CHANNEL_GPS and nDistance then
|
||||
-- Received the correct message from the correct modem: use it to determine position
|
||||
if type(tMessage) == "table" and #tMessage == 3 and tonumber(tMessage[1]) and tonumber(tMessage[2]) and tonumber(tMessage[3]) then
|
||||
local tFix = { vPosition = vector.new( tMessage[1], tMessage[2], tMessage[3] ), nDistance = nDistance }
|
||||
local tFix = { vPosition = vector.new(tMessage[1], tMessage[2], tMessage[3]), nDistance = nDistance }
|
||||
if _bDebug then
|
||||
print( tFix.nDistance .. " metres from " .. tostring( tFix.vPosition ) )
|
||||
print(tFix.nDistance .. " metres from " .. tostring(tFix.vPosition))
|
||||
end
|
||||
if tFix.nDistance == 0 then
|
||||
pos1, pos2 = tFix.vPosition, nil
|
||||
else
|
||||
table.insert( tFixes, tFix )
|
||||
table.insert(tFixes, tFix)
|
||||
if #tFixes >= 3 then
|
||||
if not pos1 then
|
||||
pos1, pos2 = trilaterate( tFixes[1], tFixes[2], tFixes[#tFixes] )
|
||||
pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[#tFixes])
|
||||
else
|
||||
pos1, pos2 = narrow( pos1, pos2, tFixes[#tFixes] )
|
||||
pos1, pos2 = narrow(pos1, pos2, tFixes[#tFixes])
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -141,24 +175,24 @@ function locate( _nTimeout, _bDebug )
|
||||
|
||||
-- Close the channel, if we opened one
|
||||
if bCloseChannel then
|
||||
modem.close( CHANNEL_GPS )
|
||||
modem.close(CHANNEL_GPS)
|
||||
end
|
||||
|
||||
-- Return the response
|
||||
if pos1 and pos2 then
|
||||
if _bDebug then
|
||||
print( "Ambiguous position" )
|
||||
print( "Could be " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z .. " or " .. pos2.x .. "," .. pos2.y .. "," .. pos2.z )
|
||||
print("Ambiguous position")
|
||||
print("Could be " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z .. " or " .. pos2.x .. "," .. pos2.y .. "," .. pos2.z)
|
||||
end
|
||||
return nil
|
||||
elseif pos1 then
|
||||
if _bDebug then
|
||||
print( "Position is " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z )
|
||||
print("Position is " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z)
|
||||
end
|
||||
return pos1.x, pos1.y, pos1.z
|
||||
else
|
||||
if _bDebug then
|
||||
print( "Could not determine position" )
|
||||
print("Could not determine position")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
@ -1,24 +1,46 @@
|
||||
--- Provides an API to read help files.
|
||||
--
|
||||
-- @module help
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local sPath = "/rom/help"
|
||||
|
||||
--- Returns a colon-separated list of directories where help files are searched
|
||||
-- for. All directories are absolute.
|
||||
--
|
||||
-- @treturn string The current help search path, separated by colons.
|
||||
-- @see help.setPath
|
||||
function path()
|
||||
return sPath
|
||||
end
|
||||
|
||||
function setPath( _sPath )
|
||||
--- Sets the colon-seperated list of directories where help files are searched
|
||||
-- for to `newPath`
|
||||
--
|
||||
-- @tparam string newPath The new path to use.
|
||||
-- @usage help.setPath( "/disk/help/" )
|
||||
-- @usage help.setPath( help.path() .. ":/myfolder/help/" )
|
||||
-- @see help.path
|
||||
function setPath(_sPath)
|
||||
expect(1, _sPath, "string")
|
||||
sPath = _sPath
|
||||
end
|
||||
|
||||
function lookup( _sTopic )
|
||||
--- Returns the location of the help file for the given topic.
|
||||
--
|
||||
-- @tparam string topic The topic to find
|
||||
-- @treturn string|nil The path to the given topic's help file, or `nil` if it
|
||||
-- cannot be found.
|
||||
-- @usage print(help.lookup("disk"))
|
||||
function lookup(_sTopic)
|
||||
expect(1, _sTopic, "string")
|
||||
-- Look on the path variable
|
||||
for sPath in string.gmatch(sPath, "[^:]+") do
|
||||
sPath = fs.combine( sPath, _sTopic )
|
||||
if fs.exists( sPath ) and not fs.isDir( sPath ) then
|
||||
sPath = fs.combine(sPath, _sTopic)
|
||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||
return sPath
|
||||
elseif fs.exists( sPath .. ".txt" ) and not fs.isDir( sPath .. ".txt" ) then
|
||||
elseif fs.exists(sPath .. ".txt") and not fs.isDir(sPath .. ".txt") then
|
||||
return sPath .. ".txt"
|
||||
end
|
||||
end
|
||||
@ -27,23 +49,26 @@ function lookup( _sTopic )
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns a list of topics that can be looked up and/or displayed.
|
||||
--
|
||||
-- @treturn table A list of topics in alphabetical order.
|
||||
function topics()
|
||||
-- Add index
|
||||
local tItems = {
|
||||
[ "index" ] = true,
|
||||
["index"] = true,
|
||||
}
|
||||
|
||||
-- Add topics from the path
|
||||
for sPath in string.gmatch(sPath, "[^:]+") do
|
||||
if fs.isDir( sPath ) then
|
||||
local tList = fs.list( sPath )
|
||||
for _, sFile in pairs( tList ) do
|
||||
if string.sub( sFile, 1, 1 ) ~= "." then
|
||||
if not fs.isDir( fs.combine( sPath, sFile ) ) then
|
||||
if fs.isDir(sPath) then
|
||||
local tList = fs.list(sPath)
|
||||
for _, sFile in pairs(tList) do
|
||||
if string.sub(sFile, 1, 1) ~= "." then
|
||||
if not fs.isDir(fs.combine(sPath, sFile)) then
|
||||
if #sFile > 4 and sFile:sub(-4) == ".txt" then
|
||||
sFile = sFile:sub(1, -5)
|
||||
end
|
||||
tItems[ sFile ] = true
|
||||
tItems[sFile] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -52,21 +77,26 @@ function topics()
|
||||
|
||||
-- Sort and return
|
||||
local tItemList = {}
|
||||
for sItem in pairs( tItems ) do
|
||||
table.insert( tItemList, sItem )
|
||||
for sItem in pairs(tItems) do
|
||||
table.insert(tItemList, sItem)
|
||||
end
|
||||
table.sort( tItemList )
|
||||
table.sort(tItemList)
|
||||
return tItemList
|
||||
end
|
||||
|
||||
function completeTopic( sText )
|
||||
--- Returns a list of topic endings that match the prefix. Can be used with
|
||||
-- `read` to allow input of a help topic.
|
||||
--
|
||||
-- @tparam string prefix The prefix to match
|
||||
-- @treturn table A list of matching topics.
|
||||
function completeTopic(sText)
|
||||
expect(1, sText, "string")
|
||||
local tTopics = topics()
|
||||
local tResults = {}
|
||||
for n = 1, #tTopics do
|
||||
local sTopic = tTopics[n]
|
||||
if #sTopic > #sText and string.sub( sTopic, 1, #sText ) == sText then
|
||||
table.insert( tResults, string.sub( sTopic, #sText + 1 ) )
|
||||
if #sTopic > #sText and string.sub(sTopic, 1, #sText) == sText then
|
||||
table.insert(tResults, string.sub(sTopic, #sText + 1))
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
|
@ -1,6 +1,10 @@
|
||||
-- Definition for the IO API
|
||||
--- Emulates Lua's standard [io library][io].
|
||||
--
|
||||
-- [io]: https://www.lua.org/manual/5.1/manual.html#5.7
|
||||
--
|
||||
-- @module io
|
||||
|
||||
local expect, typeOf = dofile("rom/modules/main/cc/expect.lua").expect, _G.type
|
||||
local expect, type_of = dofile("rom/modules/main/cc/expect.lua").expect, _G.type
|
||||
|
||||
--- If we return nil then close the file, as we've reached the end.
|
||||
-- We use this weird wrapper function as we wish to preserve the varargs
|
||||
@ -9,6 +13,9 @@ local function checkResult(handle, ...)
|
||||
return ...
|
||||
end
|
||||
|
||||
--- A file handle which can be read or written to.
|
||||
--
|
||||
-- @type Handle
|
||||
local handleMetatable
|
||||
handleMetatable = {
|
||||
__name = "FILE*",
|
||||
@ -20,10 +27,17 @@ handleMetatable = {
|
||||
return "file (" .. hash .. ")"
|
||||
end
|
||||
end,
|
||||
|
||||
__index = {
|
||||
--- Close this file handle, freeing any resources it uses.
|
||||
--
|
||||
-- @treturn[1] true If this handle was successfully closed.
|
||||
-- @treturn[2] nil If this file handle could not be closed.
|
||||
-- @treturn[2] string The reason it could not be closed.
|
||||
-- @throws If this handle was already closed.
|
||||
close = function(self)
|
||||
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
|
||||
if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
|
||||
end
|
||||
if self._closed then error("attempt to use a closed file", 2) end
|
||||
|
||||
@ -36,18 +50,24 @@ handleMetatable = {
|
||||
return nil, "attempt to close standard stream"
|
||||
end
|
||||
end,
|
||||
|
||||
--- Flush any buffered output, forcing it to be written to the file
|
||||
--
|
||||
-- @throws If the handle has been closed
|
||||
flush = function(self)
|
||||
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
|
||||
if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
|
||||
end
|
||||
if self._closed then error("attempt to use a closed file", 2) end
|
||||
|
||||
local handle = self._handle
|
||||
if handle.flush then handle.flush() end
|
||||
return true
|
||||
end,
|
||||
|
||||
lines = function(self, ...)
|
||||
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
|
||||
if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
|
||||
end
|
||||
if self._closed then error("attempt to use a closed file", 2) end
|
||||
|
||||
@ -57,9 +77,10 @@ handleMetatable = {
|
||||
local args = table.pack(...)
|
||||
return function() return checkResult(self, self:read(table.unpack(args, 1, args.n))) end
|
||||
end,
|
||||
|
||||
read = function(self, ...)
|
||||
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
|
||||
if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
|
||||
end
|
||||
if self._closed then error("attempt to use a closed file", 2) end
|
||||
|
||||
@ -71,9 +92,9 @@ handleMetatable = {
|
||||
for i = 1, n do
|
||||
local arg = select(i, ...)
|
||||
local res
|
||||
if typeOf(arg) == "number" then
|
||||
if type_of(arg) == "number" then
|
||||
if handle.read then res = handle.read(arg) end
|
||||
elseif typeOf(arg) == "string" then
|
||||
elseif type_of(arg) == "string" then
|
||||
local format = arg:gsub("^%*", ""):sub(1, 1)
|
||||
|
||||
if format == "l" then
|
||||
@ -88,7 +109,7 @@ handleMetatable = {
|
||||
error("bad argument #" .. i .. " (invalid format)", 2)
|
||||
end
|
||||
else
|
||||
error("bad argument #" .. i .. " (expected string, got " .. typeOf(arg) .. ")", 2)
|
||||
error("bad argument #" .. i .. " (expected string, got " .. type_of(arg) .. ")", 2)
|
||||
end
|
||||
|
||||
output[i] = res
|
||||
@ -99,9 +120,10 @@ handleMetatable = {
|
||||
if n == 0 and handle.readLine then return handle.readLine() end
|
||||
return table.unpack(output, 1, n)
|
||||
end,
|
||||
|
||||
seek = function(self, whence, offset)
|
||||
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
|
||||
if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
|
||||
end
|
||||
if self._closed then error("attempt to use a closed file", 2) end
|
||||
|
||||
@ -111,10 +133,18 @@ handleMetatable = {
|
||||
-- It's a tail call, so error positions are preserved
|
||||
return handle.seek(whence, offset)
|
||||
end,
|
||||
|
||||
setvbuf = function(self, mode, size) end,
|
||||
|
||||
--- Write one or more values to the file
|
||||
--
|
||||
-- @tparam string|number ... The values to write.
|
||||
-- @treturn[1] Handle The current file, allowing chained calls.
|
||||
-- @treturn[2] nil If the file could not be written to.
|
||||
-- @treturn[2] string The error message which occurred while writing.
|
||||
write = function(self, ...)
|
||||
if typeOf(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(self) .. ")", 2)
|
||||
if type_of(self) ~= "table" or getmetatable(self) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(self) .. ")", 2)
|
||||
end
|
||||
if self._closed then error("attempt to use a closed file", 2) end
|
||||
|
||||
@ -156,41 +186,88 @@ local defaultError = setmetatable({
|
||||
local currentInput = defaultInput
|
||||
local currentOutput = defaultOutput
|
||||
|
||||
--- A file handle representing the "standard input". Reading from this
|
||||
-- file will prompt the user for input.
|
||||
stdin = defaultInput
|
||||
|
||||
--- A file handle representing the "standard output". Writing to this
|
||||
-- file will display the written text to the screen.
|
||||
stdout = defaultOutput
|
||||
|
||||
--- A file handle representing the "standard error" stream.
|
||||
--
|
||||
-- One may use this to display error messages, writing to it will display
|
||||
-- them on the terminal.
|
||||
stderr = defaultError
|
||||
|
||||
function close(_file)
|
||||
if _file == nil then return currentOutput:close() end
|
||||
--- Closes the provided file handle.
|
||||
--
|
||||
-- @tparam[opt] Handle file The file handle to close, defaults to the
|
||||
-- current output file.
|
||||
--
|
||||
-- @see Handle:close
|
||||
-- @see io.output
|
||||
function close(file)
|
||||
if file == nil then return currentOutput:close() end
|
||||
|
||||
if typeOf(_file) ~= "table" or getmetatable(_file) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(_file) .. ")", 2)
|
||||
if type_of(file) ~= "table" or getmetatable(file) ~= handleMetatable then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(file) .. ")", 2)
|
||||
end
|
||||
return _file:close()
|
||||
return file:close()
|
||||
end
|
||||
|
||||
--- Flushes the current output file.
|
||||
--
|
||||
-- @see Handle:flush
|
||||
-- @see io.output
|
||||
function flush()
|
||||
return currentOutput:flush()
|
||||
end
|
||||
|
||||
function input(_arg)
|
||||
if typeOf(_arg) == "string" then
|
||||
local res, err = open(_arg, "rb")
|
||||
--- Get or set the current input file.
|
||||
--
|
||||
-- @tparam[opt] Handle|string file The new input file, either as a file path or pre-existing handle.
|
||||
-- @treturn Handle The current input file.
|
||||
-- @throws If the provided filename cannot be opened for reading.
|
||||
function input(file)
|
||||
if type_of(file) == "string" then
|
||||
local res, err = open(file, "rb")
|
||||
if not res then error(err, 2) end
|
||||
currentInput = res
|
||||
elseif typeOf(_arg) == "table" and getmetatable(_arg) == handleMetatable then
|
||||
currentInput = _arg
|
||||
elseif _arg ~= nil then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(_arg) .. ")", 2)
|
||||
elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
|
||||
currentInput = file
|
||||
elseif file ~= nil then
|
||||
error("bad fileument #1 (FILE expected, got " .. type_of(file) .. ")", 2)
|
||||
end
|
||||
|
||||
return currentInput
|
||||
end
|
||||
|
||||
function lines(_sFileName)
|
||||
expect(1, _sFileName, "string", "nil")
|
||||
if _sFileName then
|
||||
local ok, err = open(_sFileName, "rb")
|
||||
--- Opens the given file name in read mode and returns an iterator that,
|
||||
-- each time it is called, returns a new line from the file.
|
||||
--
|
||||
-- This can be used in a for loop to iterate over all lines of a file:
|
||||
--
|
||||
-- ```lua
|
||||
-- for line in io.lines(filename) do print(line) end
|
||||
-- ```
|
||||
--
|
||||
-- Once the end of the file has been reached, @{nil} will be
|
||||
-- returned. The file is automatically closed.
|
||||
--
|
||||
-- If no file name is given, the @{io.input|current input} will be used
|
||||
-- instead. In this case, the handle is not used.
|
||||
--
|
||||
-- @tparam[opt] string filename The name of the file to extract lines from
|
||||
-- @treturn function():string|nil The line iterator.
|
||||
-- @throws If the file cannot be opened for reading
|
||||
--
|
||||
-- @see Handle:lines
|
||||
-- @see io.input
|
||||
function lines(filename)
|
||||
expect(1, filename, "string", "nil")
|
||||
if filename then
|
||||
local ok, err = open(filename, "rb")
|
||||
if not ok then error(err, 2) end
|
||||
|
||||
-- We set this magic flag to mark this file as being opened by io.lines and so should be
|
||||
@ -202,38 +279,72 @@ function lines(_sFileName)
|
||||
end
|
||||
end
|
||||
|
||||
function open(_sPath, _sMode)
|
||||
expect(1, _sPath, "string")
|
||||
expect(2, _sMode, "string", "nil")
|
||||
--- Open a file with the given mode, either returning a new file handle
|
||||
-- or @{nil}, plus an error message.
|
||||
--
|
||||
-- The `mode` string can be any of the following:
|
||||
-- - **"r"**: Read mode
|
||||
-- - **"w"**: Write mode
|
||||
-- - **"w"**: Append mode
|
||||
--
|
||||
-- The mode may also have a `b` at the end, which opens the file in "binary
|
||||
-- mode". This allows you to read binary files, as well as seek within a file.
|
||||
--
|
||||
-- @tparam string filename The name of the file to open.
|
||||
-- @tparam[opt] string mode The mode to open the file with. This defaults to `rb`.
|
||||
-- @treturn[1] Handle The opened file.
|
||||
-- @treturn[2] nil In case of an error.
|
||||
-- @treturn[2] string The reason the file could not be opened.
|
||||
function open(filename, mode)
|
||||
expect(1, filename, "string")
|
||||
expect(2, mode, "string", "nil")
|
||||
|
||||
local sMode = _sMode and _sMode:gsub("%+", "") or "rb"
|
||||
local file, err = fs.open(_sPath, sMode)
|
||||
local sMode = mode and mode:gsub("%+", "") or "rb"
|
||||
local file, err = fs.open(filename, sMode)
|
||||
if not file then return nil, err end
|
||||
|
||||
return setmetatable({ _handle = file }, handleMetatable)
|
||||
end
|
||||
|
||||
function output(_arg)
|
||||
if typeOf(_arg) == "string" then
|
||||
local res, err = open(_arg, "w")
|
||||
--- Get or set the current output file.
|
||||
--
|
||||
-- @tparam[opt] Handle|string file The new output file, either as a file path or pre-existing handle.
|
||||
-- @treturn Handle The current output file.
|
||||
-- @throws If the provided filename cannot be opened for writing.
|
||||
function output(file)
|
||||
if type_of(file) == "string" then
|
||||
local res, err = open(file, "w")
|
||||
if not res then error(err, 2) end
|
||||
currentOutput = res
|
||||
elseif typeOf(_arg) == "table" and getmetatable(_arg) == handleMetatable then
|
||||
currentOutput = _arg
|
||||
elseif _arg ~= nil then
|
||||
error("bad argument #1 (FILE expected, got " .. typeOf(_arg) .. ")", 2)
|
||||
elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
|
||||
currentOutput = file
|
||||
elseif file ~= nil then
|
||||
error("bad argument #1 (FILE expected, got " .. type_of(file) .. ")", 2)
|
||||
end
|
||||
|
||||
return currentOutput
|
||||
end
|
||||
|
||||
--- Read from the currently opened input file.
|
||||
--
|
||||
-- This is equivalent to `io.input():read(...)`. See @{Handle:read|the
|
||||
-- documentation} there for full details.
|
||||
--
|
||||
-- @tparam string ... The formats to read, defaulting to a whole line.
|
||||
-- @treturn (string|nil)... The data read, or @{nil} if nothing can be read.
|
||||
function read(...)
|
||||
return currentInput:read(...)
|
||||
end
|
||||
|
||||
function type(handle)
|
||||
if typeOf(handle) == "table" and getmetatable(handle) == handleMetatable then
|
||||
if handle._closed then
|
||||
--- Checks whether `handle` is a given file handle, and determine if it is open
|
||||
-- or not.
|
||||
--
|
||||
-- @param obj The value to check
|
||||
-- @treturn string|nil `"file"` if this is an open file, `"closed file"` if it
|
||||
-- is a closed file handle, or `nil` if not a file handle.
|
||||
function type(obj)
|
||||
if type_of(obj) == "table" and getmetatable(obj) == handleMetatable then
|
||||
if obj._closed then
|
||||
return "closed file"
|
||||
else
|
||||
return "file"
|
||||
@ -242,6 +353,12 @@ function type(handle)
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Write to the currently opened output file.
|
||||
--
|
||||
-- This is equivalent to `io.output():write(...)`. See @{Handle:write|the
|
||||
-- documentation} there for full details.
|
||||
--
|
||||
-- @tparam string ... The strings to write
|
||||
function write(...)
|
||||
return currentOutput:write(...)
|
||||
end
|
||||
|
@ -1,12 +1,11 @@
|
||||
--- Key codes for ComputerCraft
|
||||
--- The Keys API provides a table of numerical codes corresponding to keyboard
|
||||
-- keys, suitable for decoding key events.
|
||||
--
|
||||
-- This is derived from the GLFW list of key codes, and mostly created via
|
||||
-- a couple of regexes.
|
||||
-- These values are not guaranteed to remain the same between versions. It is
|
||||
-- recommended that you use the constants provided by this file, rather than
|
||||
-- the underlying numerical values.
|
||||
--
|
||||
-- Note that this is technically incompatible with previous versions of CC, as
|
||||
-- they relied on Minecraft's character mappings. However, if CC emulators have
|
||||
-- taught me anything, it's that emulating LWJGL's weird key handling is nigh-on
|
||||
-- impossible.
|
||||
-- @module keys
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
@ -137,10 +136,15 @@ for nKey, sKey in pairs( tKeys ) do
|
||||
end
|
||||
|
||||
-- Alias some keys for ease-of-use and backwards compatibility
|
||||
keys["return"] = keys.enter
|
||||
keys.scollLock = keys.scrollLock
|
||||
keys.cimcumflex = keys.circumflex
|
||||
keys["return"] = keys.enter --- @local
|
||||
keys.scollLock = keys.scrollLock --- @local
|
||||
keys.cimcumflex = keys.circumflex --- @local
|
||||
|
||||
--- Translates a numerical key code to a human-readable name. The human-readable
|
||||
-- name is one of the constants in the keys API.
|
||||
--
|
||||
-- @tparam number code The key code to look up.
|
||||
-- @treturn string|nil The name of the key, or `nil` if not a valid key code.
|
||||
function getName( _nKey )
|
||||
expect(1, _nKey, "number")
|
||||
return tKeys[ _nKey ]
|
||||
|
@ -1,75 +1,117 @@
|
||||
--- An API for advanced systems which can draw pixels and lines, load and draw
|
||||
-- image files. You can use the `colors` API for easier color manipulation.
|
||||
--
|
||||
-- @module paintutils
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local function drawPixelInternal( xPos, yPos )
|
||||
term.setCursorPos( xPos, yPos )
|
||||
local function drawPixelInternal(xPos, yPos)
|
||||
term.setCursorPos(xPos, yPos)
|
||||
term.write(" ")
|
||||
end
|
||||
|
||||
local tColourLookup = {}
|
||||
for n = 1, 16 do
|
||||
tColourLookup[ string.byte( "0123456789abcdef", n, n ) ] = 2 ^ (n - 1)
|
||||
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
||||
end
|
||||
|
||||
local function parseLine( tImageArg, sLine )
|
||||
local function parseLine(tImageArg, sLine)
|
||||
local tLine = {}
|
||||
for x = 1, sLine:len() do
|
||||
tLine[x] = tColourLookup[ string.byte(sLine, x, x) ] or 0
|
||||
tLine[x] = tColourLookup[string.byte(sLine, x, x)] or 0
|
||||
end
|
||||
table.insert( tImageArg, tLine )
|
||||
table.insert(tImageArg, tLine)
|
||||
end
|
||||
|
||||
function parseImage( sRawData )
|
||||
expect(1, sRawData, "string")
|
||||
--- Parses an image from a multi-line string
|
||||
--
|
||||
-- @tparam string image The string containing the raw-image data.
|
||||
-- @treturn table The parsed image data, suitable for use with
|
||||
-- @{paintutils.drawImage}.
|
||||
function parseImage(image)
|
||||
expect(1, image, "string")
|
||||
local tImage = {}
|
||||
for sLine in ( sRawData .. "\n" ):gmatch( "(.-)\n" ) do -- read each line like original file handling did
|
||||
parseLine( tImage, sLine )
|
||||
for sLine in (image .. "\n"):gmatch("(.-)\n") do
|
||||
parseLine(tImage, sLine)
|
||||
end
|
||||
return tImage
|
||||
end
|
||||
|
||||
function loadImage( sPath )
|
||||
expect(1, sPath, "string")
|
||||
--- Loads an image from a file.
|
||||
--
|
||||
-- You can create a file suitable for being loaded using the `paint` program.
|
||||
--
|
||||
-- @tparam string path The file to load.
|
||||
--
|
||||
-- @treturn table|nil The parsed image data, suitable for use with
|
||||
-- @{paintutils.drawImage}, or `nil` if the file does not exist.
|
||||
function loadImage(path)
|
||||
expect(1, path, "string")
|
||||
|
||||
if fs.exists( sPath ) then
|
||||
local file = io.open( sPath, "r" )
|
||||
if fs.exists(path) then
|
||||
local file = io.open(path, "r")
|
||||
local sContent = file:read("*a")
|
||||
file:close()
|
||||
return parseImage( sContent ) -- delegate image parse to parseImage
|
||||
return parseImage(sContent)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function drawPixel( xPos, yPos, nColour )
|
||||
--- Draws a single pixel to the current term at the specified position.
|
||||
--
|
||||
-- Be warned, this may change the position of the cursor and the current
|
||||
-- background colour. You should not expect either to be preserved.
|
||||
--
|
||||
-- @tparam number xPos The x position to draw at, where 1 is the far left.
|
||||
-- @tparam number yPos The y position to draw at, where 1 is the very top.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
function drawPixel(xPos, yPos, colour)
|
||||
expect(1, xPos, "number")
|
||||
expect(2, yPos, "number")
|
||||
expect(3, nColour, "number", "nil")
|
||||
if nColour then
|
||||
term.setBackgroundColor( nColour )
|
||||
expect(3, colour, "number", "nil")
|
||||
|
||||
if type(xPos) ~= "number" then error("bad argument #1 (expected number, got " .. type(xPos) .. ")", 2) end
|
||||
if type(yPos) ~= "number" then error("bad argument #2 (expected number, got " .. type(yPos) .. ")", 2) end
|
||||
if colour ~= nil and type(colour) ~= "number" then error("bad argument #3 (expected number, got " .. type(colour) .. ")", 2) end
|
||||
if colour then
|
||||
term.setBackgroundColor(colour)
|
||||
end
|
||||
return drawPixelInternal( xPos, yPos )
|
||||
return drawPixelInternal(xPos, yPos)
|
||||
end
|
||||
|
||||
function drawLine( startX, startY, endX, endY, nColour )
|
||||
--- Draws a straight line from the start to end position.
|
||||
--
|
||||
-- Be warned, this may change the position of the cursor and the current
|
||||
-- background colour. You should not expect either to be preserved.
|
||||
--
|
||||
-- @tparam number startX The starting x position of the line.
|
||||
-- @tparam number startY The starting y position of the line.
|
||||
-- @tparam number endX The end x position of the line.
|
||||
-- @tparam number endY The end y position of the line.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
function drawLine(startX, startY, endX, endY, colour)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
expect(3, endX, "number")
|
||||
expect(4, endY, "number")
|
||||
expect(5, nColour, "number", "nil")
|
||||
expect(5, colour, "number", "nil")
|
||||
|
||||
startX = math.floor(startX)
|
||||
startY = math.floor(startY)
|
||||
endX = math.floor(endX)
|
||||
endY = math.floor(endY)
|
||||
|
||||
if nColour then
|
||||
term.setBackgroundColor( nColour )
|
||||
if colour then
|
||||
term.setBackgroundColor(colour)
|
||||
end
|
||||
if startX == endX and startY == endY then
|
||||
drawPixelInternal( startX, startY )
|
||||
drawPixelInternal(startX, startY)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = math.min( startX, endX )
|
||||
local minX = math.min(startX, endX)
|
||||
local maxX, minY, maxY
|
||||
if minX == startX then
|
||||
minY = startY
|
||||
@ -90,7 +132,7 @@ function drawLine( startX, startY, endX, endY, nColour )
|
||||
local y = minY
|
||||
local dy = yDiff / xDiff
|
||||
for x = minX, maxX do
|
||||
drawPixelInternal( x, math.floor( y + 0.5 ) )
|
||||
drawPixelInternal(x, math.floor(y + 0.5))
|
||||
y = y + dy
|
||||
end
|
||||
else
|
||||
@ -98,19 +140,31 @@ function drawLine( startX, startY, endX, endY, nColour )
|
||||
local dx = xDiff / yDiff
|
||||
if maxY >= minY then
|
||||
for y = minY, maxY do
|
||||
drawPixelInternal( math.floor( x + 0.5 ), y )
|
||||
drawPixelInternal(math.floor(x + 0.5), y)
|
||||
x = x + dx
|
||||
end
|
||||
else
|
||||
for y = minY, maxY, -1 do
|
||||
drawPixelInternal( math.floor( x + 0.5 ), y )
|
||||
drawPixelInternal(math.floor(x + 0.5), y)
|
||||
x = x - dx
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function drawBox( startX, startY, endX, endY, nColour )
|
||||
--- Draws the outline of a box on the current term from the specified start
|
||||
-- position to the specified end position.
|
||||
--
|
||||
-- Be warned, this may change the position of the cursor and the current
|
||||
-- background colour. You should not expect either to be preserved.
|
||||
--
|
||||
-- @tparam number startX The starting x position of the line.
|
||||
-- @tparam number startY The starting y position of the line.
|
||||
-- @tparam number endX The end x position of the line.
|
||||
-- @tparam number endY The end y position of the line.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
function drawBox(startX, startY, endX, endY, nColour)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
expect(3, endX, "number")
|
||||
@ -123,14 +177,14 @@ function drawBox( startX, startY, endX, endY, nColour )
|
||||
endY = math.floor(endY)
|
||||
|
||||
if nColour then
|
||||
term.setBackgroundColor( nColour )
|
||||
term.setBackgroundColor(nColour)
|
||||
end
|
||||
if startX == endX and startY == endY then
|
||||
drawPixelInternal( startX, startY )
|
||||
drawPixelInternal(startX, startY)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = math.min( startX, endX )
|
||||
local minX = math.min(startX, endX)
|
||||
local maxX, minY, maxY
|
||||
if minX == startX then
|
||||
minY = startY
|
||||
@ -143,19 +197,30 @@ function drawBox( startX, startY, endX, endY, nColour )
|
||||
end
|
||||
|
||||
for x = minX, maxX do
|
||||
drawPixelInternal( x, minY )
|
||||
drawPixelInternal( x, maxY )
|
||||
drawPixelInternal(x, minY)
|
||||
drawPixelInternal(x, maxY)
|
||||
end
|
||||
|
||||
if maxY - minY >= 2 then
|
||||
for y = minY + 1, maxY - 1 do
|
||||
drawPixelInternal( minX, y )
|
||||
drawPixelInternal( maxX, y )
|
||||
drawPixelInternal(minX, y)
|
||||
drawPixelInternal(maxX, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function drawFilledBox( startX, startY, endX, endY, nColour )
|
||||
--- Draws a filled box on the current term from the specified start position to
|
||||
-- the specified end position.
|
||||
--
|
||||
-- Be warned, this may change the position of the cursor and the current
|
||||
-- background colour. You should not expect either to be preserved.
|
||||
--
|
||||
-- @tparam number startX The starting x position of the line.
|
||||
-- @tparam number startY The starting y position of the line.
|
||||
-- @tparam number endX The end x position of the line.
|
||||
-- @tparam number endY The end y position of the line.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
function drawFilledBox(startX, startY, endX, endY, nColour)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
expect(3, endX, "number")
|
||||
@ -168,14 +233,14 @@ function drawFilledBox( startX, startY, endX, endY, nColour )
|
||||
endY = math.floor(endY)
|
||||
|
||||
if nColour then
|
||||
term.setBackgroundColor( nColour )
|
||||
term.setBackgroundColor(nColour)
|
||||
end
|
||||
if startX == endX and startY == endY then
|
||||
drawPixelInternal( startX, startY )
|
||||
drawPixelInternal(startX, startY)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = math.min( startX, endX )
|
||||
local minX = math.min(startX, endX)
|
||||
local maxX, minY, maxY
|
||||
if minX == startX then
|
||||
minY = startY
|
||||
@ -189,21 +254,26 @@ function drawFilledBox( startX, startY, endX, endY, nColour )
|
||||
|
||||
for x = minX, maxX do
|
||||
for y = minY, maxY do
|
||||
drawPixelInternal( x, y )
|
||||
drawPixelInternal(x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function drawImage( tImage, xPos, yPos )
|
||||
expect(1, tImage, "table")
|
||||
--- Draw an image loaded by @{paintutils.parseImage} or @{paintutils.loadImage}.
|
||||
--
|
||||
-- @tparam table image The parsed image data.
|
||||
-- @tparam number xPos The x position to start drawing at.
|
||||
-- @tparam number xPos The y position to start drawing at.
|
||||
function drawImage(image, xPos, yPos)
|
||||
expect(1, image, "table")
|
||||
expect(2, xPos, "number")
|
||||
expect(3, yPos, "number")
|
||||
for y = 1, #tImage do
|
||||
local tLine = tImage[y]
|
||||
for y = 1, #image do
|
||||
local tLine = image[y]
|
||||
for x = 1, #tLine do
|
||||
if tLine[x] > 0 then
|
||||
term.setBackgroundColor( tLine[x] )
|
||||
drawPixelInternal( x + xPos - 1, y + yPos - 1 )
|
||||
term.setBackgroundColor(tLine[x])
|
||||
drawPixelInternal(x + xPos - 1, y + yPos - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,11 +1,26 @@
|
||||
--- Provides a simple implementation of multitasking.
|
||||
--
|
||||
-- Functions are not actually executed simultaniously, but rather this API will
|
||||
-- automatically switch between them whenever they yield (eg whenever they call
|
||||
-- @{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
|
||||
-- functions that call that, etc - basically, anything that causes the function
|
||||
-- to "pause").
|
||||
--
|
||||
-- Each function executed in "parallel" gets its own copy of the event queue,
|
||||
-- and so "event consuming" functions (again, mostly anything that causes the
|
||||
-- script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
|
||||
-- etc) can safely be used in one without affecting the event queue accessed by
|
||||
-- the other.
|
||||
--
|
||||
-- @module parallel
|
||||
|
||||
local function create( ... )
|
||||
local function create(...)
|
||||
local tFns = table.pack(...)
|
||||
local tCos = {}
|
||||
for i = 1, tFns.n, 1 do
|
||||
local fn = tFns[i]
|
||||
if type( fn ) ~= "function" then
|
||||
error( "bad argument #" .. i .. " (expected function, got " .. type( fn ) .. ")", 3 )
|
||||
if type(fn) ~= "function" then
|
||||
error("bad argument #" .. i .. " (expected function, got " .. type(fn) .. ")", 3)
|
||||
end
|
||||
|
||||
tCos[i] = coroutine.create(fn)
|
||||
@ -14,7 +29,7 @@ local function create( ... )
|
||||
return tCos
|
||||
end
|
||||
|
||||
local function runUntilLimit( _routines, _limit )
|
||||
local function runUntilLimit(_routines, _limit)
|
||||
local count = #_routines
|
||||
local living = count
|
||||
|
||||
@ -25,13 +40,13 @@ local function runUntilLimit( _routines, _limit )
|
||||
local r = _routines[n]
|
||||
if r then
|
||||
if tFilters[r] == nil or tFilters[r] == eventData[1] or eventData[1] == "terminate" then
|
||||
local ok, param = coroutine.resume( r, table.unpack( eventData, 1, eventData.n ) )
|
||||
local ok, param = coroutine.resume(r, table.unpack(eventData, 1, eventData.n))
|
||||
if not ok then
|
||||
error( param, 0 )
|
||||
error(param, 0)
|
||||
else
|
||||
tFilters[r] = param
|
||||
end
|
||||
if coroutine.status( r ) == "dead" then
|
||||
if coroutine.status(r) == "dead" then
|
||||
_routines[n] = nil
|
||||
living = living - 1
|
||||
if living <= _limit then
|
||||
@ -43,7 +58,7 @@ local function runUntilLimit( _routines, _limit )
|
||||
end
|
||||
for n = 1, count do
|
||||
local r = _routines[n]
|
||||
if r and coroutine.status( r ) == "dead" then
|
||||
if r and coroutine.status(r) == "dead" then
|
||||
_routines[n] = nil
|
||||
living = living - 1
|
||||
if living <= _limit then
|
||||
@ -51,16 +66,26 @@ local function runUntilLimit( _routines, _limit )
|
||||
end
|
||||
end
|
||||
end
|
||||
eventData = table.pack( os.pullEventRaw() )
|
||||
eventData = table.pack(os.pullEventRaw())
|
||||
end
|
||||
end
|
||||
|
||||
function waitForAny( ... )
|
||||
local routines = create( ... )
|
||||
return runUntilLimit( routines, #routines - 1 )
|
||||
--- Switches between execution of the functions, until any of them
|
||||
-- finishes. If any of the functions errors, the message is propagated upwards
|
||||
-- from the @{parallel.waitForAny} call.
|
||||
--
|
||||
-- @tparam function ... The functions this task will run
|
||||
function waitForAny(...)
|
||||
local routines = create(...)
|
||||
return runUntilLimit(routines, #routines - 1)
|
||||
end
|
||||
|
||||
function waitForAll( ... )
|
||||
local routines = create( ... )
|
||||
runUntilLimit( routines, 0 )
|
||||
--- Switches between execution of the functions, until all of them are
|
||||
-- finished. If any of the functions errors, the message is propagated upwards
|
||||
-- from the @{parallel.waitForAll} call.
|
||||
--
|
||||
-- @tparam function ... The functions this task will run
|
||||
function waitForAll(...)
|
||||
local routines = create(...)
|
||||
return runUntilLimit(routines, 0)
|
||||
end
|
||||
|
@ -1,110 +1,184 @@
|
||||
--- The Peripheral API is for interacting with peripherals connected to the
|
||||
-- computer, such as the Disk Drive, the Advanced Monitor and Monitor.
|
||||
--
|
||||
-- Each peripheral block has a name, either referring to the side the peripheral
|
||||
-- can be found on, or a name on an adjacent wired network.
|
||||
--
|
||||
-- If the peripheral is next to the computer, its side is either `front`,
|
||||
-- `back`, `left`, `right`, `top` or `bottom`. If the peripheral is attached by
|
||||
-- a cable, its side will follow the format `type_id`, for example `printer_0`.
|
||||
--
|
||||
-- Peripheral functions are called *methods*, a term borrowed from Java.
|
||||
--
|
||||
-- @module peripheral
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local native = peripheral
|
||||
local sides = rs.getSides()
|
||||
|
||||
--- Provides a list of all peripherals available.
|
||||
--
|
||||
-- If a device is located directly next to the system, then its name will be
|
||||
-- listed as the side it is attached to. If a device is attached via a Wired
|
||||
-- Modem, then it'll be reported according to its name on the wired network.
|
||||
--
|
||||
-- @treturn table A list of the names of all attached peripherals.
|
||||
function getNames()
|
||||
local tResults = {}
|
||||
for _, sSide in ipairs( rs.getSides() ) do
|
||||
if native.isPresent( sSide ) then
|
||||
table.insert( tResults, sSide )
|
||||
if native.getType( sSide ) == "modem" and not native.call( sSide, "isWireless" ) then
|
||||
local tRemote = native.call( sSide, "getNamesRemote" )
|
||||
for _, sName in ipairs( tRemote ) do
|
||||
table.insert( tResults, sName )
|
||||
local results = {}
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.isPresent(side) then
|
||||
table.insert(results, side)
|
||||
if native.getType(side) == "modem" and not native.call(side, "isWireless") then
|
||||
local remote = native.call(side, "getNamesRemote")
|
||||
for _, name in ipairs(remote) do
|
||||
table.insert(results, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
return results
|
||||
end
|
||||
|
||||
function isPresent( _sSide )
|
||||
expect(1, _sSide, "string")
|
||||
if native.isPresent( _sSide ) then
|
||||
--- Determines if a peripheral is present with the given name.
|
||||
--
|
||||
-- @tparam string name The side or network name that you want to check.
|
||||
-- @treturn boolean If a peripheral is present with the given name.
|
||||
-- @usage peripheral.isPresent("top")
|
||||
-- @usage peripheral.isPresent("monitor_0")
|
||||
function isPresent(name)
|
||||
expect(1, name, "string")
|
||||
if native.isPresent(name) then
|
||||
return true
|
||||
end
|
||||
for _, sSide in ipairs( rs.getSides() ) do
|
||||
if native.getType( sSide ) == "modem" and not native.call( sSide, "isWireless" ) then
|
||||
if native.call( sSide, "isPresentRemote", _sSide ) then
|
||||
return true
|
||||
end
|
||||
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.getType(side) == "modem" and not native.call(side, "isWireless") and
|
||||
native.call(side, "isPresentRemote", name)
|
||||
then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function getType( _sSide )
|
||||
expect(1, _sSide, "string")
|
||||
if native.isPresent( _sSide ) then
|
||||
return native.getType( _sSide )
|
||||
--- Get the type of the peripheral with the given name.
|
||||
--
|
||||
-- @tparam string name The name of the peripheral to find.
|
||||
-- @treturn string|nil The peripheral's type, or `nil` if it is not present.
|
||||
function getType(name)
|
||||
expect(1, name, "string")
|
||||
if native.isPresent(name) then
|
||||
return native.getType(name)
|
||||
end
|
||||
for _, sSide in ipairs( rs.getSides() ) do
|
||||
if native.getType( sSide ) == "modem" and not native.call( sSide, "isWireless" ) then
|
||||
if native.call( sSide, "isPresentRemote", _sSide ) then
|
||||
return native.call( sSide, "getTypeRemote", _sSide )
|
||||
end
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.getType(side) == "modem" and not native.call(side, "isWireless") and
|
||||
native.call(side, "isPresentRemote", name)
|
||||
then
|
||||
return native.call(side, "getTypeRemote", name)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function getMethods( _sSide )
|
||||
expect(1, _sSide, "string")
|
||||
if native.isPresent( _sSide ) then
|
||||
return native.getMethods( _sSide )
|
||||
--- Get all available methods for the peripheral with the given name.
|
||||
--
|
||||
-- @tparam string name The name of the peripheral to find.
|
||||
-- @treturn table|nil A list of methods provided by this peripheral, or `nil` if
|
||||
-- it is not present.
|
||||
function getMethods(name)
|
||||
expect(1, name, "string")
|
||||
if native.isPresent(name) then
|
||||
return native.getMethods(name)
|
||||
end
|
||||
for _, sSide in ipairs( rs.getSides() ) do
|
||||
if native.getType( sSide ) == "modem" and not native.call( sSide, "isWireless" ) then
|
||||
if native.call( sSide, "isPresentRemote", _sSide ) then
|
||||
return native.call( sSide, "getMethodsRemote", _sSide )
|
||||
end
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.getType(side) == "modem" and not native.call(side, "isWireless") and
|
||||
native.call(side, "isPresentRemote", name)
|
||||
then
|
||||
return native.call(side, "getMethodsRemote", name)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function call( _sSide, _sMethod, ... )
|
||||
expect(1, _sSide, "string")
|
||||
expect(2, _sMethod, "string")
|
||||
if native.isPresent( _sSide ) then
|
||||
return native.call( _sSide, _sMethod, ... )
|
||||
--- Call a method on the peripheral with the given name.
|
||||
--
|
||||
-- @tparam string name The name of the peripheral to invoke the method on.
|
||||
-- @tparam string method The name of the method
|
||||
-- @param ... Additional arguments to pass to the method
|
||||
-- @return The return values of the peripheral method.
|
||||
--
|
||||
-- @usage Open the modem on the top of this computer.
|
||||
--
|
||||
-- peripheral.call("top", "open", 1)
|
||||
function call(name, method, ...)
|
||||
expect(1, name, "string")
|
||||
expect(2, method, "string")
|
||||
if native.isPresent(name) then
|
||||
return native.call(name, method, ...)
|
||||
end
|
||||
for _, sSide in ipairs( rs.getSides() ) do
|
||||
if native.getType( sSide ) == "modem" and not native.call( sSide, "isWireless" ) then
|
||||
if native.call( sSide, "isPresentRemote", _sSide ) then
|
||||
return native.call( sSide, "callRemote", _sSide, _sMethod, ... )
|
||||
end
|
||||
|
||||
for n = 1, #sides do
|
||||
local side = sides[n]
|
||||
if native.getType(side) == "modem" and not native.call(side, "isWireless") and
|
||||
native.call(side, "isPresentRemote", name)
|
||||
then
|
||||
return native.call(side, "callRemote", name, method, ...)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function wrap( _sSide )
|
||||
expect(1, _sSide, "string")
|
||||
if peripheral.isPresent( _sSide ) then
|
||||
local tMethods = peripheral.getMethods( _sSide )
|
||||
local tResult = {}
|
||||
for _, sMethod in ipairs( tMethods ) do
|
||||
tResult[sMethod] = function( ... )
|
||||
return peripheral.call( _sSide, sMethod, ... )
|
||||
end
|
||||
end
|
||||
return tResult
|
||||
--- Get a table containing functions pointing to the peripheral's methods, which
|
||||
-- can then be called as if using @{peripheral.call}.
|
||||
--
|
||||
-- @tparam string name The name of the peripheral to wrap.
|
||||
-- @treturn table|nil The table containing the peripheral's methods, or `nil` if
|
||||
-- there is no peripheral present with the given name.
|
||||
-- @usage peripheral.wrap("top").open(1)
|
||||
function wrap(name)
|
||||
expect(1, name, "string")
|
||||
|
||||
local methods = peripheral.getMethods(name)
|
||||
if not methods then
|
||||
return nil
|
||||
end
|
||||
return nil
|
||||
|
||||
local result = {}
|
||||
for _, method in ipairs(methods) do
|
||||
result[method] = function(...)
|
||||
return peripheral.call(name, method, ...)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function find( sType, fnFilter )
|
||||
expect(1, sType, "string")
|
||||
expect(2, fnFilter, "function", "nil")
|
||||
local tResults = {}
|
||||
for _, sName in ipairs( peripheral.getNames() ) do
|
||||
if peripheral.getType( sName ) == sType then
|
||||
local wrapped = peripheral.wrap( sName )
|
||||
if fnFilter == nil or fnFilter( sName, wrapped ) then
|
||||
table.insert( tResults, wrapped )
|
||||
--- Find all peripherals of a specific type, and return the
|
||||
-- @{peripheral.wrap|wrapped} peripherals.
|
||||
--
|
||||
-- @tparam string ty The type of peripheral to look for.
|
||||
-- @tparam[opt] function(name:string, wrapped:table):boolean filter A
|
||||
-- filter function, which takes the peripheral's name and wrapped table
|
||||
-- and returns if it should be included in the result.
|
||||
-- @treturn table... 0 or more wrapped peripherals matching the given filters.
|
||||
-- @usage local monitors = { peripheral.find("monitor") }
|
||||
-- @usage peripheral.find("modem", rednet.open)
|
||||
function find(ty, filter)
|
||||
expect(1, ty, "string")
|
||||
expect(2, filter, "function", "nil")
|
||||
|
||||
local results = {}
|
||||
for _, name in ipairs(peripheral.getNames()) do
|
||||
if peripheral.getType(name) == ty then
|
||||
local wrapped = peripheral.wrap(name)
|
||||
if filter == nil or filter(name, wrapped) then
|
||||
table.insert(results, wrapped)
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.unpack( tResults )
|
||||
return table.unpack(results)
|
||||
end
|
||||
|
@ -1,51 +1,92 @@
|
||||
--- The Rednet API allows systems to communicate between each other without
|
||||
-- using redstone. It serves as a wrapper for the modem API, offering ease of
|
||||
-- functionality (particularly in regards to repeating signals) with some
|
||||
-- expense of fine control.
|
||||
--
|
||||
-- In order to send and receive data, a modem (either wired, wireless, or ender)
|
||||
-- is required. The data reaches any possible destinations immediately after
|
||||
-- sending it, but is range limited.
|
||||
--
|
||||
-- Rednet also allows you to use a "protocol" - simple string names indicating
|
||||
-- what messages are about. Receiving systems may filter messages according to
|
||||
-- their protocols, thereby automatically ignoring incoming messages which don't
|
||||
-- specify an identical string. It's also possible to @{rednet.lookup|lookup}
|
||||
-- which systems in the area use certain protocols, hence making it easier to
|
||||
-- determine where given messages should be sent in the first place.
|
||||
--
|
||||
-- @module rednet
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
--- The channel used by the Rednet API to @{broadcast} messages.
|
||||
CHANNEL_BROADCAST = 65535
|
||||
|
||||
--- The channel used by the Rednet API to repeat messages.
|
||||
CHANNEL_REPEAT = 65533
|
||||
|
||||
local tReceivedMessages = {}
|
||||
local tReceivedMessageTimeouts = {}
|
||||
local tHostnames = {}
|
||||
|
||||
function open( sModem )
|
||||
expect(1, sModem, "string")
|
||||
if peripheral.getType( sModem ) ~= "modem" then
|
||||
error( "No such modem: " .. sModem, 2 )
|
||||
--- Opens a modem with the given @{peripheral} name, allowing it to send and
|
||||
--- receive messages over rednet.
|
||||
--
|
||||
-- This will open the modem on two channels: one which has the same
|
||||
-- @{os.getComputerID|ID} as the computer, and another on
|
||||
-- @{CHANNEL_BROADCAST|the broadcast channel}.
|
||||
--
|
||||
-- @tparam string modem The name of the modem to open.
|
||||
-- @throws If there is no such modem with the given name
|
||||
function open(modem)
|
||||
expect(1, modem, "string")
|
||||
if peripheral.getType(modem) ~= "modem" then
|
||||
error("No such modem: " .. modem, 2)
|
||||
end
|
||||
peripheral.call( sModem, "open", os.getComputerID() )
|
||||
peripheral.call( sModem, "open", CHANNEL_BROADCAST )
|
||||
peripheral.call(modem, "open", os.getComputerID())
|
||||
peripheral.call(modem, "open", CHANNEL_BROADCAST)
|
||||
end
|
||||
|
||||
function close( sModem )
|
||||
expect(1, sModem, "string", "nil")
|
||||
if sModem then
|
||||
--- Close a modem with the given @{peripheral} name, meaning it can no longer
|
||||
-- send and receive rednet messages.
|
||||
--
|
||||
-- @tparam[opt] string modem The side the modem exists on. If not given, all
|
||||
-- open modems will be closed.
|
||||
-- @throws If there is no such modem with the given name
|
||||
function close(modem)
|
||||
expect(1, modem, "string", "nil")
|
||||
if modem then
|
||||
-- Close a specific modem
|
||||
if peripheral.getType( sModem ) ~= "modem" then
|
||||
error( "No such modem: " .. sModem, 2 )
|
||||
if peripheral.getType(modem) ~= "modem" then
|
||||
error("No such modem: " .. modem, 2)
|
||||
end
|
||||
peripheral.call( sModem, "close", os.getComputerID() )
|
||||
peripheral.call( sModem, "close", CHANNEL_BROADCAST )
|
||||
peripheral.call(modem, "close", os.getComputerID())
|
||||
peripheral.call(modem, "close", CHANNEL_BROADCAST)
|
||||
else
|
||||
-- Close all modems
|
||||
for _, sModem in ipairs( peripheral.getNames() ) do
|
||||
if isOpen( sModem ) then
|
||||
close( sModem )
|
||||
for _, modem in ipairs(peripheral.getNames()) do
|
||||
if isOpen(modem) then
|
||||
close(modem)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function isOpen( sModem )
|
||||
expect(1, sModem, "string", "nil")
|
||||
if sModem then
|
||||
--- Determine if rednet is currently open.
|
||||
--
|
||||
-- @tparam[opt] string modem Which modem to check. If not given, all connected
|
||||
-- modems will be checked.
|
||||
-- @treturn boolean If the given modem is open.
|
||||
function isOpen(modem)
|
||||
expect(1, modem, "string", "nil")
|
||||
if modem then
|
||||
-- Check if a specific modem is open
|
||||
if peripheral.getType( sModem ) == "modem" then
|
||||
return peripheral.call( sModem, "isOpen", os.getComputerID() ) and peripheral.call( sModem, "isOpen", CHANNEL_BROADCAST )
|
||||
if peripheral.getType(modem) == "modem" then
|
||||
return peripheral.call(modem, "isOpen", os.getComputerID()) and peripheral.call(modem, "isOpen", CHANNEL_BROADCAST)
|
||||
end
|
||||
else
|
||||
-- Check if any modem is open
|
||||
for _, sModem in ipairs( peripheral.getNames() ) do
|
||||
if isOpen( sModem ) then
|
||||
for _, modem in ipairs(peripheral.getNames()) do
|
||||
if isOpen(modem) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
@ -53,15 +94,32 @@ function isOpen( sModem )
|
||||
return false
|
||||
end
|
||||
|
||||
function send( nRecipient, message, sProtocol )
|
||||
--- Allows a computer or turtle with an attached modem to send a message
|
||||
-- intended for a system with a specific ID. At least one such modem must first
|
||||
-- be @{rednet.open|opened} before sending is possible.
|
||||
--
|
||||
-- Assuming the target was in range and also had a correctly opened modem, it
|
||||
-- may then use @{rednet.receive} to collect the message.
|
||||
--
|
||||
-- @tparam number nRecipient The ID of the receiving computer.
|
||||
-- @param message The message to send. This should not contain coroutines or
|
||||
-- functions, as they will be converted to @{nil}.
|
||||
-- @tparam[opt] string sProtocol The "protocol" to send this message under. When
|
||||
-- using @{rednet.receive} one can filter to only receive messages sent under a
|
||||
-- particular protocol.
|
||||
-- @treturn boolean If this message was successfully sent (i.e. if rednet is
|
||||
-- currently @{rednet.open|open}). Note, this does not guarantee the message was
|
||||
-- actually _received_.
|
||||
-- @see rednet.receive
|
||||
function send(nRecipient, message, sProtocol)
|
||||
expect(1, nRecipient, "number")
|
||||
expect(3, sProtocol, "string", "nil")
|
||||
-- Generate a (probably) unique message ID
|
||||
-- We could do other things to guarantee uniqueness, but we really don't need to
|
||||
-- Store it to ensure we don't get our own messages back
|
||||
local nMessageID = math.random( 1, 2147483647 )
|
||||
tReceivedMessages[ nMessageID ] = true
|
||||
tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = nMessageID
|
||||
local nMessageID = math.random(1, 2147483647)
|
||||
tReceivedMessages[nMessageID] = true
|
||||
tReceivedMessageTimeouts[os.startTimer(30)] = nMessageID
|
||||
|
||||
-- Create the message
|
||||
local nReplyChannel = os.getComputerID()
|
||||
@ -75,14 +133,14 @@ function send( nRecipient, message, sProtocol )
|
||||
local sent = false
|
||||
if nRecipient == os.getComputerID() then
|
||||
-- Loopback to ourselves
|
||||
os.queueEvent( "rednet_message", nReplyChannel, message, sProtocol )
|
||||
os.queueEvent("rednet_message", nReplyChannel, message, sProtocol)
|
||||
sent = true
|
||||
else
|
||||
-- Send on all open modems, to the target and to repeaters
|
||||
for _, sModem in ipairs( peripheral.getNames() ) do
|
||||
if isOpen( sModem ) then
|
||||
peripheral.call( sModem, "transmit", nRecipient, nReplyChannel, tMessage )
|
||||
peripheral.call( sModem, "transmit", CHANNEL_REPEAT, nReplyChannel, tMessage )
|
||||
for _, sModem in ipairs(peripheral.getNames()) do
|
||||
if isOpen(sModem) then
|
||||
peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage)
|
||||
peripheral.call(sModem, "transmit", CHANNEL_REPEAT, nReplyChannel, tMessage)
|
||||
sent = true
|
||||
end
|
||||
end
|
||||
@ -91,12 +149,35 @@ function send( nRecipient, message, sProtocol )
|
||||
return sent
|
||||
end
|
||||
|
||||
function broadcast( message, sProtocol )
|
||||
--- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
|
||||
-- channel. The message will be received by every device listening to rednet.
|
||||
--
|
||||
-- @param message The message to send. This should not contain coroutines or
|
||||
-- functions, as they will be converted to @{nil}.
|
||||
-- @tparam[opt] string sProtocol The "protocol" to send this message under. When
|
||||
-- using @{rednet.receive} one can filter to only receive messages sent under a
|
||||
-- particular protocol.
|
||||
-- @see rednet.receive
|
||||
function broadcast(message, sProtocol)
|
||||
expect(2, sProtocol, "string", "nil")
|
||||
send( CHANNEL_BROADCAST, message, sProtocol )
|
||||
send(CHANNEL_BROADCAST, message, sProtocol)
|
||||
end
|
||||
|
||||
function receive( sProtocolFilter, nTimeout )
|
||||
--- Wait for a rednet message to be received, or until `nTimeout` seconds have
|
||||
-- elapsed.
|
||||
--
|
||||
-- @tparam[opt] string sProtocolFilter The protocol the received message must be
|
||||
-- sent with. If specified, any messages not sent under this protocol will be
|
||||
-- discarded.
|
||||
-- @tparam[opt] number nTimeout The number of seconds to wait if no message is
|
||||
-- received.
|
||||
-- @treturn[1] number The computer which sent this message
|
||||
-- @return[1] The received message
|
||||
-- @treturn[1] string|nil The protocol this message was sent under.
|
||||
-- @treturn[2] nil If the timeout elapsed and no message was received.
|
||||
-- @see rednet.broadcast
|
||||
-- @see rednet.send
|
||||
function receive(sProtocolFilter, nTimeout)
|
||||
-- The parameters used to be ( nTimeout ), detect this case for backwards compatibility
|
||||
if type(sProtocolFilter) == "number" and nTimeout == nil then
|
||||
sProtocolFilter, nTimeout = nil, sProtocolFilter
|
||||
@ -108,7 +189,7 @@ function receive( sProtocolFilter, nTimeout )
|
||||
local timer = nil
|
||||
local sFilter = nil
|
||||
if nTimeout then
|
||||
timer = os.startTimer( nTimeout )
|
||||
timer = os.startTimer(nTimeout)
|
||||
sFilter = nil
|
||||
else
|
||||
sFilter = "rednet_message"
|
||||
@ -116,7 +197,7 @@ function receive( sProtocolFilter, nTimeout )
|
||||
|
||||
-- Wait for events
|
||||
while true do
|
||||
local sEvent, p1, p2, p3 = os.pullEvent( sFilter )
|
||||
local sEvent, p1, p2, p3 = os.pullEvent(sFilter)
|
||||
if sEvent == "rednet_message" then
|
||||
-- Return the first matching rednet_message
|
||||
local nSenderID, message, sProtocol = p1, p2, p3
|
||||
@ -132,26 +213,62 @@ function receive( sProtocolFilter, nTimeout )
|
||||
end
|
||||
end
|
||||
|
||||
function host( sProtocol, sHostname )
|
||||
--- Register the system as "hosting" the desired protocol under the specified
|
||||
-- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
|
||||
-- maybe name) on the same network, the registered system will automatically
|
||||
-- respond via a background process, hence providing the system performing the
|
||||
-- lookup with its ID number.
|
||||
--
|
||||
-- Multiple computers may not register themselves on the same network as having
|
||||
-- the same names against the same protocols, and the title `localhost` is
|
||||
-- specifically reserved. They may, however, share names as long as their hosted
|
||||
-- protocols are different, or if they only join a given network after
|
||||
-- "registering" themselves before doing so (eg while offline or part of a
|
||||
-- different network).
|
||||
--
|
||||
-- @tparam string sProtocol The protocol this computer provides.
|
||||
-- @tparam string sHostname The name this protocol exposes for the given protocol.
|
||||
-- @throws If trying to register a hostname which is reserved, or currently in use.
|
||||
-- @see rednet.unhost
|
||||
-- @see rednet.lookup
|
||||
function host(sProtocol, sHostname)
|
||||
expect(1, sProtocol, "string")
|
||||
expect(2, sHostname, "string")
|
||||
if sHostname == "localhost" then
|
||||
error( "Reserved hostname", 2 )
|
||||
error("Reserved hostname", 2)
|
||||
end
|
||||
if tHostnames[ sProtocol ] ~= sHostname then
|
||||
if lookup( sProtocol, sHostname ) ~= nil then
|
||||
error( "Hostname in use", 2 )
|
||||
if tHostnames[sProtocol] ~= sHostname then
|
||||
if lookup(sProtocol, sHostname) ~= nil then
|
||||
error("Hostname in use", 2)
|
||||
end
|
||||
tHostnames[ sProtocol ] = sHostname
|
||||
tHostnames[sProtocol] = sHostname
|
||||
end
|
||||
end
|
||||
|
||||
function unhost( sProtocol )
|
||||
--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
|
||||
--- respond to @{rednet.lookup} requests.
|
||||
--
|
||||
-- @tparam string sProtocol The protocol to unregister your self from.
|
||||
function unhost(sProtocol)
|
||||
expect(1, sProtocol, "string")
|
||||
tHostnames[ sProtocol ] = nil
|
||||
tHostnames[sProtocol] = nil
|
||||
end
|
||||
|
||||
function lookup( sProtocol, sHostname )
|
||||
--- Search the local rednet network for systems @{rednet.host|hosting} the
|
||||
-- desired protocol and returns any computer IDs that respond as "registered"
|
||||
-- against it.
|
||||
--
|
||||
-- If a hostname is specified, only one ID will be returned (assuming an exact
|
||||
-- match is found).
|
||||
--
|
||||
-- @tparam string sProtocol The protocol to search for.
|
||||
-- @tparam[opt] string sHostname The hostname to search for.
|
||||
--
|
||||
-- @treturn[1] { number }|nil A list of computer IDs hosting the given
|
||||
-- protocol, or @{nil} if none exist.
|
||||
-- @treturn[2] number|nil The computer ID with the provided hostname and protocol,
|
||||
-- or @{nil} if none exists.
|
||||
function lookup(sProtocol, sHostname)
|
||||
expect(1, sProtocol, "string")
|
||||
expect(2, sHostname, "string", "nil")
|
||||
|
||||
@ -162,30 +279,30 @@ function lookup( sProtocol, sHostname )
|
||||
end
|
||||
|
||||
-- Check localhost first
|
||||
if tHostnames[ sProtocol ] then
|
||||
if tHostnames[sProtocol] then
|
||||
if sHostname == nil then
|
||||
table.insert( tResults, os.getComputerID() )
|
||||
elseif sHostname == "localhost" or sHostname == tHostnames[ sProtocol ] then
|
||||
table.insert(tResults, os.getComputerID())
|
||||
elseif sHostname == "localhost" or sHostname == tHostnames[sProtocol] then
|
||||
return os.getComputerID()
|
||||
end
|
||||
end
|
||||
|
||||
if not isOpen() then
|
||||
if tResults then
|
||||
return table.unpack( tResults )
|
||||
return table.unpack(tResults)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Broadcast a lookup packet
|
||||
broadcast( {
|
||||
broadcast({
|
||||
sType = "lookup",
|
||||
sProtocol = sProtocol,
|
||||
sHostname = sHostname,
|
||||
}, "dns" )
|
||||
}, "dns")
|
||||
|
||||
-- Start a timer
|
||||
local timer = os.startTimer( 2 )
|
||||
local timer = os.startTimer(2)
|
||||
|
||||
-- Wait for events
|
||||
while true do
|
||||
@ -196,7 +313,7 @@ function lookup( sProtocol, sHostname )
|
||||
if sMessageProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup response" then
|
||||
if tMessage.sProtocol == sProtocol then
|
||||
if sHostname == nil then
|
||||
table.insert( tResults, nSenderID )
|
||||
table.insert(tResults, nSenderID)
|
||||
elseif tMessage.sHostname == sHostname then
|
||||
return nSenderID
|
||||
end
|
||||
@ -210,15 +327,17 @@ function lookup( sProtocol, sHostname )
|
||||
end
|
||||
end
|
||||
if tResults then
|
||||
return table.unpack( tResults )
|
||||
return table.unpack(tResults)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local bRunning = false
|
||||
|
||||
--- @local
|
||||
function run()
|
||||
if bRunning then
|
||||
error( "rednet is already running", 2 )
|
||||
error("rednet is already running", 2)
|
||||
end
|
||||
bRunning = true
|
||||
|
||||
@ -227,12 +346,12 @@ function run()
|
||||
if sEvent == "modem_message" then
|
||||
-- Got a modem message, process it and add it to the rednet event queue
|
||||
local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
|
||||
if isOpen( sModem ) and ( nChannel == os.getComputerID() or nChannel == CHANNEL_BROADCAST ) then
|
||||
if type( tMessage ) == "table" and tMessage.nMessageID then
|
||||
if not tReceivedMessages[ tMessage.nMessageID ] then
|
||||
tReceivedMessages[ tMessage.nMessageID ] = true
|
||||
tReceivedMessageTimeouts[ os.startTimer( 30 ) ] = tMessage.nMessageID
|
||||
os.queueEvent( "rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol )
|
||||
if isOpen(sModem) and (nChannel == os.getComputerID() or nChannel == CHANNEL_BROADCAST) then
|
||||
if type(tMessage) == "table" and tMessage.nMessageID then
|
||||
if not tReceivedMessages[tMessage.nMessageID] then
|
||||
tReceivedMessages[tMessage.nMessageID] = true
|
||||
tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
|
||||
os.queueEvent("rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -241,23 +360,23 @@ function run()
|
||||
-- Got a rednet message (queued from above), respond to dns lookup
|
||||
local nSenderID, tMessage, sProtocol = p1, p2, p3
|
||||
if sProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup" then
|
||||
local sHostname = tHostnames[ tMessage.sProtocol ]
|
||||
local sHostname = tHostnames[tMessage.sProtocol]
|
||||
if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
|
||||
rednet.send( nSenderID, {
|
||||
rednet.send(nSenderID, {
|
||||
sType = "lookup response",
|
||||
sHostname = sHostname,
|
||||
sProtocol = tMessage.sProtocol,
|
||||
}, "dns" )
|
||||
}, "dns")
|
||||
end
|
||||
end
|
||||
|
||||
elseif sEvent == "timer" then
|
||||
-- Got a timer event, use it to clear the event queue
|
||||
local nTimer = p1
|
||||
local nMessage = tReceivedMessageTimeouts[ nTimer ]
|
||||
local nMessage = tReceivedMessageTimeouts[nTimer]
|
||||
if nMessage then
|
||||
tReceivedMessageTimeouts[ nTimer ] = nil
|
||||
tReceivedMessages[ nMessage ] = nil
|
||||
tReceivedMessageTimeouts[nTimer] = nil
|
||||
tReceivedMessages[nMessage] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,62 +1,197 @@
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
--- The settings API allows to store values and save them to a file for
|
||||
-- persistent configurations for CraftOS and your programs.
|
||||
--
|
||||
-- By default, the settings API will load its configuration from the
|
||||
-- `/.settings` file. One can then use @{settings.save} to update the file.
|
||||
--
|
||||
-- @module settings
|
||||
|
||||
local tSettings = {}
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua")
|
||||
local type, expect, field = type, expect.expect, expect.field
|
||||
|
||||
function set( sName, value )
|
||||
expect(1, sName, "string")
|
||||
local details, values = {}, {}
|
||||
|
||||
local function reserialize(value)
|
||||
if type(value) ~= "table" then return value end
|
||||
return textutils.unserialize(textutils.serialize(value))
|
||||
end
|
||||
|
||||
local function copy(value)
|
||||
if type(value) ~= "table" then return value end
|
||||
local result = {}
|
||||
for k, v in pairs(value) do result[k] = copy(v) end
|
||||
return result
|
||||
end
|
||||
|
||||
local valid_types = { "number", "string", "boolean", "table" }
|
||||
for _, v in ipairs(valid_types) do valid_types[v] = true end
|
||||
|
||||
--- Define a new setting, optional specifying various properties about it.
|
||||
--
|
||||
-- While settings do not have to be added before being used, doing so allows
|
||||
-- you to provide defaults and additional metadata.
|
||||
--
|
||||
-- @tparam string name The name of this option
|
||||
-- @tparam[opt] { description? = string, default? = value, type? = string } options
|
||||
-- Options for this setting. This table accepts the following fields:
|
||||
--
|
||||
-- - `description`: A description which may be printed when running the `set` program.
|
||||
-- - `default`: A default value, which is returned by @{settings.get} if the
|
||||
-- setting has not been changed.
|
||||
-- - `type`: Require values to be of this type. @{set|Setting} the value to another type
|
||||
-- will error.
|
||||
function define(name, options)
|
||||
expect(1, name, "string")
|
||||
expect(2, options, "table", nil)
|
||||
|
||||
if options then
|
||||
options = {
|
||||
description = field(options, "description", "string", "nil"),
|
||||
default = reserialize(field(options, "default", "number", "string", "boolean", "table", "nil")),
|
||||
type = field(options, "type", "string", "nil"),
|
||||
}
|
||||
|
||||
if options.type and not valid_types[options.type] then
|
||||
error(("Unknown type %q. Expected one of %s."):format(options.type, table.concat(valid_types, ", ")), 2)
|
||||
end
|
||||
else
|
||||
options = {}
|
||||
end
|
||||
|
||||
details[name] = options
|
||||
end
|
||||
|
||||
--- Remove a @{define|definition} of a setting.
|
||||
--
|
||||
-- If a setting has been changed, this does not remove its value. Use @{settings.unset}
|
||||
-- for that.
|
||||
--
|
||||
-- @tparam string name The name of this option
|
||||
function undefine(name)
|
||||
expect(1, name, "string")
|
||||
details[name] = nil
|
||||
end
|
||||
|
||||
local function set_value(name, value)
|
||||
local new = reserialize(value)
|
||||
local old = values[name]
|
||||
if old == nil then
|
||||
local opt = details[name]
|
||||
old = opt and opt.default
|
||||
end
|
||||
|
||||
values[name] = new
|
||||
if old ~= new then
|
||||
-- This should be safe, as os.queueEvent copies values anyway.
|
||||
os.queueEvent("setting_changed", name, new, old)
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the value of a setting.
|
||||
--
|
||||
-- @tparam string name The name of the setting to set
|
||||
-- @param value The setting's value. This cannot be `nil`, and must be
|
||||
-- serialisable by @{textutils.serialize}.
|
||||
-- @throws If this value cannot be serialised
|
||||
-- @see settings.unset
|
||||
function set(name, value)
|
||||
expect(1, name, "string")
|
||||
expect(2, value, "number", "string", "boolean", "table")
|
||||
|
||||
if type(value) == "table" then
|
||||
-- Ensure value is serializeable
|
||||
value = textutils.unserialize( textutils.serialize(value) )
|
||||
end
|
||||
tSettings[ sName ] = value
|
||||
local opt = details[name]
|
||||
if opt and opt.type then expect(2, value, opt.type) end
|
||||
|
||||
set_value(name, value)
|
||||
end
|
||||
|
||||
local copy
|
||||
function copy( value )
|
||||
if type(value) == "table" then
|
||||
local result = {}
|
||||
for k, v in pairs(value) do
|
||||
result[k] = copy(v)
|
||||
end
|
||||
return result
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
function get( sName, default )
|
||||
expect(1, sName, "string")
|
||||
local result = tSettings[ sName ]
|
||||
--- Get the value of a setting.
|
||||
--
|
||||
-- @tparam string name The name of the setting to get.
|
||||
-- @param[opt] default The value to use should there be pre-existing value for
|
||||
-- this setting. If not given, it will use the setting's default value if given,
|
||||
-- or `nil` otherwise.
|
||||
-- @return The setting's, or the default if the setting has not been changed.
|
||||
function get(name, default)
|
||||
expect(1, name, "string")
|
||||
local result = values[name]
|
||||
if result ~= nil then
|
||||
return copy(result)
|
||||
else
|
||||
elseif default ~= nil then
|
||||
return default
|
||||
else
|
||||
local opt = details[name]
|
||||
return opt and copy(opt.default)
|
||||
end
|
||||
end
|
||||
|
||||
function unset( sName )
|
||||
expect(1, sName, "string")
|
||||
tSettings[ sName ] = nil
|
||||
--- Get details about a specific setting.
|
||||
--
|
||||
-- @tparam string name The name of the setting to get.
|
||||
-- @treturn { description? = string, default? = value, type? = string, value? = value }
|
||||
-- Information about this setting. This includes all information from @{settings.define},
|
||||
-- as well as this setting's value.
|
||||
function getDetails(name)
|
||||
expect(1, name, "string")
|
||||
local deets = copy(details[name]) or {}
|
||||
deets.value = values[name]
|
||||
deets.changed = deets.value ~= nil
|
||||
if deets.value == nil then deets.value = deets.default end
|
||||
return deets
|
||||
end
|
||||
|
||||
--- Remove the value of a setting, setting it to the default.
|
||||
--
|
||||
-- @{settings.get} will return the default value until the setting's value is
|
||||
-- @{settings.set|set}, or the computer is rebooted.
|
||||
--
|
||||
-- @tparam string name The name of the setting to unset.
|
||||
-- @see settings.set
|
||||
-- @see settings.clear
|
||||
function unset(name)
|
||||
expect(1, name, "string")
|
||||
set_value(name, nil)
|
||||
end
|
||||
|
||||
--- Resets the value of all settings. Equivalent to calling @{settings.unset}
|
||||
--- on every setting.
|
||||
--
|
||||
-- @see settings.unset
|
||||
function clear()
|
||||
tSettings = {}
|
||||
for name in pairs(values) do
|
||||
set_value(name, nil)
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the names of all currently defined settings.
|
||||
--
|
||||
-- @treturn { string } An alphabetically sorted list of all currently-defined
|
||||
-- settings.
|
||||
function getNames()
|
||||
local result = {}
|
||||
for k in pairs( tSettings ) do
|
||||
result[ #result + 1 ] = k
|
||||
local result, n = {}, 1
|
||||
for k in pairs(details) do
|
||||
result[n], n = k, n + 1
|
||||
end
|
||||
for k in pairs(values) do
|
||||
if not details[k] then result[n], n = k, n + 1 end
|
||||
end
|
||||
table.sort(result)
|
||||
return result
|
||||
end
|
||||
|
||||
function load( sPath )
|
||||
expect(1, sPath, "string")
|
||||
local file = fs.open( sPath, "r" )
|
||||
--- Load settings from the given file.
|
||||
--
|
||||
-- Existing settings will be merged with any pre-existing ones. Conflicting
|
||||
-- entries will be overwritten, but any others will be preserved.
|
||||
--
|
||||
-- @tparam[opt] string sPath The file to load from, defaulting to `.settings`.
|
||||
-- @treturn boolean Whether settings were successfully read from this
|
||||
-- file. Reasons for failure may include the file not existing or being
|
||||
-- corrupted.
|
||||
--
|
||||
-- @see settings.save
|
||||
function load(sPath)
|
||||
expect(1, sPath, "string", "nil")
|
||||
local file = fs.open(sPath or ".settings", "r")
|
||||
if not file then
|
||||
return false
|
||||
end
|
||||
@ -64,29 +199,41 @@ function load( sPath )
|
||||
local sText = file.readAll()
|
||||
file.close()
|
||||
|
||||
local tFile = textutils.unserialize( sText )
|
||||
local tFile = textutils.unserialize(sText)
|
||||
if type(tFile) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
for k, v in pairs(tFile) do
|
||||
if type(k) == "string" and
|
||||
(type(v) == "string" or type(v) == "number" or type(v) == "boolean" or type(v) == "table") then
|
||||
set( k, v )
|
||||
local ty_v = type(k)
|
||||
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
|
||||
local opt = details[name]
|
||||
if not opt or not opt.type or ty_v == opt.type then
|
||||
set_value(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function save( sPath )
|
||||
expect(1, sPath, "string")
|
||||
local file = fs.open( sPath, "w" )
|
||||
--- Save settings to the given file.
|
||||
--
|
||||
-- This will entirely overwrite the pre-existing file. Settings defined in the
|
||||
-- file, but not currently loaded will be removed.
|
||||
--
|
||||
-- @tparam[opt] string sPath The path to save settings to, defaulting to `.settings`.
|
||||
-- @treturn boolean If the settings were successfully saved.
|
||||
--
|
||||
-- @see settings.load
|
||||
function save(sPath)
|
||||
expect(1, sPath, "string", "nil")
|
||||
local file = fs.open(sPath or ".settings", "w")
|
||||
if not file then
|
||||
return false
|
||||
end
|
||||
|
||||
file.write( textutils.serialize( tSettings ) )
|
||||
file.write(textutils.serialize(values))
|
||||
file.close()
|
||||
|
||||
return true
|
||||
|
@ -1,26 +1,49 @@
|
||||
--- The Terminal API provides functions for writing text to the terminal and
|
||||
-- monitors, and drawing ASCII graphics.
|
||||
--
|
||||
-- @module term
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local native = term.native and term.native() or term
|
||||
local redirectTarget = native
|
||||
|
||||
local function wrap( _sFunction )
|
||||
return function( ... )
|
||||
return redirectTarget[ _sFunction ]( ... )
|
||||
local function wrap(_sFunction)
|
||||
return function(...)
|
||||
return redirectTarget[_sFunction](...)
|
||||
end
|
||||
end
|
||||
|
||||
local term = {}
|
||||
local term = _ENV
|
||||
|
||||
term.redirect = function( target )
|
||||
--- Redirects terminal output to a monitor, a @{window}, or any other custom
|
||||
-- terminal object. Once the redirect is performed, any calls to a "term"
|
||||
-- function - or to a function that makes use of a term function, as @{print} -
|
||||
-- will instead operate with the new terminal object.
|
||||
--
|
||||
-- A "terminal object" is simply a table that contains functions with the same
|
||||
-- names - and general features - as those found in the term table. For example,
|
||||
-- a wrapped monitor is suitable.
|
||||
--
|
||||
-- The redirect can be undone by pointing back to the previous terminal object
|
||||
-- (which this function returns whenever you switch).
|
||||
--
|
||||
-- @tparam Redirect target The terminal redirect the @{term} API will draw to.
|
||||
-- @treturn Redirect The previous redirect object, as returned by
|
||||
-- @{term.current}.
|
||||
-- @usage
|
||||
-- Redirect to a monitor on the right of the computer.
|
||||
-- term.redirect(peripheral.wrap("right"))
|
||||
term.redirect = function(target)
|
||||
expect(1, target, "table")
|
||||
if target == term or target == _G.term then
|
||||
error( "term is not a recommended redirect target, try term.current() instead", 2 )
|
||||
error("term is not a recommended redirect target, try term.current() instead", 2)
|
||||
end
|
||||
for k, v in pairs( native ) do
|
||||
if type( k ) == "string" and type( v ) == "function" then
|
||||
if type( target[k] ) ~= "function" then
|
||||
for k, v in pairs(native) do
|
||||
if type(k) == "string" and type(v) == "function" then
|
||||
if type(target[k]) ~= "function" then
|
||||
target[k] = function()
|
||||
error( "Redirect object is missing method " .. k .. ".", 2 )
|
||||
error("Redirect object is missing method " .. k .. ".", 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -30,31 +53,36 @@ term.redirect = function( target )
|
||||
return oldRedirectTarget
|
||||
end
|
||||
|
||||
--- Returns the current terminal object of the computer.
|
||||
--
|
||||
-- @treturn Redirect The current terminal redirect
|
||||
-- @usage
|
||||
-- Create a new @{window} which draws to the current redirect target
|
||||
-- window.create(term.current(), 1, 1, 10, 10)
|
||||
term.current = function()
|
||||
return redirectTarget
|
||||
end
|
||||
|
||||
--- Get the native terminal object of the current computer.
|
||||
--
|
||||
-- It is recommended you do not use this function unless you absolutely have
|
||||
-- to. In a multitasked environment, @{term.native} will _not_ be the current
|
||||
-- terminal object, and so drawing may interfere with other programs.
|
||||
--
|
||||
-- @treturn Redirect The native terminal redirect.
|
||||
term.native = function()
|
||||
-- NOTE: please don't use this function unless you have to.
|
||||
-- If you're running in a redirected or multitasked enviorment, term.native() will NOT be
|
||||
-- the current terminal when your program starts up. It is far better to use term.current()
|
||||
return native
|
||||
end
|
||||
|
||||
-- Some methods shouldn't go through redirects, so we move them to the main
|
||||
-- term API.
|
||||
for _, method in ipairs { "nativePaletteColor", "nativePaletteColour"} do
|
||||
for _, method in ipairs { "nativePaletteColor", "nativePaletteColour" } do
|
||||
term[method] = native[method]
|
||||
native[method] = nil
|
||||
end
|
||||
|
||||
for k, v in pairs( native ) do
|
||||
if type( k ) == "string" and type( v ) == "function" and term[k] == nil then
|
||||
term[k] = wrap( k )
|
||||
for k, v in pairs(native) do
|
||||
if type(k) == "string" and type(v) == "function" and rawget(term, k) == nil then
|
||||
term[k] = wrap(k)
|
||||
end
|
||||
end
|
||||
|
||||
local env = _ENV
|
||||
for k, v in pairs( term ) do
|
||||
env[k] = v
|
||||
end
|
||||
|
@ -1,32 +1,65 @@
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
--- The `textutils` API provides helpful utilities for formatting and
|
||||
-- manipulating strings.
|
||||
--
|
||||
-- @module textutils
|
||||
|
||||
function slowWrite( sText, nRate )
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua")
|
||||
local expect, field = expect.expect, expect.field
|
||||
|
||||
--- Slowly writes string text at current cursor position,
|
||||
-- character-by-character.
|
||||
--
|
||||
-- Like @{write}, this does not insert a newline at the end.
|
||||
--
|
||||
-- @tparam string sText The the text to write to the screen
|
||||
-- @tparam[opt] number nRate The number of characters to write each second,
|
||||
-- Defaults to 20.
|
||||
-- @usage textutils.slowWrite("Hello, world!")
|
||||
-- @usage textutils.slowWrite("Hello, world!", 5)
|
||||
function slowWrite(sText, nRate)
|
||||
expect(2, nRate, "number", "nil")
|
||||
nRate = nRate or 20
|
||||
if nRate < 0 then
|
||||
error( "Rate must be positive", 2 )
|
||||
error("Rate must be positive", 2)
|
||||
end
|
||||
local nSleep = 1 / nRate
|
||||
|
||||
sText = tostring( sText )
|
||||
sText = tostring(sText)
|
||||
local x, y = term.getCursorPos()
|
||||
local len = #sText
|
||||
|
||||
for n = 1, len do
|
||||
term.setCursorPos( x, y )
|
||||
sleep( nSleep )
|
||||
local nLines = write( string.sub( sText, 1, n ) )
|
||||
term.setCursorPos(x, y)
|
||||
sleep(nSleep)
|
||||
local nLines = write(string.sub(sText, 1, n))
|
||||
local _, newY = term.getCursorPos()
|
||||
y = newY - nLines
|
||||
end
|
||||
end
|
||||
|
||||
function slowPrint( sText, nRate )
|
||||
slowWrite( sText, nRate )
|
||||
--- Slowly prints string text at current cursor position,
|
||||
-- character-by-character.
|
||||
--
|
||||
-- Like @{print}, this inserts a newline after printing.
|
||||
--
|
||||
-- @tparam string sText The the text to write to the screen
|
||||
-- @tparam[opt] number nRate The number of characters to write each second,
|
||||
-- Defaults to 20.
|
||||
-- @usage textutils.slowPrint("Hello, world!")
|
||||
-- @usage textutils.slowPrint("Hello, world!", 5)
|
||||
function slowPrint(sText, nRate)
|
||||
slowWrite(sText, nRate)
|
||||
print()
|
||||
end
|
||||
|
||||
function formatTime( nTime, bTwentyFourHour )
|
||||
--- Takes input time and formats it in a more readable format such as `6:30 PM`.
|
||||
--
|
||||
-- @tparam number nTime The time to format, as provided by @{os.time}.
|
||||
-- @tparam[opt] boolean bTwentyFourHour Whether to format this as a 24-hour
|
||||
-- clock (`18:30`) rather than a 12-hour one (`6:30 AM`)
|
||||
-- @treturn string The formatted time
|
||||
-- @usage textutils.formatTime(os.time())
|
||||
function formatTime(nTime, bTwentyFourHour)
|
||||
expect(1, nTime, "number")
|
||||
expect(2, bTwentyFourHour, "boolean", "nil")
|
||||
local sTOD = nil
|
||||
@ -44,26 +77,26 @@ function formatTime( nTime, bTwentyFourHour )
|
||||
local nHour = math.floor(nTime)
|
||||
local nMinute = math.floor((nTime - nHour) * 60)
|
||||
if sTOD then
|
||||
return string.format( "%d:%02d %s", nHour, nMinute, sTOD )
|
||||
return string.format("%d:%02d %s", nHour, nMinute, sTOD)
|
||||
else
|
||||
return string.format( "%d:%02d", nHour, nMinute )
|
||||
return string.format("%d:%02d", nHour, nMinute)
|
||||
end
|
||||
end
|
||||
|
||||
local function makePagedScroll( _term, _nFreeLines )
|
||||
local function makePagedScroll(_term, _nFreeLines)
|
||||
local nativeScroll = _term.scroll
|
||||
local nFreeLines = _nFreeLines or 0
|
||||
return function( _n )
|
||||
return function(_n)
|
||||
for _ = 1, _n do
|
||||
nativeScroll( 1 )
|
||||
nativeScroll(1)
|
||||
|
||||
if nFreeLines <= 0 then
|
||||
local _, h = _term.getSize()
|
||||
_term.setCursorPos( 1, h )
|
||||
_term.write( "Press any key to continue" )
|
||||
os.pullEvent( "key" )
|
||||
_term.setCursorPos(1, h)
|
||||
_term.write("Press any key to continue")
|
||||
os.pullEvent("key")
|
||||
_term.clearLine()
|
||||
_term.setCursorPos( 1, h )
|
||||
_term.setCursorPos(1, h)
|
||||
else
|
||||
nFreeLines = nFreeLines - 1
|
||||
end
|
||||
@ -71,38 +104,55 @@ local function makePagedScroll( _term, _nFreeLines )
|
||||
end
|
||||
end
|
||||
|
||||
function pagedPrint( _sText, _nFreeLines )
|
||||
--- Prints a given string to the display.
|
||||
--
|
||||
-- If the action can be completed without scrolling, it acts much the same as
|
||||
-- @{print}; otherwise, it will throw up a "Press any key to continue" prompt at
|
||||
-- the bottom of the display. Each press will cause it to scroll down and write
|
||||
-- a single line more before prompting again, if need be.
|
||||
--
|
||||
-- @tparam string _sText The text to print to the screen.
|
||||
-- @tparam[opt] number _nFreeLines The number of lines which will be
|
||||
-- automatically scrolled before the first prompt appears (meaning _nFreeLines +
|
||||
-- 1 lines will be printed). This can be set to the terminal's height - 2 to
|
||||
-- always try to fill the screen. Defaults to 0, meaning only one line is
|
||||
-- displayed before prompting.
|
||||
-- @treturn number The number of lines printed.
|
||||
-- @usage
|
||||
-- local width, height = term.getSize()
|
||||
-- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2)
|
||||
function pagedPrint(_sText, _nFreeLines)
|
||||
expect(2, _nFreeLines, "number", "nil")
|
||||
-- Setup a redirector
|
||||
local oldTerm = term.current()
|
||||
local newTerm = {}
|
||||
for k, v in pairs( oldTerm ) do
|
||||
for k, v in pairs(oldTerm) do
|
||||
newTerm[k] = v
|
||||
end
|
||||
newTerm.scroll = makePagedScroll( oldTerm, _nFreeLines )
|
||||
term.redirect( newTerm )
|
||||
newTerm.scroll = makePagedScroll(oldTerm, _nFreeLines)
|
||||
term.redirect(newTerm)
|
||||
|
||||
-- Print the text
|
||||
local result
|
||||
local ok, err = pcall( function()
|
||||
local ok, err = pcall(function()
|
||||
if _sText ~= nil then
|
||||
result = print( _sText )
|
||||
result = print(_sText)
|
||||
else
|
||||
result = print()
|
||||
end
|
||||
end )
|
||||
end)
|
||||
|
||||
-- Removed the redirector
|
||||
term.redirect( oldTerm )
|
||||
term.redirect(oldTerm)
|
||||
|
||||
-- Propogate errors
|
||||
if not ok then
|
||||
error( err, 0 )
|
||||
error(err, 0)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function tabulateCommon( bPaged, ... )
|
||||
local function tabulateCommon(bPaged, ...)
|
||||
local tAll = table.pack(...)
|
||||
for i = 1, tAll.n do
|
||||
expect(i, tAll[i], "number", "table")
|
||||
@ -110,17 +160,17 @@ local function tabulateCommon( bPaged, ... )
|
||||
|
||||
local w, h = term.getSize()
|
||||
local nMaxLen = w / 8
|
||||
for n, t in ipairs( tAll ) do
|
||||
for n, t in ipairs(tAll) do
|
||||
if type(t) == "table" then
|
||||
for nu, sItem in pairs(t) do
|
||||
if type( sItem ) ~= "string" then
|
||||
error( "bad argument #" .. n .. "." .. nu .. " (expected string, got " .. type( sItem ) .. ")", 3 )
|
||||
if type(sItem) ~= "string" then
|
||||
error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. type(sItem) .. ")", 3)
|
||||
end
|
||||
nMaxLen = math.max( #sItem + 1, nMaxLen )
|
||||
nMaxLen = math.max(#sItem + 1, nMaxLen)
|
||||
end
|
||||
end
|
||||
end
|
||||
local nCols = math.floor( w / nMaxLen )
|
||||
local nCols = math.floor(w / nMaxLen)
|
||||
local nLines = 0
|
||||
local function newLine()
|
||||
if bPaged and nLines >= h - 3 then
|
||||
@ -131,9 +181,9 @@ local function tabulateCommon( bPaged, ... )
|
||||
nLines = nLines + 1
|
||||
end
|
||||
|
||||
local function drawCols( _t )
|
||||
local function drawCols(_t)
|
||||
local nCol = 1
|
||||
for _, s in ipairs( _t ) do
|
||||
for _, s in ipairs(_t) do
|
||||
if nCol > nCols then
|
||||
nCol = 1
|
||||
newLine()
|
||||
@ -141,61 +191,81 @@ local function tabulateCommon( bPaged, ... )
|
||||
|
||||
local cx, cy = term.getCursorPos()
|
||||
cx = 1 + (nCol - 1) * nMaxLen
|
||||
term.setCursorPos( cx, cy )
|
||||
term.write( s )
|
||||
term.setCursorPos(cx, cy)
|
||||
term.write(s)
|
||||
|
||||
nCol = nCol + 1
|
||||
end
|
||||
print()
|
||||
end
|
||||
for _, t in ipairs( tAll ) do
|
||||
for _, t in ipairs(tAll) do
|
||||
if type(t) == "table" then
|
||||
if #t > 0 then
|
||||
drawCols( t )
|
||||
drawCols(t)
|
||||
end
|
||||
elseif type(t) == "number" then
|
||||
term.setTextColor( t )
|
||||
term.setTextColor(t)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function tabulate( ... )
|
||||
return tabulateCommon( false, ... )
|
||||
--- Prints tables in a structured form.
|
||||
--
|
||||
-- This accepts multiple arguments, either a table or a number. When
|
||||
-- encountering a table, this will be treated as a table row, with each column
|
||||
-- width being auto-adjusted.
|
||||
--
|
||||
-- When encountering a number, this sets the text color of the subsequent rows to it.
|
||||
--
|
||||
-- @tparam {string...}|number ... The rows and text colors to display.
|
||||
-- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" })
|
||||
function tabulate(...)
|
||||
return tabulateCommon(false, ...)
|
||||
end
|
||||
|
||||
function pagedTabulate( ... )
|
||||
return tabulateCommon( true, ... )
|
||||
--- Prints tables in a structured form, stopping and prompting for input should
|
||||
-- the result not fit on the terminal.
|
||||
--
|
||||
-- This functions identically to @{textutils.tabulate}, but will prompt for user
|
||||
-- input should the whole output not fit on the display.
|
||||
--
|
||||
-- @tparam {string...}|number ... The rows and text colors to display.
|
||||
-- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" })
|
||||
-- @see textutils.tabulate
|
||||
-- @see textutils.pagedPrint
|
||||
function pagedTabulate(...)
|
||||
return tabulateCommon(true, ...)
|
||||
end
|
||||
|
||||
local g_tLuaKeywords = {
|
||||
[ "and" ] = true,
|
||||
[ "break" ] = true,
|
||||
[ "do" ] = true,
|
||||
[ "else" ] = true,
|
||||
[ "elseif" ] = true,
|
||||
[ "end" ] = true,
|
||||
[ "false" ] = true,
|
||||
[ "for" ] = true,
|
||||
[ "function" ] = true,
|
||||
[ "if" ] = true,
|
||||
[ "in" ] = true,
|
||||
[ "local" ] = true,
|
||||
[ "nil" ] = true,
|
||||
[ "not" ] = true,
|
||||
[ "or" ] = true,
|
||||
[ "repeat" ] = true,
|
||||
[ "return" ] = true,
|
||||
[ "then" ] = true,
|
||||
[ "true" ] = true,
|
||||
[ "until" ] = true,
|
||||
[ "while" ] = true,
|
||||
["and"] = true,
|
||||
["break"] = true,
|
||||
["do"] = true,
|
||||
["else"] = true,
|
||||
["elseif"] = true,
|
||||
["end"] = true,
|
||||
["false"] = true,
|
||||
["for"] = true,
|
||||
["function"] = true,
|
||||
["if"] = true,
|
||||
["in"] = true,
|
||||
["local"] = true,
|
||||
["nil"] = true,
|
||||
["not"] = true,
|
||||
["or"] = true,
|
||||
["repeat"] = true,
|
||||
["return"] = true,
|
||||
["then"] = true,
|
||||
["true"] = true,
|
||||
["until"] = true,
|
||||
["while"] = true,
|
||||
}
|
||||
|
||||
local function serializeImpl( t, tTracking, sIndent )
|
||||
local function serializeImpl(t, tTracking, sIndent)
|
||||
local sType = type(t)
|
||||
if sType == "table" then
|
||||
if tTracking[t] ~= nil then
|
||||
error( "Cannot serialize table with recursive entries", 0 )
|
||||
error("Cannot serialize table with recursive entries", 0)
|
||||
end
|
||||
tTracking[t] = true
|
||||
|
||||
@ -209,15 +279,15 @@ local function serializeImpl( t, tTracking, sIndent )
|
||||
local tSeen = {}
|
||||
for k, v in ipairs(t) do
|
||||
tSeen[k] = true
|
||||
sResult = sResult .. sSubIndent .. serializeImpl( v, tTracking, sSubIndent ) .. ",\n"
|
||||
sResult = sResult .. sSubIndent .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
|
||||
end
|
||||
for k, v in pairs(t) do
|
||||
if not tSeen[k] then
|
||||
local sEntry
|
||||
if type(k) == "string" and not g_tLuaKeywords[k] and string.match( k, "^[%a_][%a%d_]*$" ) then
|
||||
sEntry = k .. " = " .. serializeImpl( v, tTracking, sSubIndent ) .. ",\n"
|
||||
if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
|
||||
sEntry = k .. " = " .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
|
||||
else
|
||||
sEntry = "[ " .. serializeImpl( k, tTracking, sSubIndent ) .. " ] = " .. serializeImpl( v, tTracking, sSubIndent ) .. ",\n"
|
||||
sEntry = "[ " .. serializeImpl(k, tTracking, sSubIndent) .. " ] = " .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
|
||||
end
|
||||
sResult = sResult .. sSubIndent .. sEntry
|
||||
end
|
||||
@ -227,31 +297,52 @@ local function serializeImpl( t, tTracking, sIndent )
|
||||
end
|
||||
|
||||
elseif sType == "string" then
|
||||
return string.format( "%q", t )
|
||||
return string.format("%q", t)
|
||||
|
||||
elseif sType == "number" or sType == "boolean" or sType == "nil" then
|
||||
return tostring(t)
|
||||
|
||||
else
|
||||
error( "Cannot serialize type " .. sType, 0 )
|
||||
error("Cannot serialize type " .. sType, 0)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
empty_json_array = setmetatable({}, {
|
||||
__newindex = function()
|
||||
error("attempt to mutate textutils.empty_json_array", 2)
|
||||
end,
|
||||
})
|
||||
local function mk_tbl(str, name)
|
||||
local msg = "attempt to mutate textutils." .. name
|
||||
return setmetatable({}, {
|
||||
__newindex = function() error(msg, 2) end,
|
||||
__tostring = function() return str end,
|
||||
})
|
||||
end
|
||||
|
||||
local function serializeJSONImpl( t, tTracking, bNBTStyle )
|
||||
--- A table representing an empty JSON array, in order to distinguish it from an
|
||||
-- empty JSON object.
|
||||
--
|
||||
-- The contents of this table should not be modified.
|
||||
--
|
||||
-- @usage textutils.serialiseJSON(textutils.empty_json_array)
|
||||
-- @see textutils.serialiseJSON
|
||||
-- @see textutils.unserialiseJSON
|
||||
empty_json_array = mk_tbl("[]", "empty_json_array")
|
||||
|
||||
--- A table representing the JSON null value.
|
||||
--
|
||||
-- The contents of this table should not be modified.
|
||||
--
|
||||
-- @usage textutils.serialiseJSON(textutils.json_null)
|
||||
-- @see textutils.serialiseJSON
|
||||
-- @see textutils.unserialiseJSON
|
||||
json_null = mk_tbl("null", "json_null")
|
||||
|
||||
local function serializeJSONImpl(t, tTracking, bNBTStyle)
|
||||
local sType = type(t)
|
||||
if t == empty_json_array then
|
||||
return "[]"
|
||||
if t == empty_json_array then return "[]"
|
||||
elseif t == json_null then return "null"
|
||||
|
||||
elseif sType == "table" then
|
||||
if tTracking[t] ~= nil then
|
||||
error( "Cannot serialize table with recursive entries", 0 )
|
||||
error("Cannot serialize table with recursive entries", 0)
|
||||
end
|
||||
tTracking[t] = true
|
||||
|
||||
@ -268,9 +359,9 @@ local function serializeJSONImpl( t, tTracking, bNBTStyle )
|
||||
if type(k) == "string" then
|
||||
local sEntry
|
||||
if bNBTStyle then
|
||||
sEntry = tostring(k) .. ":" .. serializeJSONImpl( v, tTracking, bNBTStyle )
|
||||
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
|
||||
else
|
||||
sEntry = string.format( "%q", k ) .. ":" .. serializeJSONImpl( v, tTracking, bNBTStyle )
|
||||
sEntry = string.format("%q", k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
|
||||
end
|
||||
if nObjectSize == 0 then
|
||||
sObjectResult = sObjectResult .. sEntry
|
||||
@ -281,7 +372,7 @@ local function serializeJSONImpl( t, tTracking, bNBTStyle )
|
||||
end
|
||||
end
|
||||
for _, v in ipairs(t) do
|
||||
local sEntry = serializeJSONImpl( v, tTracking, bNBTStyle )
|
||||
local sEntry = serializeJSONImpl(v, tTracking, bNBTStyle)
|
||||
if nArraySize == 0 then
|
||||
sArrayResult = sArrayResult .. sEntry
|
||||
else
|
||||
@ -299,27 +390,247 @@ local function serializeJSONImpl( t, tTracking, bNBTStyle )
|
||||
end
|
||||
|
||||
elseif sType == "string" then
|
||||
return string.format( "%q", t )
|
||||
return string.format("%q", t)
|
||||
|
||||
elseif sType == "number" or sType == "boolean" then
|
||||
return tostring(t)
|
||||
|
||||
else
|
||||
error( "Cannot serialize type " .. sType, 0 )
|
||||
error("Cannot serialize type " .. sType, 0)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function serialize( t )
|
||||
local tTracking = {}
|
||||
return serializeImpl( t, tTracking, "" )
|
||||
local unserialise_json
|
||||
do
|
||||
local sub, find, match, concat, tonumber = string.sub, string.find, string.match, table.concat, tonumber
|
||||
|
||||
--- Skip any whitespace
|
||||
local function skip(str, pos)
|
||||
local _, last = find(str, "^[ \n\r\v]+", pos)
|
||||
if last then return last + 1 else return pos end
|
||||
end
|
||||
|
||||
local escapes = {
|
||||
["b"] = '\b', ["f"] = '\f', ["n"] = '\n', ["r"] = '\r', ["t"] = '\t',
|
||||
["\""] = "\"", ["/"] = "/", ["\\"] = "\\",
|
||||
}
|
||||
|
||||
local mt = {}
|
||||
|
||||
local function error_at(pos, msg, ...)
|
||||
if select('#', ...) > 0 then msg = msg:format(...) end
|
||||
error(setmetatable({ pos = pos, msg = msg }, mt))
|
||||
end
|
||||
|
||||
local function expected(pos, actual, exp)
|
||||
if actual == "" then actual = "end of input" else actual = ("%q"):format(actual) end
|
||||
error_at(pos, "Unexpected %s, expected %s.", actual, exp)
|
||||
end
|
||||
|
||||
local function parse_string(str, pos)
|
||||
local buf, n = {}, 1
|
||||
|
||||
while true do
|
||||
local c = sub(str, pos, pos)
|
||||
if c == "" then error_at(pos, "Unexpected end of input, expected '\"'.") end
|
||||
if c == '"' then break end
|
||||
|
||||
if c == '\\' then
|
||||
-- Handle the various escapes
|
||||
c = sub(str, pos + 1, pos + 1)
|
||||
if c == "" then error_at(pos, "Unexpected end of input, expected escape sequence.") end
|
||||
|
||||
if c == "u" then
|
||||
local num_str = match(str, "^%x%x%x%x", pos + 2)
|
||||
if not num_str then error_at(pos, "Malformed unicode escape %q.", sub(str, pos + 2, pos + 5)) end
|
||||
buf[n], n, pos = utf8.char(tonumber(num_str, 16)), n + 1, pos + 6
|
||||
else
|
||||
local unesc = escapes[c]
|
||||
if not unesc then error_at(pos + 1, "Unknown escape character %q.", unesc) end
|
||||
buf[n], n, pos = unesc, n + 1, pos + 2
|
||||
end
|
||||
elseif c >= '\x20' then
|
||||
buf[n], n, pos = c, n + 1, pos + 1
|
||||
else
|
||||
error_at(pos + 1, "Unescaped whitespace %q.", c)
|
||||
end
|
||||
end
|
||||
|
||||
return concat(buf, "", 1, n - 1), pos + 1
|
||||
end
|
||||
|
||||
local valid = { b = true, B = true, s = true, S = true, l = true, L = true, f = true, F = true, d = true, D = true }
|
||||
local function parse_number(str, pos, opts)
|
||||
local _, last, num_str = find(str, '^(-?%d+%.?%d*[eE]?[+-]?%d*)', pos)
|
||||
local val = tonumber(num_str)
|
||||
if not val then error_at(pos, "Malformed number %q.", num_str) end
|
||||
|
||||
if opts.nbt_style and valid[sub(str, pos + 1, pos + 1)] then return val, last + 2 end
|
||||
|
||||
return val, last + 1
|
||||
end
|
||||
|
||||
local function parse_ident(str, pos)
|
||||
local _, last, val = find(str, '^([%a][%w_]*)', pos)
|
||||
return val, last + 1
|
||||
end
|
||||
|
||||
local function decode_impl(str, pos, opts)
|
||||
local c = sub(str, pos, pos)
|
||||
if c == '"' then return parse_string(str, pos + 1)
|
||||
elseif c == "-" or c >= "0" and c <= "9" then return parse_number(str, pos, opts)
|
||||
elseif c == "t" then
|
||||
if sub(str, pos + 1, pos + 3) == "rue" then return true, pos + 4 end
|
||||
elseif c == 'f' then
|
||||
if sub(str, pos + 1, pos + 4) == "alse" then return false, pos + 5 end
|
||||
elseif c == 'n' then
|
||||
if sub(str, pos + 1, pos + 3) == "ull" then
|
||||
if opts.parse_null then
|
||||
return json_null, pos + 4
|
||||
else
|
||||
return nil, pos + 4
|
||||
end
|
||||
end
|
||||
elseif c == "{" then
|
||||
local obj = {}
|
||||
|
||||
pos = skip(str, pos + 1)
|
||||
c = sub(str, pos, pos)
|
||||
|
||||
if c == "" then return error_at(pos, "Unexpected end of input, expected '}'.") end
|
||||
if c == "}" then return obj, pos + 1 end
|
||||
|
||||
while true do
|
||||
local key, value
|
||||
if c == "\"" then key, pos = parse_string(str, pos + 1)
|
||||
elseif opts.nbt_style then key, pos = parse_ident(str, pos)
|
||||
else return expected(pos, c, "object key")
|
||||
end
|
||||
|
||||
pos = skip(str, pos)
|
||||
|
||||
c = sub(str, pos, pos)
|
||||
if c ~= ":" then return expected(pos, c, "':'") end
|
||||
|
||||
value, pos = decode_impl(str, skip(str, pos + 1), opts)
|
||||
obj[key] = value
|
||||
|
||||
-- Consume the next delimiter
|
||||
pos = skip(str, pos)
|
||||
c = sub(str, pos, pos)
|
||||
if c == "}" then break
|
||||
elseif c == "," then pos = skip(str, pos + 1)
|
||||
else return expected(pos, c, "',' or '}'")
|
||||
end
|
||||
|
||||
c = sub(str, pos, pos)
|
||||
end
|
||||
|
||||
return obj, pos + 1
|
||||
|
||||
elseif c == "[" then
|
||||
local arr, n = {}, 1
|
||||
|
||||
pos = skip(str, pos + 1)
|
||||
c = sub(str, pos, pos)
|
||||
|
||||
if c == "" then return expected(pos, c, "']'") end
|
||||
if c == "]" then return empty_json_array, pos + 1 end
|
||||
|
||||
while true do
|
||||
n, arr[n], pos = n + 1, decode_impl(str, pos, opts)
|
||||
|
||||
-- Consume the next delimiter
|
||||
pos = skip(str, pos)
|
||||
c = sub(str, pos, pos)
|
||||
if c == "]" then break
|
||||
elseif c == "," then pos = skip(str, pos + 1)
|
||||
else return expected(pos, c, "',' or ']'")
|
||||
end
|
||||
end
|
||||
|
||||
return arr, pos + 1
|
||||
elseif c == "" then error_at(pos, 'Unexpected end of input.')
|
||||
end
|
||||
|
||||
error_at(pos, "Unexpected character %q.", c)
|
||||
end
|
||||
|
||||
--- Converts a serialised JSON string back into a reassembled Lua object.
|
||||
--
|
||||
-- This may be used with @{textutils.serializeJSON}, or when communicating
|
||||
-- with command blocks or web APIs.
|
||||
--
|
||||
-- @tparam string s The serialised string to deserialise.
|
||||
-- @tparam[opt] { nbt_style? = boolean, parse_null? = boolean } options
|
||||
-- Options which control how this JSON object is parsed.
|
||||
--
|
||||
-- - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings,
|
||||
-- as produced by many commands.
|
||||
-- - `parse_null`: When true, `null` will be parsed as @{json_null}, rather
|
||||
-- than `nil`.
|
||||
--
|
||||
-- [nbt]: https://minecraft.gamepedia.com/NBT_format
|
||||
-- @return[1] The deserialised object
|
||||
-- @treturn[2] nil If the object could not be deserialised.
|
||||
-- @treturn string A message describing why the JSON string is invalid.
|
||||
unserialise_json = function(s, options)
|
||||
expect(1, s, "string")
|
||||
expect(2, options, "table", "nil")
|
||||
|
||||
if options then
|
||||
field(options, "nbt_style", "boolean", "nil")
|
||||
field(options, "nbt_style", "boolean", "nil")
|
||||
else
|
||||
options = {}
|
||||
end
|
||||
|
||||
local ok, res, pos = pcall(decode_impl, s, skip(s, 1), options)
|
||||
if not ok then
|
||||
if type(res) == "table" and getmetatable(res) == mt then
|
||||
return nil, ("Malformed JSON at position %d: %s"):format(res.pos, res.msg)
|
||||
end
|
||||
|
||||
error(res, 0)
|
||||
end
|
||||
|
||||
pos = skip(s, pos)
|
||||
if pos <= #s then
|
||||
return nil, ("Malformed JSON at position %d: Unexpected trailing character %q."):format(pos, sub(s, pos, pos))
|
||||
end
|
||||
return res
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function unserialize( s )
|
||||
--- Convert a Lua object into a textual representation, suitable for
|
||||
-- saving in a file or pretty-printing.
|
||||
--
|
||||
-- @param t The object to serialise
|
||||
-- @treturn string The serialised representation
|
||||
-- @throws If the object contains a value which cannot be
|
||||
-- serialised. This includes functions and tables which appear multiple
|
||||
-- times.
|
||||
function serialize(t)
|
||||
local tTracking = {}
|
||||
return serializeImpl(t, tTracking, "")
|
||||
end
|
||||
|
||||
serialise = serialize -- GB version
|
||||
|
||||
--- Converts a serialised string back into a reassembled Lua object.
|
||||
--
|
||||
-- This is mainly used together with @{textutils.serialize}.
|
||||
--
|
||||
-- @tparam string s The serialised string to deserialise.
|
||||
-- @return[1] The deserialised object
|
||||
-- @treturn[2] nil If the object could not be deserialised.
|
||||
function unserialize(s)
|
||||
expect(1, s, "string")
|
||||
local func = load( "return " .. s, "unserialize", "t", {} )
|
||||
local func = load("return " .. s, "unserialize", "t", {})
|
||||
if func then
|
||||
local ok, result = pcall( func )
|
||||
local ok, result = pcall(func)
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
@ -327,14 +638,44 @@ function unserialize( s )
|
||||
return nil
|
||||
end
|
||||
|
||||
function serializeJSON( t, bNBTStyle )
|
||||
unserialise = unserialize -- GB version
|
||||
|
||||
--- Returns a JSON representation of the given data.
|
||||
--
|
||||
-- This function attempts to guess whether a table is a JSON array or
|
||||
-- object. However, empty tables are assumed to be empty objects - use
|
||||
-- @{textutils.empty_json_array} to mark an empty array.
|
||||
--
|
||||
-- This is largely intended for interacting with various functions from the
|
||||
-- @{commands} API, though may also be used in making @{http} requests.
|
||||
--
|
||||
-- @param t The value to serialise. Like @{textutils.serialise}, this should not
|
||||
-- contain recursive tables or functions.
|
||||
-- @tparam[opt] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
|
||||
-- instead of standard JSON.
|
||||
-- @treturn string The JSON representation of the input.
|
||||
-- @throws If the object contains a value which cannot be
|
||||
-- serialised. This includes functions and tables which appear multiple
|
||||
-- times.
|
||||
-- @usage textutils.serializeJSON({ values = { 1, "2", true } })
|
||||
function serializeJSON(t, bNBTStyle)
|
||||
expect(1, t, "table", "string", "number", "boolean")
|
||||
expect(2, bNBTStyle, "boolean", "nil")
|
||||
local tTracking = {}
|
||||
return serializeJSONImpl( t, tTracking, bNBTStyle or false )
|
||||
return serializeJSONImpl(t, tTracking, bNBTStyle or false)
|
||||
end
|
||||
|
||||
function urlEncode( str )
|
||||
serialiseJSON = serializeJSON -- GB version
|
||||
|
||||
unserializeJSON = unserialise_json
|
||||
unserialiseJSON = unserialise_json
|
||||
|
||||
--- Replaces certain characters in a string to make it safe for use in URLs or POST data.
|
||||
--
|
||||
-- @tparam string str The string to encode
|
||||
-- @treturn string The encoded string.
|
||||
-- @usage print("https://example.com/?view=" .. textutils.urlEncode(read()))
|
||||
function urlEncode(str)
|
||||
expect(1, str, "string")
|
||||
if str then
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
@ -346,40 +687,57 @@ function urlEncode( str )
|
||||
else
|
||||
-- Non-ASCII (encode as UTF-8)
|
||||
return
|
||||
string.format("%%%02X", 192 + bit32.band( bit32.arshift(n, 6), 31 ) ) ..
|
||||
string.format("%%%02X", 128 + bit32.band( n, 63 ) )
|
||||
string.format("%%%02X", 192 + bit32.band(bit32.arshift(n, 6), 31)) ..
|
||||
string.format("%%%02X", 128 + bit32.band(n, 63))
|
||||
end
|
||||
end )
|
||||
end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
local tEmpty = {}
|
||||
function complete( sSearchText, tSearchTable )
|
||||
|
||||
--- Provides a list of possible completions for a partial Lua expression.
|
||||
--
|
||||
-- If the completed element is a table, suggestions will have `.` appended to
|
||||
-- them. Similarly, functions have `(` appended to them.
|
||||
--
|
||||
-- @tparam string sSearchText The partial expression to complete, such as a
|
||||
-- variable name or table index.
|
||||
--
|
||||
-- @tparam[opt] table tSearchTable The table to find variables in, defaulting to
|
||||
-- the global environment (@{_G}). The function also searches the "parent"
|
||||
-- environment via the `__index` metatable field.
|
||||
--
|
||||
-- @treturn { string... } The (possibly empty) list of completions.
|
||||
-- @see shell.setCompletionFunction
|
||||
-- @see read
|
||||
-- @usage textutils.complete( "pa", getfenv() )
|
||||
function complete(sSearchText, tSearchTable)
|
||||
expect(1, sSearchText, "string")
|
||||
expect(2, tSearchTable, "table", "nil")
|
||||
|
||||
if g_tLuaKeywords[sSearchText] then return tEmpty end
|
||||
local nStart = 1
|
||||
local nDot = string.find( sSearchText, ".", nStart, true )
|
||||
local nDot = string.find(sSearchText, ".", nStart, true)
|
||||
local tTable = tSearchTable or _ENV
|
||||
while nDot do
|
||||
local sPart = string.sub( sSearchText, nStart, nDot - 1 )
|
||||
local value = tTable[ sPart ]
|
||||
if type( value ) == "table" then
|
||||
local sPart = string.sub(sSearchText, nStart, nDot - 1)
|
||||
local value = tTable[sPart]
|
||||
if type(value) == "table" then
|
||||
tTable = value
|
||||
nStart = nDot + 1
|
||||
nDot = string.find( sSearchText, ".", nStart, true )
|
||||
nDot = string.find(sSearchText, ".", nStart, true)
|
||||
else
|
||||
return tEmpty
|
||||
end
|
||||
end
|
||||
local nColon = string.find( sSearchText, ":", nStart, true )
|
||||
local nColon = string.find(sSearchText, ":", nStart, true)
|
||||
if nColon then
|
||||
local sPart = string.sub( sSearchText, nStart, nColon - 1 )
|
||||
local value = tTable[ sPart ]
|
||||
if type( value ) == "table" then
|
||||
local sPart = string.sub(sSearchText, nStart, nColon - 1)
|
||||
local value = tTable[sPart]
|
||||
if type(value) == "table" then
|
||||
tTable = value
|
||||
nStart = nColon + 1
|
||||
else
|
||||
@ -387,24 +745,24 @@ function complete( sSearchText, tSearchTable )
|
||||
end
|
||||
end
|
||||
|
||||
local sPart = string.sub( sSearchText, nStart )
|
||||
local sPart = string.sub(sSearchText, nStart)
|
||||
local nPartLength = #sPart
|
||||
|
||||
local tResults = {}
|
||||
local tSeen = {}
|
||||
while tTable do
|
||||
for k, v in pairs( tTable ) do
|
||||
for k, v in pairs(tTable) do
|
||||
if not tSeen[k] and type(k) == "string" then
|
||||
if string.find( k, sPart, 1, true ) == 1 then
|
||||
if not g_tLuaKeywords[k] and string.match( k, "^[%a_][%a%d_]*$" ) then
|
||||
local sResult = string.sub( k, nPartLength + 1 )
|
||||
if string.find(k, sPart, 1, true) == 1 then
|
||||
if not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
|
||||
local sResult = string.sub(k, nPartLength + 1)
|
||||
if nColon then
|
||||
if type(v) == "function" then
|
||||
table.insert( tResults, sResult .. "(" )
|
||||
table.insert(tResults, sResult .. "(")
|
||||
elseif type(v) == "table" then
|
||||
local tMetatable = getmetatable( v )
|
||||
if tMetatable and ( type( tMetatable.__call ) == "function" or type( tMetatable.__call ) == "table" ) then
|
||||
table.insert( tResults, sResult .. "(" )
|
||||
local tMetatable = getmetatable(v)
|
||||
if tMetatable and (type(tMetatable.__call) == "function" or type(tMetatable.__call) == "table") then
|
||||
table.insert(tResults, sResult .. "(")
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -413,26 +771,21 @@ function complete( sSearchText, tSearchTable )
|
||||
elseif type(v) == "table" and next(v) ~= nil then
|
||||
sResult = sResult .. "."
|
||||
end
|
||||
table.insert( tResults, sResult )
|
||||
table.insert(tResults, sResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
tSeen[k] = true
|
||||
end
|
||||
local tMetatable = getmetatable( tTable )
|
||||
if tMetatable and type( tMetatable.__index ) == "table" then
|
||||
local tMetatable = getmetatable(tTable)
|
||||
if tMetatable and type(tMetatable.__index) == "table" then
|
||||
tTable = tMetatable.__index
|
||||
else
|
||||
tTable = nil
|
||||
end
|
||||
end
|
||||
|
||||
table.sort( tResults )
|
||||
table.sort(tResults)
|
||||
return tResults
|
||||
end
|
||||
|
||||
-- GB versions
|
||||
serialise = serialize
|
||||
unserialise = unserialize
|
||||
serialiseJSON = serializeJSON
|
||||
|
@ -1,17 +1,20 @@
|
||||
--- The turtle API allows you to control your turtle.
|
||||
--
|
||||
-- @module turtle
|
||||
|
||||
if not turtle then
|
||||
error( "Cannot load turtle API on computer", 2 )
|
||||
error("Cannot load turtle API on computer", 2)
|
||||
end
|
||||
native = turtle.native or turtle
|
||||
native = turtle.native or turtle --- @local
|
||||
|
||||
local function addCraftMethod( object )
|
||||
if peripheral.getType( "left" ) == "workbench" then
|
||||
object.craft = function( ... )
|
||||
return peripheral.call( "left", "craft", ... )
|
||||
local function addCraftMethod(object)
|
||||
if peripheral.getType("left") == "workbench" then
|
||||
object.craft = function(...)
|
||||
return peripheral.call("left", "craft", ...)
|
||||
end
|
||||
elseif peripheral.getType( "right" ) == "workbench" then
|
||||
object.craft = function( ... )
|
||||
return peripheral.call( "right", "craft", ... )
|
||||
elseif peripheral.getType("right") == "workbench" then
|
||||
object.craft = function(...)
|
||||
return peripheral.call("right", "craft", ...)
|
||||
end
|
||||
else
|
||||
object.craft = nil
|
||||
@ -20,15 +23,15 @@ end
|
||||
|
||||
-- Put commands into environment table
|
||||
local env = _ENV
|
||||
for k, v in pairs( native ) do
|
||||
for k, v in pairs(native) do
|
||||
if k == "equipLeft" or k == "equipRight" then
|
||||
env[k] = function( ... )
|
||||
local result, err = v( ... )
|
||||
addCraftMethod( turtle )
|
||||
env[k] = function(...)
|
||||
local result, err = v(...)
|
||||
addCraftMethod(turtle)
|
||||
return result, err
|
||||
end
|
||||
else
|
||||
env[k] = v
|
||||
end
|
||||
end
|
||||
addCraftMethod( env )
|
||||
addCraftMethod(env)
|
||||
|
@ -1,85 +1,178 @@
|
||||
--- The vector API provides methods to create and manipulate vectors.
|
||||
--
|
||||
-- An introduction to vectors can be found on [Wikipedia][wiki].
|
||||
--
|
||||
-- [wiki]: http://en.wikipedia.org/wiki/Euclidean_vector
|
||||
--
|
||||
-- @module vector
|
||||
|
||||
--- A 3-dimensional vector, with `x`, `y`, and `z` values.
|
||||
--
|
||||
-- This is suitable for representing both position and directional vectors.
|
||||
--
|
||||
-- @type Vector
|
||||
local vector = {
|
||||
add = function( self, o )
|
||||
return vector.new(
|
||||
self.x + o.x,
|
||||
self.y + o.y,
|
||||
self.z + o.z
|
||||
)
|
||||
end,
|
||||
sub = function( self, o )
|
||||
return vector.new(
|
||||
self.x - o.x,
|
||||
self.y - o.y,
|
||||
self.z - o.z
|
||||
)
|
||||
end,
|
||||
mul = function( self, m )
|
||||
return vector.new(
|
||||
self.x * m,
|
||||
self.y * m,
|
||||
self.z * m
|
||||
)
|
||||
end,
|
||||
div = function( self, m )
|
||||
return vector.new(
|
||||
self.x / m,
|
||||
self.y / m,
|
||||
self.z / m
|
||||
)
|
||||
end,
|
||||
unm = function( self )
|
||||
return vector.new(
|
||||
-self.x,
|
||||
-self.y,
|
||||
-self.z
|
||||
)
|
||||
end,
|
||||
dot = function( self, o )
|
||||
return self.x * o.x + self.y * o.y + self.z * o.z
|
||||
end,
|
||||
cross = function( self, o )
|
||||
return vector.new(
|
||||
self.y * o.z - self.z * o.y,
|
||||
self.z * o.x - self.x * o.z,
|
||||
self.x * o.y - self.y * o.x
|
||||
)
|
||||
end,
|
||||
length = function( self )
|
||||
return math.sqrt( self.x * self.x + self.y * self.y + self.z * self.z )
|
||||
end,
|
||||
normalize = function( self )
|
||||
return self:mul( 1 / self:length() )
|
||||
end,
|
||||
round = function( self, nTolerance )
|
||||
nTolerance = nTolerance or 1.0
|
||||
return vector.new(
|
||||
math.floor( (self.x + nTolerance * 0.5) / nTolerance ) * nTolerance,
|
||||
math.floor( (self.y + nTolerance * 0.5) / nTolerance ) * nTolerance,
|
||||
math.floor( (self.z + nTolerance * 0.5) / nTolerance ) * nTolerance
|
||||
)
|
||||
end,
|
||||
tostring = function( self )
|
||||
return self.x .. "," .. self.y .. "," .. self.z
|
||||
end,
|
||||
--- Adds two vectors together.
|
||||
--
|
||||
-- @tparam Vector self The first vector to add.
|
||||
-- @tparam Vector o The second vector to add.
|
||||
-- @treturn Vector The resulting vector
|
||||
-- @usage v1:add(v2)
|
||||
-- @usage v1 + v2
|
||||
add = function(self, o)
|
||||
return vector.new(
|
||||
self.x + o.x,
|
||||
self.y + o.y,
|
||||
self.z + o.z
|
||||
)
|
||||
end,
|
||||
|
||||
--- Subtracts one vector from another.
|
||||
--
|
||||
-- @tparam Vector self The vector to subtract from.
|
||||
-- @tparam Vector o The vector to subtract.
|
||||
-- @treturn Vector The resulting vector
|
||||
-- @usage v1:sub(v2)
|
||||
-- @usage v1 - v2
|
||||
sub = function(self, o)
|
||||
return vector.new(
|
||||
self.x - o.x,
|
||||
self.y - o.y,
|
||||
self.z - o.z
|
||||
)
|
||||
end,
|
||||
|
||||
--- Multiplies a vector by a scalar value.
|
||||
--
|
||||
-- @tparam Vector self The vector to multiply.
|
||||
-- @tparam number m The scalar value to multiply with.
|
||||
-- @treturn Vector A vector with value `(x * m, y * m, z * m)`.
|
||||
-- @usage v:mul(3)
|
||||
-- @usage v * 3
|
||||
mul = function(self, m)
|
||||
return vector.new(
|
||||
self.x * m,
|
||||
self.y * m,
|
||||
self.z * m
|
||||
)
|
||||
end,
|
||||
|
||||
--- Divides a vector by a scalar value.
|
||||
--
|
||||
-- @tparam Vector self The vector to divide.
|
||||
-- @tparam number m The scalar value to divide by.
|
||||
-- @treturn Vector A vector with value `(x / m, y / m, z / m)`.
|
||||
-- @usage v:div(3)
|
||||
-- @usage v / 3
|
||||
div = function(self, m)
|
||||
return vector.new(
|
||||
self.x / m,
|
||||
self.y / m,
|
||||
self.z / m
|
||||
)
|
||||
end,
|
||||
|
||||
--- Negate a vector
|
||||
--
|
||||
-- @tparam Vector self The vector to negate.
|
||||
-- @treturn Vector The negated vector.
|
||||
-- @usage -v
|
||||
unm = function(self)
|
||||
return vector.new(
|
||||
-self.x,
|
||||
-self.y,
|
||||
-self.z
|
||||
)
|
||||
end,
|
||||
|
||||
--- Compute the dot product of two vectors
|
||||
--
|
||||
-- @tparam Vector self The first vector to compute the dot product of.
|
||||
-- @tparam Vector o The second vector to compute the dot product of.
|
||||
-- @treturn Vector The dot product of `self` and `o`.
|
||||
-- @usage v1:dot(v2)
|
||||
dot = function(self, o)
|
||||
return self.x * o.x + self.y * o.y + self.z * o.z
|
||||
end,
|
||||
|
||||
--- Compute the cross product of two vectors
|
||||
--
|
||||
-- @tparam Vector self The first vector to compute the cross product of.
|
||||
-- @tparam Vector o The second vector to compute the cross product of.
|
||||
-- @treturn Vector The cross product of `self` and `o`.
|
||||
-- @usage v1:cross(v2)
|
||||
cross = function(self, o)
|
||||
return vector.new(
|
||||
self.y * o.z - self.z * o.y,
|
||||
self.z * o.x - self.x * o.z,
|
||||
self.x * o.y - self.y * o.x
|
||||
)
|
||||
end,
|
||||
|
||||
--- Get the length (also referred to as magnitude) of this vector.
|
||||
-- @tparam Vector self This vector.
|
||||
-- @treturn number The length of this vector.
|
||||
length = function(self)
|
||||
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
|
||||
end,
|
||||
|
||||
--- Divide this vector by its length, producing with the same direction, but
|
||||
-- of length 1.
|
||||
--
|
||||
-- @tparam Vector self The vector to normalise
|
||||
-- @treturn Vector The normalised vector
|
||||
-- @usage v:normalize()
|
||||
normalize = function(self)
|
||||
return self:mul(1 / self:length())
|
||||
end,
|
||||
|
||||
--- Construct a vector with each dimension rounded to the nearest value.
|
||||
--
|
||||
-- @tparam Vector self The vector to round
|
||||
-- @tparam[opt] number tolerance The tolerance that we should round to,
|
||||
-- defaulting to 1. For instance, a tolerance of 0.5 will round to the
|
||||
-- nearest 0.5.
|
||||
-- @treturn Vector The rounded vector.
|
||||
round = function(self, tolerance)
|
||||
tolerance = tolerance or 1.0
|
||||
return vector.new(
|
||||
math.floor((self.x + tolerance * 0.5) / tolerance) * tolerance,
|
||||
math.floor((self.y + tolerance * 0.5) / tolerance) * tolerance,
|
||||
math.floor((self.z + tolerance * 0.5) / tolerance) * tolerance
|
||||
)
|
||||
end,
|
||||
|
||||
--- Convert this vector into a string, for pretty printing.
|
||||
--
|
||||
-- @tparam Vector self This vector.
|
||||
-- @treturn string This vector's string representation.
|
||||
-- @usage v:tostring()
|
||||
-- @usage tostring(v)
|
||||
tostring = function(self)
|
||||
return self.x .. "," .. self.y .. "," .. self.z
|
||||
end,
|
||||
}
|
||||
|
||||
local vmetatable = {
|
||||
__index = vector,
|
||||
__add = vector.add,
|
||||
__sub = vector.sub,
|
||||
__mul = vector.mul,
|
||||
__div = vector.div,
|
||||
__unm = vector.unm,
|
||||
__tostring = vector.tostring,
|
||||
__index = vector,
|
||||
__add = vector.add,
|
||||
__sub = vector.sub,
|
||||
__mul = vector.mul,
|
||||
__div = vector.div,
|
||||
__unm = vector.unm,
|
||||
__tostring = vector.tostring,
|
||||
}
|
||||
|
||||
function new( x, y, z )
|
||||
local v = {
|
||||
x = tonumber(x) or 0,
|
||||
y = tonumber(y) or 0,
|
||||
z = tonumber(z) or 0,
|
||||
}
|
||||
setmetatable( v, vmetatable )
|
||||
return v
|
||||
--- Construct a new @{Vector} with the given coordinates.
|
||||
--
|
||||
-- @tparam number x The X coordinate or direction of the vector.
|
||||
-- @tparam number y The Y coordinate or direction of the vector.
|
||||
-- @tparam number z The Z coordinate or direction of the vector.
|
||||
-- @treturn Vector The constructed vector.
|
||||
function new(x, y, z)
|
||||
return setmetatable({
|
||||
x = tonumber(x) or 0,
|
||||
y = tonumber(y) or 0,
|
||||
z = tonumber(z) or 0,
|
||||
}, vmetatable)
|
||||
end
|
||||
|
@ -1,29 +1,76 @@
|
||||
--- The Window API allows easy definition of spaces within the display that can
|
||||
-- be written/drawn to, then later redrawn/repositioned/etc as need be. The API
|
||||
-- itself contains only one function, @{window.create}, which returns the
|
||||
-- windows themselves.
|
||||
--
|
||||
-- Windows are considered terminal objects - as such, they have access to nearly
|
||||
-- all the commands in the term API (plus a few extras of their own, listed
|
||||
-- within said API) and are valid targets to redirect to.
|
||||
--
|
||||
-- Each window has a "parent" terminal object, which can be the computer's own
|
||||
-- display, a monitor, another window or even other, user-defined terminal
|
||||
-- objects. Whenever a window is rendered to, the actual screen-writing is
|
||||
-- performed via that parent (or, if that has one too, then that parent, and so
|
||||
-- forth). Bear in mind that the cursor of a window's parent will hence be moved
|
||||
-- around etc when writing a given child window.
|
||||
--
|
||||
-- Windows retain a memory of everything rendered "through" them (hence acting
|
||||
-- as display buffers), and if the parent's display is wiped, the window's
|
||||
-- content can be easily redrawn later. A window may also be flagged as
|
||||
-- invisible, preventing any changes to it from being rendered until it's
|
||||
-- flagged as visible once more.
|
||||
--
|
||||
-- A parent terminal object may have multiple children assigned to it, and
|
||||
-- windows may overlap. For example, the Multishell system functions by
|
||||
-- assigning each tab a window covering the screen, each using the starting
|
||||
-- terminal display as its parent, and only one of which is visible at a time.
|
||||
--
|
||||
-- @module window
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local tHex = {
|
||||
[ colors.white ] = "0",
|
||||
[ colors.orange ] = "1",
|
||||
[ colors.magenta ] = "2",
|
||||
[ colors.lightBlue ] = "3",
|
||||
[ colors.yellow ] = "4",
|
||||
[ colors.lime ] = "5",
|
||||
[ colors.pink ] = "6",
|
||||
[ colors.gray ] = "7",
|
||||
[ colors.lightGray ] = "8",
|
||||
[ colors.cyan ] = "9",
|
||||
[ colors.purple ] = "a",
|
||||
[ colors.blue ] = "b",
|
||||
[ colors.brown ] = "c",
|
||||
[ colors.green ] = "d",
|
||||
[ colors.red ] = "e",
|
||||
[ colors.black ] = "f",
|
||||
[colors.white] = "0",
|
||||
[colors.orange] = "1",
|
||||
[colors.magenta] = "2",
|
||||
[colors.lightBlue] = "3",
|
||||
[colors.yellow] = "4",
|
||||
[colors.lime] = "5",
|
||||
[colors.pink] = "6",
|
||||
[colors.gray] = "7",
|
||||
[colors.lightGray] = "8",
|
||||
[colors.cyan] = "9",
|
||||
[colors.purple] = "a",
|
||||
[colors.blue] = "b",
|
||||
[colors.brown] = "c",
|
||||
[colors.green] = "d",
|
||||
[colors.red] = "e",
|
||||
[colors.black] = "f",
|
||||
}
|
||||
|
||||
local type = type
|
||||
local string_rep = string.rep
|
||||
local string_sub = string.sub
|
||||
|
||||
function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
--- Returns a terminal object that is a space within the specified parent
|
||||
-- terminal object. This can then be used (or even redirected to) in the same
|
||||
-- manner as eg a wrapped monitor. Refer to @{term|the term API} for a list of
|
||||
-- functions available to it.
|
||||
--
|
||||
-- @{term} itself may not be passed as the parent, though @{term.native} is
|
||||
-- acceptable. Generally, @{term.current} or a wrapped monitor will be most
|
||||
-- suitable, though windows may even have other windows assigned as their
|
||||
-- parents.
|
||||
--
|
||||
-- @tparam term.Redirect parent The parent terminal redirect to draw to.
|
||||
-- @tparam number nX The x coordinate this window is drawn at in the parent terminal
|
||||
-- @tparam number nY The y coordinate this window is drawn at in the parent terminal
|
||||
-- @tparam number nWidth The width of this window
|
||||
-- @tparam number nHeight The height of this window
|
||||
-- @tparam[opt] boolean bStartVisible Whether this window is visible by
|
||||
-- default. Defaults to `true`.
|
||||
-- @treturn Window The constructed window
|
||||
function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
expect(1, parent, "table")
|
||||
expect(2, nX, "number")
|
||||
expect(3, nY, "number")
|
||||
@ -32,21 +79,21 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
expect(6, bStartVisible, "boolean", "nil")
|
||||
|
||||
if parent == term then
|
||||
error( "term is not a recommended window parent, try term.current() instead", 2 )
|
||||
error("term is not a recommended window parent, try term.current() instead", 2)
|
||||
end
|
||||
|
||||
local sEmptySpaceLine
|
||||
local tEmptyColorLines = {}
|
||||
local function createEmptyLines( nWidth )
|
||||
sEmptySpaceLine = string_rep( " ", nWidth )
|
||||
local function createEmptyLines(nWidth)
|
||||
sEmptySpaceLine = string_rep(" ", nWidth)
|
||||
for n = 0, 15 do
|
||||
local nColor = 2 ^ n
|
||||
local sHex = tHex[nColor]
|
||||
tEmptyColorLines[nColor] = string_rep( sHex, nWidth )
|
||||
tEmptyColorLines[nColor] = string_rep(sHex, nWidth)
|
||||
end
|
||||
end
|
||||
|
||||
createEmptyLines( nWidth )
|
||||
createEmptyLines(nWidth)
|
||||
|
||||
-- Setup
|
||||
local bVisible = bStartVisible ~= false
|
||||
@ -59,8 +106,8 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
local tPalette = {}
|
||||
do
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[ nTextColor ]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[ nBackgroundColor ]
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, nHeight do
|
||||
tLines[y] = {
|
||||
text = sEmptyText,
|
||||
@ -71,7 +118,7 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
|
||||
for i = 0, 15 do
|
||||
local c = 2 ^ i
|
||||
tPalette[c] = { parent.getPaletteColour( c ) }
|
||||
tPalette[c] = { parent.getPaletteColour(c) }
|
||||
end
|
||||
end
|
||||
|
||||
@ -79,45 +126,45 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
local function updateCursorPos()
|
||||
if nCursorX >= 1 and nCursorY >= 1 and
|
||||
nCursorX <= nWidth and nCursorY <= nHeight then
|
||||
parent.setCursorPos( nX + nCursorX - 1, nY + nCursorY - 1 )
|
||||
parent.setCursorPos(nX + nCursorX - 1, nY + nCursorY - 1)
|
||||
else
|
||||
parent.setCursorPos( 0, 0 )
|
||||
parent.setCursorPos(0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateCursorBlink()
|
||||
parent.setCursorBlink( bCursorBlink )
|
||||
parent.setCursorBlink(bCursorBlink)
|
||||
end
|
||||
|
||||
local function updateCursorColor()
|
||||
parent.setTextColor( nTextColor )
|
||||
parent.setTextColor(nTextColor)
|
||||
end
|
||||
|
||||
local function redrawLine( n )
|
||||
local tLine = tLines[ n ]
|
||||
parent.setCursorPos( nX, nY + n - 1 )
|
||||
parent.blit( tLine.text, tLine.textColor, tLine.backgroundColor )
|
||||
local function redrawLine(n)
|
||||
local tLine = tLines[n]
|
||||
parent.setCursorPos(nX, nY + n - 1)
|
||||
parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor)
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
for n = 1, nHeight do
|
||||
redrawLine( n )
|
||||
redrawLine(n)
|
||||
end
|
||||
end
|
||||
|
||||
local function updatePalette()
|
||||
for k, v in pairs( tPalette ) do
|
||||
parent.setPaletteColour( k, v[1], v[2], v[3] )
|
||||
for k, v in pairs(tPalette) do
|
||||
parent.setPaletteColour(k, v[1], v[2], v[3])
|
||||
end
|
||||
end
|
||||
|
||||
local function internalBlit( sText, sTextColor, sBackgroundColor )
|
||||
local function internalBlit(sText, sTextColor, sBackgroundColor)
|
||||
local nStart = nCursorX
|
||||
local nEnd = nStart + #sText - 1
|
||||
if nCursorY >= 1 and nCursorY <= nHeight then
|
||||
if nStart <= nWidth and nEnd >= 1 then
|
||||
-- Modify line
|
||||
local tLine = tLines[ nCursorY ]
|
||||
local tLine = tLines[nCursorY]
|
||||
if nStart == 1 and nEnd == nWidth then
|
||||
tLine.text = sText
|
||||
tLine.textColor = sTextColor
|
||||
@ -127,14 +174,14 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
if nStart < 1 then
|
||||
local nClipStart = 1 - nStart + 1
|
||||
local nClipEnd = nWidth - nStart + 1
|
||||
sClippedText = string_sub( sText, nClipStart, nClipEnd )
|
||||
sClippedTextColor = string_sub( sTextColor, nClipStart, nClipEnd )
|
||||
sClippedBackgroundColor = string_sub( sBackgroundColor, nClipStart, nClipEnd )
|
||||
sClippedText = string_sub(sText, nClipStart, nClipEnd)
|
||||
sClippedTextColor = string_sub(sTextColor, nClipStart, nClipEnd)
|
||||
sClippedBackgroundColor = string_sub(sBackgroundColor, nClipStart, nClipEnd)
|
||||
elseif nEnd > nWidth then
|
||||
local nClipEnd = nWidth - nStart + 1
|
||||
sClippedText = string_sub( sText, 1, nClipEnd )
|
||||
sClippedTextColor = string_sub( sTextColor, 1, nClipEnd )
|
||||
sClippedBackgroundColor = string_sub( sBackgroundColor, 1, nClipEnd )
|
||||
sClippedText = string_sub(sText, 1, nClipEnd)
|
||||
sClippedTextColor = string_sub(sTextColor, 1, nClipEnd)
|
||||
sClippedBackgroundColor = string_sub(sBackgroundColor, 1, nClipEnd)
|
||||
else
|
||||
sClippedText = sText
|
||||
sClippedTextColor = sTextColor
|
||||
@ -147,9 +194,9 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
local sNewText, sNewTextColor, sNewBackgroundColor
|
||||
if nStart > 1 then
|
||||
local nOldEnd = nStart - 1
|
||||
sNewText = string_sub( sOldText, 1, nOldEnd ) .. sClippedText
|
||||
sNewTextColor = string_sub( sOldTextColor, 1, nOldEnd ) .. sClippedTextColor
|
||||
sNewBackgroundColor = string_sub( sOldBackgroundColor, 1, nOldEnd ) .. sClippedBackgroundColor
|
||||
sNewText = string_sub(sOldText, 1, nOldEnd) .. sClippedText
|
||||
sNewTextColor = string_sub(sOldTextColor, 1, nOldEnd) .. sClippedTextColor
|
||||
sNewBackgroundColor = string_sub(sOldBackgroundColor, 1, nOldEnd) .. sClippedBackgroundColor
|
||||
else
|
||||
sNewText = sClippedText
|
||||
sNewTextColor = sClippedTextColor
|
||||
@ -157,9 +204,9 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
end
|
||||
if nEnd < nWidth then
|
||||
local nOldStart = nEnd + 1
|
||||
sNewText = sNewText .. string_sub( sOldText, nOldStart, nWidth )
|
||||
sNewTextColor = sNewTextColor .. string_sub( sOldTextColor, nOldStart, nWidth )
|
||||
sNewBackgroundColor = sNewBackgroundColor .. string_sub( sOldBackgroundColor, nOldStart, nWidth )
|
||||
sNewText = sNewText .. string_sub(sOldText, nOldStart, nWidth)
|
||||
sNewTextColor = sNewTextColor .. string_sub(sOldTextColor, nOldStart, nWidth)
|
||||
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
|
||||
end
|
||||
|
||||
tLine.text = sNewText
|
||||
@ -169,7 +216,7 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
|
||||
-- Redraw line
|
||||
if bVisible then
|
||||
redrawLine( nCursorY )
|
||||
redrawLine(nCursorY)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -182,28 +229,32 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
end
|
||||
end
|
||||
|
||||
-- Terminal implementation
|
||||
--- The window object. Refer to the @{window|module's documentation} for
|
||||
-- a full description.
|
||||
--
|
||||
-- @type Window
|
||||
-- @see term.Redirect
|
||||
local window = {}
|
||||
|
||||
function window.write( sText )
|
||||
sText = tostring( sText )
|
||||
internalBlit( sText, string_rep( tHex[ nTextColor ], #sText ), string_rep( tHex[ nBackgroundColor ], #sText ) )
|
||||
function window.write(sText)
|
||||
sText = tostring(sText)
|
||||
internalBlit(sText, string_rep(tHex[nTextColor], #sText), string_rep(tHex[nBackgroundColor], #sText))
|
||||
end
|
||||
|
||||
function window.blit( sText, sTextColor, sBackgroundColor )
|
||||
function window.blit(sText, sTextColor, sBackgroundColor)
|
||||
if type(sText) ~= "string" then expect(1, sText, "string") end
|
||||
if type(sTextColor) ~= "string" then expect(2, sTextColor, "string") end
|
||||
if type(sBackgroundColor) ~= "string" then expect(3, sBackgroundColor, "string") end
|
||||
if #sTextColor ~= #sText or #sBackgroundColor ~= #sText then
|
||||
error( "Arguments must be the same length", 2 )
|
||||
error("Arguments must be the same length", 2)
|
||||
end
|
||||
internalBlit( sText, sTextColor, sBackgroundColor )
|
||||
internalBlit(sText, sTextColor, sBackgroundColor)
|
||||
end
|
||||
|
||||
function window.clear()
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[ nTextColor ]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[ nBackgroundColor ]
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, nHeight do
|
||||
tLines[y] = {
|
||||
text = sEmptyText,
|
||||
@ -221,15 +272,15 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
function window.clearLine()
|
||||
if nCursorY >= 1 and nCursorY <= nHeight then
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[ nTextColor ]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[ nBackgroundColor ]
|
||||
tLines[ nCursorY ] = {
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
tLines[nCursorY] = {
|
||||
text = sEmptyText,
|
||||
textColor = sEmptyTextColor,
|
||||
backgroundColor = sEmptyBackgroundColor,
|
||||
}
|
||||
if bVisible then
|
||||
redrawLine( nCursorY )
|
||||
redrawLine(nCursorY)
|
||||
updateCursorColor()
|
||||
updateCursorPos()
|
||||
end
|
||||
@ -240,17 +291,17 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
return nCursorX, nCursorY
|
||||
end
|
||||
|
||||
function window.setCursorPos( x, y )
|
||||
function window.setCursorPos(x, y)
|
||||
if type(x) ~= "number" then expect(1, x, "number") end
|
||||
if type(y) ~= "number" then expect(2, y, "number") end
|
||||
nCursorX = math.floor( x )
|
||||
nCursorY = math.floor( y )
|
||||
nCursorX = math.floor(x)
|
||||
nCursorY = math.floor(y)
|
||||
if bVisible then
|
||||
updateCursorPos()
|
||||
end
|
||||
end
|
||||
|
||||
function window.setCursorBlink( blink )
|
||||
function window.setCursorBlink(blink)
|
||||
if type(blink) ~= "boolean" then expect(1, blink, "boolean") end
|
||||
bCursorBlink = blink
|
||||
if bVisible then
|
||||
@ -274,10 +325,10 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
return isColor()
|
||||
end
|
||||
|
||||
local function setTextColor( color )
|
||||
local function setTextColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
if tHex[color] == nil then
|
||||
error( "Invalid color (got " .. color .. ")" , 2 )
|
||||
error("Invalid color (got " .. color .. ")" , 2)
|
||||
end
|
||||
|
||||
nTextColor = color
|
||||
@ -289,50 +340,50 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
window.setTextColor = setTextColor
|
||||
window.setTextColour = setTextColor
|
||||
|
||||
function window.setPaletteColour( colour, r, g, b )
|
||||
function window.setPaletteColour(colour, r, g, b)
|
||||
if type(colour) ~= "number" then expect(1, colour, "number") end
|
||||
|
||||
if tHex[colour] == nil then
|
||||
error( "Invalid color (got " .. colour .. ")" , 2 )
|
||||
error("Invalid color (got " .. colour .. ")" , 2)
|
||||
end
|
||||
|
||||
local tCol
|
||||
if type(r) == "number" and g == nil and b == nil then
|
||||
tCol = { colours.unpackRGB( r ) }
|
||||
tPalette[ colour ] = tCol
|
||||
tCol = { colours.unpackRGB(r) }
|
||||
tPalette[colour] = tCol
|
||||
else
|
||||
if type(r) ~= "number" then expect(2, r, "number") end
|
||||
if type(g) ~= "number" then expect(3, g, "number") end
|
||||
if type(b) ~= "number" then expect(4, b, "number") end
|
||||
|
||||
tCol = tPalette[ colour ]
|
||||
tCol = tPalette[colour]
|
||||
tCol[1] = r
|
||||
tCol[2] = g
|
||||
tCol[3] = b
|
||||
end
|
||||
|
||||
if bVisible then
|
||||
return parent.setPaletteColour( colour, tCol[1], tCol[2], tCol[3] )
|
||||
return parent.setPaletteColour(colour, tCol[1], tCol[2], tCol[3])
|
||||
end
|
||||
end
|
||||
|
||||
window.setPaletteColor = window.setPaletteColour
|
||||
|
||||
function window.getPaletteColour( colour )
|
||||
function window.getPaletteColour(colour)
|
||||
if type(colour) ~= "number" then expect(1, colour, "number") end
|
||||
if tHex[colour] == nil then
|
||||
error( "Invalid color (got " .. colour .. ")" , 2 )
|
||||
error("Invalid color (got " .. colour .. ")" , 2)
|
||||
end
|
||||
local tCol = tPalette[ colour ]
|
||||
local tCol = tPalette[colour]
|
||||
return tCol[1], tCol[2], tCol[3]
|
||||
end
|
||||
|
||||
window.getPaletteColor = window.getPaletteColour
|
||||
|
||||
local function setBackgroundColor( color )
|
||||
local function setBackgroundColor(color)
|
||||
if type(color) ~= "number" then expect(1, color, "number") end
|
||||
if tHex[color] == nil then
|
||||
error( "Invalid color (got " .. color .. ")", 2 )
|
||||
error("Invalid color (got " .. color .. ")", 2)
|
||||
end
|
||||
nBackgroundColor = color
|
||||
end
|
||||
@ -344,13 +395,13 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
return nWidth, nHeight
|
||||
end
|
||||
|
||||
function window.scroll( n )
|
||||
function window.scroll(n)
|
||||
if type(n) ~= "number" then expect(1, n, "number") end
|
||||
if n ~= 0 then
|
||||
local tNewLines = {}
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[ nTextColor ]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[ nBackgroundColor ]
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for newY = 1, nHeight do
|
||||
local y = newY + n
|
||||
if y >= 1 and y <= nHeight then
|
||||
@ -388,6 +439,13 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
return nBackgroundColor
|
||||
end
|
||||
|
||||
--- Get the buffered contents of a line in this window.
|
||||
---
|
||||
-- @tparam number y The y position of the line to get.
|
||||
-- @treturn string The textual content of this line.
|
||||
-- @treturn string The text colours of this line, suitable for use with @{term.blit}.
|
||||
-- @treturn string The background colours of this line, suitable for use with @{term.blit}.
|
||||
-- @throws If `y` is not between 1 and this window's height.
|
||||
function window.getLine(y)
|
||||
if type(y) ~= "number" then expect(1, y, "number") end
|
||||
|
||||
@ -399,16 +457,26 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
end
|
||||
|
||||
-- Other functions
|
||||
function window.setVisible( bVis )
|
||||
if type(bVis) ~= "boolean" then expect(1, bVis, "boolean") end
|
||||
if bVisible ~= bVis then
|
||||
bVisible = bVis
|
||||
|
||||
--- Set whether this window is visible. Invisible windows will not be drawn
|
||||
-- to the screen until they are made visible again.
|
||||
--
|
||||
-- Making an invisible window visible will immediately draw it.
|
||||
--
|
||||
-- @tparam boolean visible Whether this window is visible.
|
||||
function window.setVisible(visible)
|
||||
if type(visible) ~= "boolean" then expect(1, visible, "boolean") end
|
||||
if bVisible ~= visible then
|
||||
bVisible = visible
|
||||
if bVisible then
|
||||
window.redraw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Draw this window. This does nothing if the window is not visible.
|
||||
--
|
||||
-- @see Window:setVisible
|
||||
function window.redraw()
|
||||
if bVisible then
|
||||
redraw()
|
||||
@ -419,6 +487,8 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the current terminal's cursor to where this window's cursor is. This
|
||||
-- does nothing if the window is not visible.
|
||||
function window.restoreCursor()
|
||||
if bVisible then
|
||||
updateCursorBlink()
|
||||
@ -427,31 +497,47 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the position of the top left corner of this window.
|
||||
--
|
||||
-- @treturn number The x position of this window.
|
||||
-- @treturn number The y position of this window.
|
||||
function window.getPosition()
|
||||
return nX, nY
|
||||
end
|
||||
|
||||
function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight, newParent )
|
||||
if type(nNewX) ~= "number" then expect(1, nNewX, "number") end
|
||||
if type(nNewY) ~= "number" then expect(2, nNewY, "number") end
|
||||
if nNewWidth ~= nil or nNewHeight ~= nil then
|
||||
expect(3, nNewWidth, "number")
|
||||
expect(4, nNewHeight, "number")
|
||||
--- Reposition or resize the given window.
|
||||
--
|
||||
-- This function also accepts arguments to change the size of this window.
|
||||
-- It is recommended that you fire a `term_resize` event after changing a
|
||||
-- window's, to allow programs to adjust their sizing.
|
||||
--
|
||||
-- @tparam number new_x The new x position of this window.
|
||||
-- @tparam number new_y The new y position of this window.
|
||||
-- @tparam[opt] number new_width The new width of this window.
|
||||
-- @tparam number new_height The new height of this window.
|
||||
-- @tparam[opt] term.Redirect new_parent The new redirect object this
|
||||
-- window should draw to.
|
||||
function window.reposition(new_x, new_y, new_width, new_height, new_parent)
|
||||
if type(new_x) ~= "number" then expect(1, new_x, "number") end
|
||||
if type(new_y) ~= "number" then expect(2, new_y, "number") end
|
||||
if new_width ~= nil or new_height ~= nil then
|
||||
expect(3, new_width, "number")
|
||||
expect(4, new_height, "number")
|
||||
end
|
||||
if newParent ~= nil and type(newParent) ~= "table" then expect(5, newParent, "table") end
|
||||
if new_parent ~= nil and type(new_parent) ~= "table" then expect(5, new_parent, "table") end
|
||||
|
||||
nX = nNewX
|
||||
nY = nNewY
|
||||
nX = new_x
|
||||
nY = new_y
|
||||
|
||||
if newParent then parent = newParent end
|
||||
if new_parent then parent = new_parent end
|
||||
|
||||
if nNewWidth and nNewHeight then
|
||||
if new_width and new_height then
|
||||
local tNewLines = {}
|
||||
createEmptyLines( nNewWidth )
|
||||
createEmptyLines(new_width)
|
||||
local sEmptyText = sEmptySpaceLine
|
||||
local sEmptyTextColor = tEmptyColorLines[ nTextColor ]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[ nBackgroundColor ]
|
||||
for y = 1, nNewHeight do
|
||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||
for y = 1, new_height do
|
||||
if y > nHeight then
|
||||
tNewLines[y] = {
|
||||
text = sEmptyText,
|
||||
@ -460,25 +546,25 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
}
|
||||
else
|
||||
local tOldLine = tLines[y]
|
||||
if nNewWidth == nWidth then
|
||||
if new_width == nWidth then
|
||||
tNewLines[y] = tOldLine
|
||||
elseif nNewWidth < nWidth then
|
||||
elseif new_width < nWidth then
|
||||
tNewLines[y] = {
|
||||
text = string_sub( tOldLine.text, 1, nNewWidth ),
|
||||
textColor = string_sub( tOldLine.textColor, 1, nNewWidth ),
|
||||
backgroundColor = string_sub( tOldLine.backgroundColor, 1, nNewWidth ),
|
||||
text = string_sub(tOldLine.text, 1, new_width),
|
||||
textColor = string_sub(tOldLine.textColor, 1, new_width),
|
||||
backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width),
|
||||
}
|
||||
else
|
||||
tNewLines[y] = {
|
||||
text = tOldLine.text .. string_sub( sEmptyText, nWidth + 1, nNewWidth ),
|
||||
textColor = tOldLine.textColor .. string_sub( sEmptyTextColor, nWidth + 1, nNewWidth ),
|
||||
backgroundColor = tOldLine.backgroundColor .. string_sub( sEmptyBackgroundColor, nWidth + 1, nNewWidth ),
|
||||
text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width),
|
||||
textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
|
||||
backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
nWidth = nNewWidth
|
||||
nHeight = nNewHeight
|
||||
nWidth = new_width
|
||||
nHeight = new_height
|
||||
tLines = tNewLines
|
||||
end
|
||||
if bVisible then
|
||||
|
@ -1 +1 @@
|
||||
adventure is a text adventure game for CraftOS. To navigate around the world of adventure, type simple instructions to the interpreter, for example: "go north", "punch tree", "craft planks", "mine coal with pickaxe", "hit creeper with sword"
|
||||
adventure is a text adventure game for CraftOS. To navigate around the world of adventure, type simple instructions to the interpreter, for example: "go north", "punch tree", "craft planks", "mine coal with pickaxe", "hit creeper with sword"
|
||||
|
@ -3,4 +3,4 @@ alias assigns shell commands to run other programs.
|
||||
ex:
|
||||
"alias dir ls" will make the "dir" command run the "ls" program
|
||||
"alias dir" will remove the alias set on "dir"
|
||||
"alias" will list all current aliases.
|
||||
"alias" will list all current aliases.
|
||||
|
@ -2,4 +2,4 @@ Surf the rednet superhighway with "chat", the networked chat program for CraftOS
|
||||
|
||||
ex:
|
||||
"chat host forgecraft" will create a chatroom with the name "forgecraft"
|
||||
"chat join forgecraft direwolf20" will connect to the chatroom with the name "forgecraft", using the nickname "direwolf20"
|
||||
"chat join forgecraft direwolf20" will connect to the chatroom with the name "forgecraft", using the nickname "direwolf20"
|
||||
|
@ -1 +1 @@
|
||||
clear clears the screen.
|
||||
clear clears the screen.
|
||||
|
@ -1,2 +1,2 @@
|
||||
coroutine is a standard Lua5.1 API.
|
||||
Refer to http://www.lua.org/manual/5.1/ for more information.
|
||||
Refer to http://www.lua.org/manual/5.1/ for more information.
|
||||
|
@ -1 +1 @@
|
||||
dance is a program for Turtles. Turtles love to get funky.
|
||||
dance is a program for Turtles. Turtles love to get funky.
|
||||
|
@ -14,4 +14,4 @@ disk.getID( drive )
|
||||
Events fired by the disk API:
|
||||
"disk" when a disk or other item is inserted into a disk drive. Argument is the name of the drive
|
||||
"disk_eject" when a disk is removed from a disk drive. Argument is the name of the drive
|
||||
Type "help events" to learn about the event system.
|
||||
Type "help events" to learn about the event system.
|
||||
|
@ -3,4 +3,4 @@ dj plays Music Discs from disk drives attached to the computer.
|
||||
ex:
|
||||
"dj" or "dj play" plays a random disc.
|
||||
"dj play left" plays the disc in the drive on the left of the computer.
|
||||
"dj stop" stops the current disc.
|
||||
"dj stop" stops the current disc.
|
||||
|
@ -2,4 +2,4 @@ drive tells you which disk drive the current or specified directory is located i
|
||||
|
||||
ex:
|
||||
"drive" tell you the disk drive of the current directory.
|
||||
"drive foo" tells you the disk drive of the subdirectory "foo"
|
||||
"drive foo" tells you the disk drive of the subdirectory "foo"
|
||||
|
@ -16,4 +16,4 @@ getDiskID()
|
||||
Events fired by the Disk Drive:
|
||||
"disk" when a disk or other item is inserted into the drive. Argument is the name of the drive.
|
||||
"disk_eject" when a disk is removed from a drive. Argument is the name of the drive.
|
||||
Type "help events" to learn about the event system.
|
||||
Type "help events" to learn about the event system.
|
||||
|
@ -1 +1 @@
|
||||
Mostly harmless.
|
||||
Mostly harmless.
|
||||
|
@ -1,4 +1,4 @@
|
||||
edit is a text editor for creating or modifying programs or text files. After creating a program with edit, type its filename in the shell to run it. You can open any of the builtin programs with edit to learn how to program.
|
||||
|
||||
ex:
|
||||
"edit hello" opens a file called "hello" for editing.
|
||||
"edit hello" opens a file called "hello" for editing.
|
||||
|
@ -1 +1 @@
|
||||
exit will exit the current shell.
|
||||
exit will exit the current shell.
|
||||
|
@ -4,7 +4,7 @@ Type "help gpsapi" for help using GPS functions in lua programs.
|
||||
ex:
|
||||
"gps locate" will connect to nearby GPS servers, and try to determine the position of the computer or turtle.
|
||||
"gps host" will try to determine the position, and host a GPS server if successful.
|
||||
"gps host 10 20 30" will host a GPS server, using the manually entered position 10,20,30.
|
||||
"gps host 10 20 30" will host a GPS server, using the manually entered position 10,20,30.
|
||||
|
||||
Take care when manually entering host positions. If the positions entered into multiple GPS hosts
|
||||
are not consistent, the results of locate calls will be incorrect.
|
||||
are not consistent, the results of locate calls will be incorrect.
|
||||
|
@ -1,4 +1,4 @@
|
||||
Functions in the GPS API:
|
||||
gps.locate( timeout )
|
||||
|
||||
The locate function will send a signal to nearby gps servers, and wait for responses before the timeout. If it receives enough responses to determine this computers position then x, y and z co-ordinates will be returned, otherwise it will return nil. If GPS hosts do not have their positions configured correctly, results will be inaccurate.
|
||||
The locate function will send a signal to nearby gps servers, and wait for responses before the timeout. If it receives enough responses to determine this computers position then x, y and z co-ordinates will be returned, otherwise it will return nil. If GPS hosts do not have their positions configured correctly, results will be inaccurate.
|
||||
|
@ -1 +1 @@
|
||||
hello prints the text "Hello World!" to the screen.
|
||||
hello prints the text "Hello World!" to the screen.
|
||||
|
@ -1,4 +1,4 @@
|
||||
help is the help tool you're currently using.
|
||||
Type "help index" to see all help topics.
|
||||
Type "help" to see the help intro.
|
||||
Type "help helpapi" for information on the help Lua API.
|
||||
Type "help helpapi" for information on the help Lua API.
|
||||
|
@ -2,4 +2,4 @@ id prints the unique identifier of this computer, or a Disk in an attached Disk
|
||||
|
||||
ex:
|
||||
"id" will print this Computers ID and label
|
||||
"id left" will print the ID and label of the disk in the Disk Drive on the left
|
||||
"id left" will print the ID and label of the disk in the Disk Drive on the left
|
||||
|
@ -1,2 +1,2 @@
|
||||
io is a standard Lua5.1 API, reimplemented for CraftOS. Not all the features are availiable.
|
||||
Refer to http://www.lua.org/manual/5.1/ for more information.
|
||||
Refer to http://www.lua.org/manual/5.1/ for more information.
|
||||
|
@ -3,7 +3,7 @@ The keys API contains constants for all the key codes that can be returned by th
|
||||
Example usage:
|
||||
local sEvent, nKey = os.pullEvent()
|
||||
if sEvent == "key" and nKey == keys.enter then
|
||||
-- Do something
|
||||
-- Do something
|
||||
end
|
||||
|
||||
See http://www.minecraftwiki.net/wiki/Key_codes, or the source code, for a complete reference.
|
||||
See http://www.minecraftwiki.net/wiki/Key_codes, or the source code, for a complete reference.
|
||||
|
@ -1 +1 @@
|
||||
ls will list all the directories and files in the current location. Use "type" to find out if an item is a file or a directory.
|
||||
ls will list all the directories and files in the current location. Use "type" to find out if an item is a file or a directory.
|
||||
|
@ -1 +1 @@
|
||||
lua is an interactive prompt for the lua programming language. It's a useful tool for learning the language.
|
||||
lua is an interactive prompt for the lua programming language. It's a useful tool for learning the language.
|
||||
|
@ -1,2 +1,2 @@
|
||||
math is a standard Lua5.1 API.
|
||||
Refer to http://www.lua.org/manual/5.1/ for more information.
|
||||
Refer to http://www.lua.org/manual/5.1/ for more information.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user