1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-21 22:46:57 +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:
SquidDev 2020-04-22 09:45:23 +01:00
commit d847a4d9e0
537 changed files with 8869 additions and 4413 deletions

View File

@ -14,5 +14,9 @@ trim_trailing_whitespace = false
[*.sexp]
indent_size = 2
[*.yml]
indent_size = 2
[*.properties]
insert_final_newline = false

View File

@ -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
View 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
View 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
View File

@ -3,6 +3,8 @@
/logs
/build
/out
/doc/**/*.html
/doc/index.json
# Runtime directories
/run

36
CONTRIBUTING.md Normal file
View 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

View File

@ -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!

View File

@ -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
View 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"

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

6
doc/stub/commands.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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; }

View File

@ -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))

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 ) );
}
}

View File

@ -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();
}
}

View File

@ -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 );
}
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();
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
private boolean isGreyScale( int colour )
{
return colour == 0 || colour == 15 || colour == 7 || colour == 8;
drawQuad( buffer, x, y, width, height, r, g, b );
}
public void drawStringBackgroundPart( int x, int y, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p )
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
)
{
// Draw the quads
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
renderer.begin( GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION_COLOR );
if( leftMarginSize > 0.0 )
if( leftMarginSize > 0 )
{
int colour1 = "0123456789abcdef".indexOf( backgroundColour.charAt( 0 ) );
if( colour1 < 0 || (greyScale && !isGreyScale( colour1 )) )
{
colour1 = 15;
drawQuad( renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
}
drawQuad( renderer, x - leftMarginSize, y, colour1, leftMarginSize, p, greyScale );
}
if( rightMarginSize > 0.0 )
if( rightMarginSize > 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 + i * FONT_WIDTH, y, colour, FONT_WIDTH, p, greyScale );
}
GlStateManager.disableTexture();
tessellator.draw();
GlStateManager.enableTexture();
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
}
public void drawStringTextPart( int x, int y, TextBuffer s, TextBuffer textColour, boolean greyScale, Palette p )
blockColour = colourIndex;
blockStart = i;
}
if( blockColour != '\0' )
{
// 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++ )
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
}
}
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
)
{
// Switch colour
int colour = "0123456789abcdef".indexOf( textColour.charAt( i ) );
if( colour < 0 || (greyScale && !isGreyScale( colour )) )
if( backgroundColour != null )
{
colour = 0;
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 )
{
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 )
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
)
{
index = '?';
}
drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale );
}
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 )
{
// Bind the background texture
m_textureManager.bindTexture( BACKGROUND );
Palette palette = terminal.getPalette();
int height = terminal.getHeight();
// Draw the quads
drawStringBackgroundPart( x, y, backgroundColour, leftMarginSize, rightMarginSize, greyScale, p );
// 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++ )
{
drawString(
buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ),
palette, greyscale, leftMarginSize, rightMarginSize
);
}
}
// Draw text
if( s != null && textColour != null )
public static void drawCursor(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale
)
{
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() )
{
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 );
}
}
public static void drawTerminal(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
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
)
{
// Bind the font texture
bindFont();
// Draw the quads
drawStringTextPart( x, y, s, textColour, greyScale, p );
}
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawTerminal( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
tessellator.draw();
}
public int getStringWidth( String s )
public static void drawEmptyTerminal( float x, float y, float width, float height )
{
if( s == null )
{
return 0;
}
return s.length() * FONT_WIDTH;
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 void bindFont()
public static void drawBlocker( @Nonnull BufferBuilder buffer, float x, float y, float width, float height )
{
m_textureManager.bindTexture( FONT );
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 );
}
}

View File

@ -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;
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 );
}
}
}

View File

@ -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 );

View File

@ -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
);
}
}

View File

@ -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 + TileMonitor.RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN) + 0,
-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 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
double ySize = origin.getHeight() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
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();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer();
// Get terminal
boolean redraw = originTerminal.pollTerminalChanged();
// Draw the contents
// 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();
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();
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
GlStateManager.pushMatrix();
try
{
double xScale = xSize / (width * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (height * FixedWidthFontRenderer.FONT_HEIGHT);
GlStateManager.scaled( xScale, -yScale, 1.0 );
GlStateManager.scaled( (float) xScale, (float) -yScale, 1.0f );
// 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;
renderTerminal( originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
// 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();
FixedWidthFontRenderer.drawEmptyTerminal(
-MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
}
}
finally
{
// Tear down render state for monitors.
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
{
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 );
}
}
finally
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
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 );
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 )

View File

@ -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,27 +82,8 @@ 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();
}
}
}
// 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 };
}
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:

View File

@ -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 )
{
p = m_peripherals[side.ordinal()];
}
if( p != null )
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;
}
}
}

View File

@ -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";

View File

@ -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,8 +79,16 @@ 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

View File

@ -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 );

View File

@ -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 )

View File

@ -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;
}
}
}

View File

@ -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" );
}
}

View 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 )
Visitor visitor = new Visitor();
Files.walkFileTree( file.toPath(), visitor );
return visitor.size;
}
catch( IOException e )
{
size += measureUsedSpace( new File( file, content ) );
}
return size;
}
else
{
return Math.max( file.length(), MINIMUM_FILE_SIZE );
ComputerCraft.log.error( "Error computing file size for {}", file, e );
return 0;
}
}
}

View File

@ -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( ".." ) )
{

View File

@ -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;
}
}
}

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
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();
}

View File

@ -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;
}
}

View File

@ -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 );
}

View 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)

View 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=터틀 액션 미사용

View File

@ -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" }

View File

@ -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" }
}
}
]

View File

@ -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" }
}
}
]

View File

@ -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" }

View File

@ -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

View File

@ -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
@ -70,8 +70,6 @@ if _VERSION == "Lua 5.1" then
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.
@ -98,70 +96,6 @@ 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"
@ -947,18 +881,66 @@ if bAPIError then
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
@ -991,8 +973,7 @@ if fs.exists( ".settings" ) then
end
-- Run the shell
local ok, err = pcall( function()
parallel.waitForAny(
local ok, err = pcall(parallel.waitForAny,
function()
local sShell
if term.isColour() and settings.get("bios.use_multishell") then
@ -1003,10 +984,8 @@ local ok, err = pcall( function()
os.run({}, sShell)
os.run({}, "rom/programs/shutdown.lua")
end,
function()
rednet.run()
end )
end )
rednet.run
)
-- If the shell errored, let the user read it.
term.redirect(term.native())

View File

@ -1,23 +1,91 @@
--- 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
--- 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
@ -28,6 +96,20 @@ function combine( ... )
return r
end
--- 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
@ -39,12 +121,33 @@ function subtract( colors, ... )
return r
end
--- 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
--- 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")
@ -55,6 +158,18 @@ function packRGB( r, g, b )
bit32.band(b * 255, 0xFF)
end
--- 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
@ -63,6 +178,30 @@ function unpackRGB( rgb )
bit32.band(rgb, 0xFF) / 255
end
--- 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)

View File

@ -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

View File

@ -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, ... )

View File

@ -1,3 +1,15 @@
--- 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
@ -6,6 +18,11 @@ local function isDrive( name )
return peripheral.getType(name) == "drive"
end
--- 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")
@ -13,6 +30,16 @@ function isPresent( name )
return false
end
--- 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")
@ -20,12 +47,23 @@ function getLabel( name )
return nil
end
--- 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
--- 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")
@ -33,6 +71,13 @@ function hasData( name )
return false
end
--- 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")
@ -40,6 +85,15 @@ function getMountPath( name )
return nil
end
--- 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")
@ -47,6 +101,13 @@ function hasAudio( name )
return false
end
--- 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")
@ -54,12 +115,25 @@ function getAudioTitle( name )
return nil
end
--- 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
--- 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
@ -72,16 +146,26 @@ function stopAudio( name )
end
end
--- 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
--- 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

View File

@ -1,5 +1,30 @@
--- 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)
@ -56,6 +81,15 @@ local function narrow( p1, p2, fix )
end
end
--- 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")

View File

@ -1,16 +1,38 @@
--- 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
--- 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
--- 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
@ -27,6 +49,9 @@ 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 = {
@ -59,6 +84,11 @@ function topics()
return tItemList
end
--- 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()

View File

@ -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

View File

@ -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 ]

View File

@ -1,3 +1,8 @@
--- 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)
@ -18,51 +23,88 @@ local function parseLine( tImageArg, sLine )
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
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)
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)
@ -110,6 +152,18 @@ function drawLine( startX, startY, endX, endY, nColour )
end
end
--- 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")
@ -154,7 +208,18 @@ function drawBox( startX, startY, endX, endY, nColour )
end
end
end
--- 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")
@ -194,12 +259,17 @@ function drawFilledBox( startX, startY, endX, endY, nColour )
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])

View File

@ -1,3 +1,18 @@
--- 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 tFns = table.pack(...)
@ -55,12 +70,22 @@ local function runUntilLimit( _routines, _limit )
end
end
--- 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
--- 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(...)
runUntilLimit( routines, 0 )
return runUntilLimit(routines, 0)
end

View File

@ -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
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
end
return false
end
function getType( _sSide )
expect(1, _sSide, "string")
if native.isPresent( _sSide ) then
return native.getType( _sSide )
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 )
--- 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 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 )
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 )
--- 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 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, ... )
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, ... )
--- 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 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
end
--- 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
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 )
local result = {}
for _, method in ipairs(methods) do
result[method] = function(...)
return peripheral.call(name, method, ...)
end
end
return result
end
--- 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

View File

@ -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,6 +94,23 @@ function isOpen( sModem )
return false
end
--- 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")
@ -91,11 +149,34 @@ function send( nRecipient, message, sProtocol )
return sent
end
--- 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)
end
--- 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
@ -132,6 +213,24 @@ function receive( sProtocolFilter, nTimeout )
end
end
--- 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")
@ -146,11 +245,29 @@ function host( sProtocol, sHostname )
end
end
--- 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
end
--- 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")
@ -216,6 +333,8 @@ function lookup( sProtocol, sHostname )
end
local bRunning = false
--- @local
function run()
if bRunning then
error("rednet is already running", 2)

View File

@ -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
--- 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")
local file = fs.open( sPath, "r" )
expect(1, sPath, "string", "nil")
local file = fs.open(sPath or ".settings", "r")
if not file then
return false
end
@ -70,23 +205,35 @@ function load( sPath )
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
--- 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")
local file = fs.open( sPath, "w" )
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

View File

@ -1,3 +1,8 @@
--- 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
@ -9,8 +14,26 @@ local function wrap( _sFunction )
end
end
local term = {}
local term = _ENV
--- 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
@ -30,14 +53,24 @@ 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
@ -49,12 +82,7 @@ for _, method in ipairs { "nativePaletteColor", "nativePaletteColour"} do
end
for k, v in pairs(native) do
if type( k ) == "string" and type( v ) == "function" and term[k] == nil then
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

View File

@ -1,5 +1,21 @@
local expect = dofile("rom/modules/main/cc/expect.lua").expect
--- The `textutils` API provides helpful utilities for formatting and
-- manipulating strings.
--
-- @module textutils
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
@ -21,11 +37,28 @@ function slowWrite( sText, nRate )
end
end
--- 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
--- 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")
@ -71,6 +104,23 @@ local function makePagedScroll( _term, _nFreeLines )
end
end
--- 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
@ -159,10 +209,30 @@ local function tabulateCommon( bPaged, ... )
end
end
--- 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
--- 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
@ -238,16 +308,37 @@ local function serializeImpl( t, tTracking, sIndent )
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
--- 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
@ -310,11 +401,231 @@ local function serializeJSONImpl( t, tTracking, bNBTStyle )
end
end
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
--- 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", {})
@ -327,6 +638,26 @@ function unserialize( s )
return nil
end
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")
@ -334,6 +665,16 @@ function serializeJSON( t, bNBTStyle )
return serializeJSONImpl(t, tTracking, bNBTStyle or false)
end
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
@ -356,6 +697,23 @@ function urlEncode( str )
end
local tEmpty = {}
--- 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")
@ -431,8 +789,3 @@ function complete( sSearchText, tSearchTable )
table.sort(tResults)
return tResults
end
-- GB versions
serialise = serialize
unserialise = unserialize
serialiseJSON = serializeJSON

View File

@ -1,8 +1,11 @@
--- The turtle API allows you to control your turtle.
--
-- @module turtle
if not turtle then
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

View File

@ -1,5 +1,24 @@
--- 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 = {
--- 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,
@ -7,6 +26,14 @@ local vector = {
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,
@ -14,6 +41,14 @@ local vector = {
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,
@ -21,6 +56,14 @@ local vector = {
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,
@ -28,6 +71,12 @@ local vector = {
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,
@ -35,9 +84,23 @@ local vector = {
-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,
@ -45,20 +108,46 @@ local vector = {
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,
round = function( self, nTolerance )
nTolerance = nTolerance or 1.0
--- 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 + nTolerance * 0.5) / nTolerance ) * nTolerance,
math.floor( (self.y + nTolerance * 0.5) / nTolerance ) * nTolerance,
math.floor( (self.z + nTolerance * 0.5) / nTolerance ) * nTolerance
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,
@ -74,12 +163,16 @@ local vmetatable = {
__tostring = vector.tostring,
}
--- 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)
local v = {
return setmetatable({
x = tonumber(x) or 0,
y = tonumber(y) or 0,
z = tonumber(z) or 0,
}
setmetatable( v, vmetatable )
return v
}, vmetatable)
end

View File

@ -1,3 +1,32 @@
--- 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 = {
@ -23,6 +52,24 @@ local type = type
local string_rep = string.rep
local string_sub = string.sub
--- 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")
@ -182,7 +229,11 @@ 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)
@ -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
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

View File

@ -5,28 +5,29 @@
local native_select, native_type = select, type
--- Expect an argument to have a specific type.
--
-- @tparam int index The 1-based argument index.
-- @param value The argument's value.
-- @tparam string ... The allowed types of the argument.
-- @throws If the value is not one of the allowed types.
local function expect(index, value, ...)
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return true end
end
local function get_type_names(...)
local types = table.pack(...)
for i = types.n, 1, -1 do
if types[i] == "nil" then table.remove(types, i) end
end
local type_names
if #types <= 1 then
type_names = tostring(...)
return tostring(...)
else
type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
end
end
--- Expect an argument to have a specific type.
--
-- @tparam number index The 1-based argument index.
-- @param value The argument's value.
-- @tparam string ... The allowed types of the argument.
-- @return The given `value`.
-- @throws If the value is not one of the allowed types.
local function expect(index, value, ...)
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
-- If we can determine the function name with a high level of confidence, try to include it.
@ -36,6 +37,7 @@ local function expect(index, value, ...)
if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
end
local type_names = get_type_names(...)
if name then
error(("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3)
else
@ -43,4 +45,31 @@ local function expect(index, value, ...)
end
end
return { expect = expect }
--- Expect an field to have a specific type.
--
-- @tparam table tbl The table to index.
-- @tparam string index The field name to check.
-- @tparam string ... The allowed types of the argument.
-- @return The contents of the given field.
-- @throws If the field is not one of the allowed types.
local function field(tbl, index, ...)
expect(1, tbl, "table")
expect(2, index, "string")
local value = tbl[index]
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
if value == nil then
error(("field '%s' missing from table"):format(index), 3)
else
error(("bad field '%s' (expected %s, got %s)"):format(index, get_type_names(...), t), 3)
end
end
return {
expect = expect,
field = field,
}

View File

@ -0,0 +1,419 @@
--- Provides a "pretty printer", for rendering data structures in an
-- aesthetically pleasing manner.
--
-- In order to display something using @{cc.pretty}, you build up a series of
-- @{Doc|documents}. These behave a little bit like strings; you can concatenate
-- them together and then print them to the screen.
--
-- However, documents also allow you to control how they should be printed. There
-- are several functions (such as @{nest} and @{group}) which allow you to control
-- the "layout" of the document. When you come to display the document, the 'best'
-- (most compact) layout is used.
--
-- @module cc.pretty
-- @usage Print a table to the terminal
-- local pretty = require "cc.pretty"
-- pretty.write(pretty.dump({ 1, 2, 3 }))
--
-- @usage Build a custom document and display it
-- local pretty = require "cc.pretty"
-- pretty.write(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
local expect = require "cc.expect".expect
local type, getmetatable, setmetatable, colours, str_write = type, getmetatable, setmetatable, colours, write
--- @{table.insert} alternative, but with the length stored inline.
local function append(out, value)
local n = out.n + 1
out[n], out.n = value, n
end
--- A document containing formatted text, with multiple possible layouts.
--
-- Documents effectively represent a sequence of strings in alternative layouts,
-- which we will try to print in the most compact form necessary.
--
-- @type Doc
local Doc = { }
--- An empty document.
local empty = setmetatable({ tag = "nil" }, Doc)
--- A document with a single space in it.
local space = setmetatable({ tag = "text", text = " " }, Doc)
--- A line break. When collapsed with @{group}, this will be replaced with @{empty}.
local line = setmetatable({ tag = "line", flat = empty }, Doc)
--- A line break. When collapsed with @{group}, this will be replaced with @{space}.
local space_line = setmetatable({ tag = "line", flat = space }, Doc)
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
local function mk_text(text, colour)
return text_cache[text] or setmetatable({ tag = "text", text = text, colour = colour }, Doc)
end
--- Create a new document from a string.
--
-- If your string contains multiple lines, @{group} will flatten the string
-- into a single line, with spaces between each line.
--
-- @tparam string text The string to construct a new document with.
-- @tparam[opt] number colour The colour this text should be printed with. If not given, we default to the current
-- colour.
-- @treturn Doc The document with the provided text.
local function text(text, colour)
expect(1, text, "string")
expect(2, colour, "number", "nil")
local cached = text_cache[text]
if cached then return cached end
local new_line = text:find("\n", 1)
if not new_line then return mk_text(text, colour) end
-- Split the string by "\n". With a micro-optimisation to skip empty strings.
local doc = setmetatable({ tag = "concat", n = 0 }, Doc)
if new_line ~= 1 then append(doc, mk_text(text:sub(1, new_line - 1), colour)) end
new_line = new_line + 1
while true do
local next_line = text:find("\n", new_line)
append(doc, space_line)
if not next_line then
if new_line <= #text then append(doc, mk_text(text:sub(new_line), colour)) end
return doc
else
if new_line <= next_line - 1 then
append(doc, mk_text(text:sub(new_line, next_line - 1), colour))
end
new_line = next_line + 1
end
end
end
--- Concatenate several documents together. This behaves very similar to string concatenation.
--
-- @tparam Doc|string ... The documents to concatenate.
-- @treturn Doc The concatenated documents.
-- @usage pretty.concat(doc1, " - ", doc2)
-- @usage doc1 .. " - " .. doc2
local function concat(...)
local args = table.pack(...)
for i = 1, args.n do
if type(args[i]) == "string" then args[i] = text(args[i]) end
if getmetatable(args[i]) ~= Doc then expect(i, args[i], "document") end
end
if args.n == 0 then return empty end
if args.n == 1 then return args[1] end
args.tag = "concat"
return setmetatable(args, Doc)
end
Doc.__concat = concat --- @local
--- Indent later lines of the given document with the given number of spaces.
--
-- For instance, nesting the document
-- ```txt
-- foo
-- bar
-- ```
-- by two spaces will produce
-- ```txt
-- foo
-- bar
-- ```
--
-- @tparam number depth The number of spaces with which the document should be indented.
-- @tparam Doc doc The document to indent.
-- @treturn Doc The nested document.
-- @usage pretty.nest(2, pretty.text("foo\nbar"))
local function nest(depth, doc)
expect(1, depth, "number")
if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
if depth <= 0 then error("depth must be a positive number", 2) end
return setmetatable({ tag = "nest", depth = depth, doc }, Doc)
end
local function flatten(doc)
if doc.flat then return doc.flat end
local kind = doc.tag
if kind == "nil" or kind == "text" then
return doc
elseif kind == "concat" then
local out = setmetatable({ tag = "concat", n = doc.n }, Doc)
for i = 1, doc.n do out[i] = flatten(doc[i]) end
doc.flat, out.flat = out, out -- cache the flattened node
return out
elseif kind == "nest" then
return flatten(doc[1])
elseif kind == "group" then
return doc[1]
else
error("Unknown doc " .. kind)
end
end
--- Builds a document which is displayed on a single line if there is enough
-- room, or as normal if not.
--
-- @tparam Doc doc The document to group.
-- @treturn Doc The grouped document.
local function group(doc)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
if doc.tag == "group" then return doc end -- Skip if already grouped.
local flattened = flatten(doc)
if flattened == doc then return doc end -- Also skip if flattening does nothing.
return setmetatable({ tag = "group", flattened, doc }, Doc)
end
local function get_remaining(doc, width)
local kind = doc.tag
if kind == "nil" or kind == "line" then
return width
elseif kind == "text" then
return width - #doc.text
elseif kind == "concat" then
for i = 1, doc.n do
width = get_remaining(doc[i], width)
if width < 0 then break end
end
return width
elseif kind == "group" or kind == "nest" then
return get_remaining(kind[1])
else
error("Unknown doc " .. kind)
end
end
--- Display a document on the terminal.
--
-- @tparam Doc doc The document to render
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
local function write(doc, ribbon_frac)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
expect(2, ribbon_frac, "number", "nil")
local term = term
local width, height = term.getSize()
local ribbon_width = (ribbon_frac or 0.6) * width
if ribbon_width < 0 then ribbon_width = 0 end
if ribbon_width > width then ribbon_width = width end
local def_colour = term.getTextColour()
local current_colour = def_colour
local function go(doc, indent, col)
local kind = doc.tag
if kind == "nil" then
return col
elseif kind == "text" then
local doc_colour = doc.colour or def_colour
if doc_colour ~= current_colour then
term.setTextColour(doc_colour)
current_colour = doc_colour
end
str_write(doc.text)
return col + #doc.text
elseif kind == "line" then
local _, y = term.getCursorPos()
if y < height then
term.setCursorPos(indent + 1, y + 1)
else
term.scroll(1)
term.setCursorPos(indent + 1, height)
end
return indent
elseif kind == "concat" then
for i = 1, doc.n do col = go(doc[i], indent, col) end
return col
elseif kind == "nest" then
return go(doc[1], indent + doc.depth, col)
elseif kind == "group" then
if get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
return go(doc[1], indent, col)
else
return go(doc[2], indent, col)
end
else
error("Unknown doc " .. kind)
end
end
local col = math.max(term.getCursorPos() - 1, 0)
go(doc, 0, col)
if current_colour ~= def_colour then term.setTextColour(def_colour) end
end
--- Display a document on the terminal with a trailing new line.
--
-- @tparam Doc doc The document to render.
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
local function print(doc, ribbon_frac)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
expect(2, ribbon_frac, "number", "nil")
write(doc, ribbon_frac)
str_write("\n")
end
--- Render a document, converting it into a string.
--
-- @tparam Doc doc The document to render.
-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to
-- fit this width - it is only used for finding the best layout.
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
-- @treturn string The rendered document as a string.
local function render(doc, width, ribbon_frac)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
expect(2, width, "number", "nil")
expect(3, ribbon_frac, "number", "nil")
local ribbon_width
if width then
ribbon_width = (ribbon_frac or 0.6) * width
if ribbon_width < 0 then ribbon_width = 0 end
if ribbon_width > width then ribbon_width = width end
end
local out = { n = 0 }
local function go(doc, indent, col)
local kind = doc.tag
if kind == "nil" then
return col
elseif kind == "text" then
append(out, doc.text)
return col + #doc.text
elseif kind == "line" then
append(out, "\n" .. (" "):rep(indent))
return indent
elseif kind == "concat" then
for i = 1, doc.n do col = go(doc[i], indent, col) end
return col
elseif kind == "nest" then
return go(doc[1], indent + doc.depth, col)
elseif kind == "group" then
if not width or get_remaining(doc[1], math.min(width, ribbon_width + indent) - col) >= 0 then
return go(doc[1], indent, col)
else
return go(doc[2], indent, col)
end
else
error("Unknown doc " .. kind)
end
end
go(doc, 0, 0)
return table.concat(out, "", 1, out.n)
end
Doc.__tostring = render --- @local
local keywords = {
["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 comma = text(",")
local braces = text("{}")
local obrace, cbrace = text("{"), text("}")
local obracket, cbracket = text("["), text("] = ")
local function key_compare(a, b)
local ta, tb = type(a), type(b)
if ta == "string" then return tb ~= "string" or a < b
elseif tb == "string" then return false
end
if ta == "number" then return tb ~= "number" or a < b end
return false
end
local function pretty_impl(obj, tracking)
local obj_type = type(obj)
if obj_type == "string" then
local formatted = ("%q"):format(obj):gsub("\\\n", "\\n")
return text(formatted, colours.red)
elseif obj_type == "number" then
return text(tostring(obj), colours.magenta)
elseif obj_type ~= "table" or tracking[obj] then
return text(tostring(obj), colours.lightGrey)
elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
return text(tostring(obj))
elseif next(obj) == nil then
return braces
else
tracking[obj] = true
local doc = setmetatable({ tag = "concat", n = 1, space_line }, Doc)
local length, keys, keysn = #obj, {}, 1
for k in pairs(obj) do keys[keysn], keysn = k, keysn + 1 end
table.sort(keys, key_compare)
for i = 1, keysn - 1 do
if i > 1 then append(doc, comma) append(doc, space_line) end
local k = keys[i]
local v = obj[k]
local ty = type(k)
if ty == "number" and k % 1 == 0 and k >= 1 and k <= length then
append(doc, pretty_impl(v, tracking))
elseif ty == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
append(doc, text(k .. " = "))
append(doc, pretty_impl(v, tracking))
else
append(doc, obracket)
append(doc, pretty_impl(k, tracking))
append(doc, cbracket)
append(doc, pretty_impl(v, tracking))
end
end
tracking[obj] = nil
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, n))), space_line, cbrace))
end
end
--- Pretty-print an arbitrary object, converting it into a document.
--
-- This can then be rendered with @{write} or @{print}.
--
-- @param obj The object to pretty-print.
-- @treturn Doc The object formatted as a document.
-- @usage Display a table on the screen
-- local pretty = require "cc.pretty"
-- pretty.print(pretty.pretty({ 1, 2, 3 }))
local function pretty(obj)
return pretty_impl(obj, {})
end
return {
empty = empty,
space = space,
line = line,
space_line = space_line,
text = text,
concat = concat,
nest = nest,
group = group,
write = write,
print = print,
render = render,
pretty = pretty,
}

View File

@ -97,7 +97,7 @@ end
-- complete.build(
-- { complete.choice, { "get", "put" } },
-- complete.dir,
-- } complete.file, many = true }
-- { complete.file, many = true }
-- )
local function build(...)
local arguments = table.pack(...)

View File

@ -390,7 +390,7 @@ local function getRoom( x, y, z, dontCreate )
["west"] = true,
}
if math.random(1, 8) == 1 then
room.exits["down"] = true
room.exits.down = true
room.items["a cave entrance"] = items["a cave entrance"]
end
@ -412,8 +412,8 @@ local function getRoom( x, y, z, dontCreate )
if y == -1 then
local above = getRoom(x, y + 1, z)
if above.exits["down"] then
room.exits["up"] = true
if above.exits.down then
room.exits.up = true
room.items["an exit to the surface"] = items["an exit to the surface"]
end
else
@ -608,7 +608,7 @@ local tMatches = {
local commands = {}
local function doCommand(text)
if text == "" then
commands[ "noinput" ]()
commands.noinput()
return
end
@ -626,7 +626,7 @@ local function doCommand( text )
end
end
end
commands[ "badinput" ]()
commands.badinput()
end
function commands.wait()
@ -761,24 +761,24 @@ function commands.dig( _sDir, _sTool )
end
if _sDir == "north" then
room.exits["north"] = true
room.exits.north = true
z = z + 1
getRoom( x, y, z ).exits["south"] = true
getRoom(x, y, z).exits.south = true
elseif _sDir == "south" then
room.exits["south"] = true
room.exits.south = true
z = z - 1
getRoom( x, y, z ).exits["north"] = true
getRoom(x, y, z).exits.north = true
elseif _sDir == "east" then
room.exits["east"] = true
room.exits.east = true
x = x - 1
getRoom( x, y, z ).exits["west"] = true
getRoom(x, y, z).exits.west = true
elseif _sDir == "west" then
room.exits["west"] = true
room.exits.west = true
x = x + 1
getRoom( x, y, z ).exits["east"] = true
getRoom(x, y, z).exits.east = true
elseif _sDir == "up" then
if y == 0 then
@ -786,14 +786,14 @@ function commands.dig( _sDir, _sTool )
return
end
room.exits["up"] = true
room.exits.up = true
if y == -1 then
room.items["an exit to the surface"] = items["an exit to the surface"]
end
y = y + 1
room = getRoom(x, y, z)
room.exits["down"] = true
room.exits.down = true
if y == 0 then
room.items["a cave entrance"] = items["a cave entrance"]
end
@ -804,14 +804,14 @@ function commands.dig( _sDir, _sTool )
return
end
room.exits["down"] = true
room.exits.down = true
if y == 0 then
room.items["a cave entrance"] = items["a cave entrance"]
end
y = y - 1
room = getRoom(x, y, z)
room.exits["up"] = true
room.exits.up = true
if y == -1 then
room.items["an exit to the surface"] = items["an exit to the surface"]
end

View File

@ -13,8 +13,8 @@ if #tArgs < 2 then
end
if not http then
printError( "Pastebin requires http API" )
printError( "Set http_enable to true in ComputerCraft.cfg" )
printError("Pastebin requires the http API")
printError("Set http.enabled to true in CC: Tweaked's config")
return
end

View File

@ -21,8 +21,8 @@ end
local url = table.remove(tArgs, 1)
if not http then
printError( "wget requires http API" )
printError( "Set http_enable to true in ComputerCraft.cfg" )
printError("wget requires the http API")
printError("Set http.enabled to true in CC: Tweaked's config")
return
end

View File

@ -6,11 +6,13 @@ if #tArgs > 0 then
return
end
local pretty = require "cc.pretty"
local bRunning = true
local tCommandHistory = {}
local tEnv = {
["exit"] = setmetatable({}, {
__tostring = function() return "Call exit() to exit" end,
__tostring = function() return "Call exit() to exit." end,
__call = function() bRunning = false end,
}),
["_echo"] = function(...)
@ -87,18 +89,9 @@ while bRunning do
local n = 1
while n < tResults.n or n <= nForcePrint do
local value = tResults[n + 1]
if type( value ) == "table" then
local metatable = getmetatable( value )
if type(metatable) == "table" and type(metatable.__tostring) == "function" then
print( tostring( value ) )
else
local ok, serialised = pcall( textutils.serialise, value )
local ok, serialised = pcall(pretty.pretty, value)
if ok then
print( serialised )
else
print( tostring( value ) )
end
end
pretty.print(serialised)
else
print(tostring(value))
end

View File

@ -39,6 +39,8 @@ local function resume( ... )
return param
end
local timers = {}
local ok, param = pcall(function()
local sFilter = resume()
while coroutine.status(co) ~= "dead" do
@ -48,6 +50,7 @@ local ok, param = pcall( function()
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_click") then
if tEvent[1] == "monitor_touch" and tEvent[2] == sName then
timers[os.startTimer(0.1)] = { tEvent[3], tEvent[4] }
sFilter = resume("mouse_click", 1, table.unpack(tEvent, 3, tEvent.n))
end
end
@ -56,6 +59,12 @@ local ok, param = pcall( function()
sFilter = resume("term_resize")
end
end
if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_up") then
if tEvent[1] == "timer" and timers[tEvent[2]] then
sFilter = resume("mouse_up", 1, table.unpack(timers[tEvent[2]], 1, 2))
timers[tEvent[2]] = nil
end
end
end
end)

View File

@ -105,8 +105,8 @@ if sCommand == "host" then
end
-- Handle messages
local ok, error = pcall( function()
parallel.waitForAny( function()
local ok, error = pcall(parallel.waitForAny,
function()
while true do
local _, timer = os.pullEvent("timer")
local nUserID = tPingPongTimer[timer]
@ -223,8 +223,8 @@ if sCommand == "host" then
end
end
end
end )
end )
end
)
if not ok then
printError(error)
end
@ -332,8 +332,8 @@ elseif sCommand == "join" then
drawTitle()
local ok, error = pcall( function()
parallel.waitForAny( function()
local ok, error = pcall(parallel.waitForAny,
function()
while true do
local sEvent, timer = os.pullEvent()
if sEvent == "timer" then
@ -402,8 +402,8 @@ elseif sCommand == "join" then
table.insert(tSendHistory, sChat)
end
end
end )
end )
end
)
-- Close the windows
term.redirect(parentTerm)

View File

@ -1,3 +1,4 @@
local pp = require "cc.pretty"
local tArgs = { ... }
if #tArgs == 0 then
@ -12,7 +13,13 @@ if #tArgs == 0 then
elseif #tArgs == 1 then
-- "set foo"
local sName = tArgs[1]
print( textutils.serialize(sName) .. " is " .. textutils.serialize(settings.get(sName)) )
local deets = settings.getDetails(sName)
local msg = pp.text(sName, colors.cyan) .. " is " .. pp.pretty(deets.value)
if deets.default ~= nil and deets.value ~= deets.default then
msg = msg .. " (default is " .. pp.pretty(deets.default) .. ")"
end
pp.print(msg)
if deets.description then print(deets.description) end
else
-- "set foo bar"
@ -31,15 +38,18 @@ else
value = sValue
end
local oldValue = settings.get( sValue )
if value ~= nil then
settings.set( sName, value )
print( textutils.serialize(sName) .. " set to " .. textutils.serialize(value) )
else
local option = settings.getDetails(sName)
if value == nil then
settings.unset(sName)
print(textutils.serialize(sName) .. " unset")
elseif option.type and option.type ~= type(value) then
printError(("%s is not a valid %s."):format(textutils.serialize(sValue), option.type))
else
settings.set(sName, value)
print(textutils.serialize(sName) .. " set to " .. textutils.serialize(value))
end
if value ~= oldValue then
settings.save( ".settings" )
if value ~= option.value then
settings.save()
end
end

View File

@ -18,8 +18,8 @@ local tProgramStack = {}
local shell = {}
local function createShellEnv(sDir)
local tEnv = {}
tEnv[ "shell" ] = shell
tEnv[ "multishell" ] = multishell
tEnv.shell = shell
tEnv.multishell = multishell
local package = {}
package.loaded = {
@ -100,8 +100,8 @@ local function createShellEnv( sDir )
error(sError, 2)
end
tEnv["package"] = package
tEnv["require"] = require
tEnv.package = package
tEnv.require = require
return tEnv
end
@ -132,7 +132,7 @@ local function run( _sCommand, ... )
local sDir = fs.getDir(sPath)
local env = createShellEnv(sDir)
env[ "arg" ] = { [0] = _sCommand, ... }
env.arg = { [0] = _sCommand, ... }
local result = os.run(env, sPath, ...)
tProgramStack[#tProgramStack] = nil
@ -194,7 +194,7 @@ function shell.setDir( _sDir )
if not fs.isDir(_sDir) then
error("Not a directory", 2)
end
sDir = _sDir
sDir = fs.combine(_sDir, "")
end
function shell.path()

View File

@ -10,12 +10,18 @@ import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.BasicEnvironment;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.*;
@ -78,7 +84,7 @@ public class ComputerTestDelegate
ComputerCraft.logPeripheralErrors = true;
Terminal term = new Terminal( 78, 20 );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), Long.MAX_VALUE );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files
List<String> children = new ArrayList<>();
@ -89,10 +95,11 @@ public class ComputerTestDelegate
try( WritableByteChannel channel = mount.openChannelForWrite( "startup.lua" );
Writer writer = Channels.newWriter( channel, StandardCharsets.UTF_8.newEncoder(), -1 ) )
{
writer.write( "loadfile('test/mcfly.lua', nil, _ENV)('test/spec') cct_test.finish()" );
writer.write( "loadfile('test-rom/mcfly.lua', nil, _ENV)('test-rom/spec') cct_test.finish()" );
}
computer = new Computer( new BasicEnvironment( mount ), term, 0 );
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
computer.addApi( new ILuaAPI()
{
@Override
@ -114,7 +121,7 @@ public class ComputerTestDelegate
try
{
computer.getAPIEnvironment().getFileSystem().mount(
"test-rom", "test",
"test-rom", "test-rom",
BasicEnvironment.createMount( ComputerTestDelegate.class, "test-rom", "test" )
);
}
@ -416,4 +423,33 @@ public class ComputerTestDelegate
{
return name.replace( "\0", " -> " );
}
private static class FakeModem extends WirelessModemPeripheral
{
FakeModem()
{
super( new ModemState(), true );
}
@Nonnull
@Override
@SuppressWarnings( "ConstantConditions" )
public World getWorld()
{
return null;
}
@Nonnull
@Override
public Vec3d getPosition()
{
return Vec3d.ZERO;
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
return this == other;
}
}
}

View File

@ -115,7 +115,7 @@ public class BasicEnvironment implements IComputerEnvironment
while( baseFile != null && !wholeFile.exists() )
{
baseFile = baseFile.getParentFile();
wholeFile = new File( baseFile, "resources/" + fallback + "/" + path );
wholeFile = new File( baseFile, "src/" + fallback + "/resources/" + path );
}
if( !wholeFile.exists() ) throw new IllegalStateException( "Cannot find ROM mount at " + file );

View File

@ -15,20 +15,24 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings( "deprecation" )
public class JarMountTest
{
private static final File ZIP_FILE = new File( "test-files/jar-mount.zip" );
private static final FileTime MODIFY_TIME = FileTime.from( Instant.EPOCH.plus( 2, ChronoUnit.DAYS ) );
@BeforeAll
public static void before() throws IOException
{
if( ZIP_FILE.exists() ) return;
ZIP_FILE.getParentFile().mkdirs();
try( ZipOutputStream stream = new ZipOutputStream( new FileOutputStream( ZIP_FILE ) ) )
@ -36,7 +40,7 @@ public class JarMountTest
stream.putNextEntry( new ZipEntry( "dir/" ) );
stream.closeEntry();
stream.putNextEntry( new ZipEntry( "dir/file.lua" ) );
stream.putNextEntry( new ZipEntry( "dir/file.lua" ).setLastModifiedTime( MODIFY_TIME ) );
stream.write( "print('testing')".getBytes( StandardCharsets.UTF_8 ) );
stream.closeEntry();
}
@ -63,7 +67,7 @@ public class JarMountTest
{
IMount mount = new JarMount( ZIP_FILE, "dir/file.lua" );
byte[] contents;
try( InputStream stream = mount.openForRead( "" ) )
try( @SuppressWarnings( "deprecation" ) InputStream stream = mount.openForRead( "" ) )
{
contents = ByteStreams.toByteArray( stream );
}
@ -76,11 +80,28 @@ public class JarMountTest
{
IMount mount = new JarMount( ZIP_FILE, "dir" );
byte[] contents;
try( InputStream stream = mount.openForRead( "file.lua" ) )
try( @SuppressWarnings( "deprecation" ) InputStream stream = mount.openForRead( "file.lua" ) )
{
contents = ByteStreams.toByteArray( stream );
}
assertEquals( new String( contents, StandardCharsets.UTF_8 ), "print('testing')" );
}
@Test
public void fileAttributes() throws IOException
{
BasicFileAttributes attributes = new JarMount( ZIP_FILE, "dir" ).getAttributes( "file.lua" );
assertFalse( attributes.isDirectory() );
assertEquals( "print('testing')".length(), attributes.size() );
assertEquals( MODIFY_TIME, attributes.lastModifiedTime() );
}
@Test
public void directoryAttributes() throws IOException
{
BasicFileAttributes attributes = new JarMount( ZIP_FILE, "dir" ).getAttributes( "" );
assertTrue( attributes.isDirectory() );
assertEquals( 0, attributes.size() );
}
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Nicolas Seriot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,9 @@
# JSON Parsing Test Suite
This is a collection of JSON test cases from [nst/JSONTestSuite][gh]. We simply
determine whether an object is succesfully parsed or not, and do not check the
contents.
See `LICENSE` for copyright information.
[gh]: https://github.com/nst/JSONTestSuite

View File

@ -0,0 +1 @@
[123.456e-789]

View File

@ -0,0 +1 @@
[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]

View File

@ -0,0 +1 @@
[-1e+9999]

View File

@ -0,0 +1 @@
[-123123e100000]

View File

@ -0,0 +1 @@
[123123e100000]

View File

@ -0,0 +1 @@
[123e-10000000]

Some files were not shown because too many files have changed in this diff Show More