mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-16 14:37:39 +00:00
Compare commits
39 Commits
v1.12.2-1.
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1da86d6f75 | ||
![]() |
53a23f8d3d | ||
![]() |
550f63b1cb | ||
![]() |
416e87852e | ||
![]() |
7940687df2 | ||
![]() |
37a447e745 | ||
![]() |
9e2232d240 | ||
![]() |
2a8efb3fd5 | ||
![]() |
c0f3ca81fb | ||
![]() |
014bf55cd4 | ||
![]() |
085ae2e74a | ||
![]() |
4ff33f165d | ||
![]() |
d929c02d2a | ||
![]() |
d50a08a549 | ||
![]() |
161a5b4707 | ||
![]() |
c6b6b4479c | ||
![]() |
96e7b60285 | ||
![]() |
086fccd997 | ||
![]() |
a4ae36b6b3 | ||
![]() |
ac075d9f53 | ||
![]() |
05d7be0362 | ||
![]() |
9a71dc1a26 | ||
![]() |
156023b154 | ||
![]() |
6b3773a862 | ||
![]() |
376d628cf0 | ||
![]() |
44062ebd52 | ||
![]() |
5739285fc2 | ||
![]() |
70b457ed18 | ||
![]() |
1547ecbeb3 | ||
![]() |
550ada2f9e | ||
![]() |
17b7727262 | ||
![]() |
4553e404b2 | ||
![]() |
a565a571f9 | ||
![]() |
fb64b6017b | ||
![]() |
ed4229ab70 | ||
![]() |
3fb906ef6c | ||
![]() |
e1663f3df0 | ||
![]() |
447c3ab125 | ||
![]() |
8fac68386e |
50
.github/workflows/main-ci.yml
vendored
50
.github/workflows/main-ci.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
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
|
||||
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: CC-Tweaked
|
||||
path: build/libs
|
||||
|
||||
lint-lua:
|
||||
name: Lint Lua
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Lint Lua code
|
||||
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 lint
|
||||
|
||||
- name: Check whitespace
|
||||
run: python3 tools/check-lines.py
|
16
.github/workflows/make-doc.sh
vendored
16
.github/workflows/make-doc.sh
vendored
@@ -1,16 +0,0 @@
|
||||
#!/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
29
.github/workflows/make-doc.yml
vendored
@@ -1,29 +0,0 @@
|
||||
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 }}
|
25
build.gradle
25
build.gradle
@@ -18,6 +18,7 @@ buildscript {
|
||||
|
||||
plugins {
|
||||
id "checkstyle"
|
||||
id "jacoco"
|
||||
id "com.github.hierynomus.license" version "0.15.0"
|
||||
id "com.matthewprenger.cursegradle" version "1.3.0"
|
||||
id "com.github.breadmoirai.github-release" version "2.2.4"
|
||||
@@ -51,14 +52,6 @@ repositories {
|
||||
name "Charset"
|
||||
artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]"
|
||||
}
|
||||
maven {
|
||||
name "Amadornes"
|
||||
url "https://maven.amadornes.com/"
|
||||
}
|
||||
maven {
|
||||
name "CraftTweaker"
|
||||
url "https://maven.blamejared.com/"
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
@@ -77,7 +70,7 @@ dependencies {
|
||||
|
||||
runtime "mezz.jei:jei_1.12.2:4.15.0.269"
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
|
||||
shade 'org.squiddev:Cobalt:0.5.5'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
|
||||
@@ -143,6 +136,9 @@ task proguard(type: ProGuardTask, dependsOn: jar) {
|
||||
// We want to avoid as much obfuscation as possible. We're only doing this to shrink code size.
|
||||
dontobfuscate; dontoptimize; keepattributes; keepparameternames
|
||||
|
||||
// Tell ProGuard to shut up
|
||||
dontwarn 'org.checkerframework.**'
|
||||
|
||||
// Proguard will remove directories by default, but that breaks JarMount.
|
||||
keepdirectories 'assets/computercraft/lua**'
|
||||
|
||||
@@ -251,11 +247,20 @@ test {
|
||||
}
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled true
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
|
||||
check.dependsOn jacocoTestReport
|
||||
|
||||
license {
|
||||
mapping("java", "SLASHSTAR_STYLE")
|
||||
strictCheck true
|
||||
|
||||
ext.year = Calendar.getInstance().get(Calendar.YEAR)
|
||||
ext.year = 2020
|
||||
}
|
||||
|
||||
[licenseMain, licenseFormatMain].forEach {
|
||||
|
@@ -1,6 +1,77 @@
|
||||
--- Execute a specific command.
|
||||
--
|
||||
-- @tparam string command The command to execute.
|
||||
-- @treturn boolean Whether the command executed successfully.
|
||||
-- @treturn { string... } The output of this command, as a list of lines.
|
||||
-- @treturn number|nil The number of "affected" objects, or `nil` if the command
|
||||
-- failed. The definition of this varies from command to command.
|
||||
-- @usage Set the block above the command computer to stone.
|
||||
--
|
||||
-- commands.exec("setblock ~ ~1 ~ minecraft:stone")
|
||||
function exec(command) end
|
||||
|
||||
--- Asynchronously execute a command.
|
||||
--
|
||||
-- Unlike @{exec}, this will immediately return, instead of waiting for the
|
||||
-- command to execute. This allows you to run multiple commands at the same
|
||||
-- time.
|
||||
--
|
||||
-- When this command has finished executing, it will queue a `task_complete`
|
||||
-- event containing the result of executing this command (what @{exec} would
|
||||
-- return).
|
||||
--
|
||||
-- @tparam string command The command to execute.
|
||||
-- @treturn number The "task id". When this command has been executed, it will
|
||||
-- queue a `task_complete` event with a matching id.
|
||||
-- @usage Asynchronously sets the block above the computer to stone.
|
||||
--
|
||||
-- commands.execAsync("~ ~1 ~ minecraft:stone")
|
||||
-- @see parallel One may also use the parallel API to run multiple commands at
|
||||
-- once.
|
||||
function execAsync(commad) end
|
||||
|
||||
--- List all available commands which the computer has permission to execute.
|
||||
--
|
||||
-- @treturn { string... } A list of all available commands
|
||||
function list() end
|
||||
|
||||
--- Get the position of the current command computer.
|
||||
--
|
||||
-- @treturn number This computer's x position.
|
||||
-- @treturn number This computer's y position.
|
||||
-- @treturn number This computer's z position.
|
||||
-- @see gps.locate To get the position of a non-command computer.
|
||||
function getBlockPosition() end
|
||||
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
||||
|
||||
--- Get some basic information about a block.
|
||||
--
|
||||
-- The returned table contains the current name, metadata and block state (as
|
||||
-- with @{turtle.inspect}). If there is a tile entity for that block, its NBT
|
||||
-- will also be returned.
|
||||
--
|
||||
-- @tparam number x The x position of the block to query.
|
||||
-- @tparam number y The y position of the block to query.
|
||||
-- @tparam number z The z position of the block to query.
|
||||
-- @treturn table The given block's information.
|
||||
-- @throws If the coordinates are not within the world, or are not currently
|
||||
-- loaded.
|
||||
function getBlockInfo(x, y, z) end
|
||||
|
||||
--- Get information about a range of blocks.
|
||||
--
|
||||
-- This returns the same information as @{getBlockInfo}, just for multiple
|
||||
-- blocks at once.
|
||||
--
|
||||
-- Blocks are traversed by ascending y level, followed by z and x - the returned
|
||||
-- table may be indexed using `x + z*width + y*depth*depth`.
|
||||
--
|
||||
-- @tparam number min_x The start x coordinate of the range to query.
|
||||
-- @tparam number min_y The start y coordinate of the range to query.
|
||||
-- @tparam number min_z The start z coordinate of the range to query.
|
||||
-- @tparam number max_x The end x coordinate of the range to query.
|
||||
-- @tparam number max_y The end y coordinate of the range to query.
|
||||
-- @tparam number max_z The end z coordinate of the range to query.
|
||||
-- @treturn { table... } A list of information about each block.
|
||||
-- @throws If the coordinates are not within the world.
|
||||
-- @throws If trying to get information about more than 4096 blocks.
|
||||
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
|
||||
|
@@ -19,6 +19,48 @@ function getFreeSpace(path) end
|
||||
function find(pattern) end
|
||||
function getDir(path) end
|
||||
|
||||
--- Returns true if a path is mounted to the parent filesystem.
|
||||
--
|
||||
-- The root filesystem "/" is considered a mount, along with disk folders and
|
||||
-- the rom folder. Other programs (such as network shares) can exstend this to
|
||||
-- make other mount types by correctly assigning their return value for getDrive.
|
||||
--
|
||||
-- @tparam string path The path to check.
|
||||
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
|
||||
-- @throws If the path does not exist.
|
||||
-- @see getDrive
|
||||
function isDriveRoot(path) end
|
||||
|
||||
--- Get the capacity of the drive at the given path.
|
||||
--
|
||||
-- This may be used in conjunction with @{getFreeSpace} to determine what
|
||||
-- percentage of this drive has been used.
|
||||
--
|
||||
-- @tparam string path The path of the drive to get.
|
||||
-- @treturn number This drive's capacity. This will be 0 for "read-only" drives,
|
||||
-- such as the ROM or treasure disks.
|
||||
function getCapacity(path) end
|
||||
|
||||
--- Get attributes about a specific file or folder.
|
||||
--
|
||||
-- The returned attributes table contains information about the size of the
|
||||
-- file, whether it is a directory, and when it was created and last modified.
|
||||
--
|
||||
-- The creation and modification times are given as the number of milliseconds
|
||||
-- since the UNIX epoch. This may be given to @{os.date} in order to convert it
|
||||
-- to more usable form.
|
||||
--
|
||||
-- @tparam string path The path to get attributes for.
|
||||
-- @treturn { size = number, isDir = boolean, created = number, modified = number }
|
||||
-- The resulting attributes.
|
||||
-- @throws If the path does not exist.
|
||||
-- @see getSize If you only care about the file's size.
|
||||
-- @see isDir If you only care whether a path is a directory or not.
|
||||
function attributes(path) end
|
||||
|
||||
-- Defined in bios.lua
|
||||
function complete(sPath, sLocation, bIncludeFiles, bIncludeDirs) end
|
||||
|
||||
--- A file handle which can be read from.
|
||||
--
|
||||
-- @type ReadHandle
|
||||
|
@@ -15,3 +15,10 @@ function cancelTimer(id) end
|
||||
function cancelAlarm(id) end
|
||||
function epoch(timezone) end
|
||||
function date(format, time) end
|
||||
|
||||
-- Defined in bios.lua
|
||||
function loadAPI(path) end
|
||||
function pullEvent(filter) end
|
||||
function pullEventRaw(filter) end
|
||||
function version() end
|
||||
function run(env, path, ...) end
|
||||
|
28
doc/stub/pocket.lua
Normal file
28
doc/stub/pocket.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
--[[-
|
||||
Control the current pocket computer, adding or removing upgrades.
|
||||
|
||||
This API is only available on pocket computers. As such, you may use its
|
||||
presence to determine what kind of computer you are using:
|
||||
|
||||
```lua
|
||||
if pocket then
|
||||
print("On a pocket computer")
|
||||
else
|
||||
print("On something else")
|
||||
end
|
||||
```
|
||||
]]
|
||||
|
||||
--- Search the player's inventory for another upgrade, replacing the existing
|
||||
-- one with that item if found.
|
||||
--
|
||||
-- This inventory search starts from the player's currently selected slot,
|
||||
-- allowing you to prioritise upgrades.
|
||||
--
|
||||
-- @throws If an upgrade cannot be found.
|
||||
function equipBack() end
|
||||
|
||||
--- Remove the pocket computer's current upgrade.
|
||||
--
|
||||
-- @throws If this pocket computer does not currently have an upgrade.
|
||||
function unequipBack() end
|
@@ -1,14 +1,120 @@
|
||||
--[[- Interact with redstone attached to this computer.
|
||||
|
||||
The @{redstone} library exposes three "types" of redstone control:
|
||||
- Binary input/output (@{setOutput}/@{getInput}): These simply check if a
|
||||
redstone wire has any input or output. A signal strength of 1 and 15 are
|
||||
treated the same.
|
||||
- Analogue input/output (@{setAnalogueOutput}/@{getAnalogueInput}): These
|
||||
work with the actual signal strength of the redstone wired, from 0 to 15.
|
||||
- Bundled cables (@{setBundledOutput}/@{getBundledInput}): These interact with
|
||||
"bundled" cables, such as those from Project:Red. These allow you to send
|
||||
16 separate on/off signals. Each channel corresponds to a colour, with the
|
||||
first being @{colors.white} and the last @{colors.black}.
|
||||
|
||||
Whenever a redstone input changes, a `redstone` event will be fired. This may
|
||||
be used in or
|
||||
|
||||
This module may also be referred to as `rs`. For example, one may call
|
||||
`rs.getSides()` instead of @{redstone.getSides}.
|
||||
|
||||
@module redstone
|
||||
@usage Toggle the redstone signal above the computer every 0.5 seconds.
|
||||
|
||||
while true do
|
||||
redstone.setOutput("top", not redstone.getOutput("top"))
|
||||
sleep(0.5)
|
||||
end
|
||||
@usage Mimic a redstone comparator in [subtraction mode][comparator].
|
||||
|
||||
while true do
|
||||
local rear = rs.getAnalogueInput("back")
|
||||
local sides = math.max(rs.getAnalogueInput("left"), rs.getAnalogueInput("right"))
|
||||
rs.setAnalogueOutput("front", math.max(rear - sides, 0))
|
||||
|
||||
os.pullEvent("redstone") -- Wait for a change to inputs.
|
||||
end
|
||||
|
||||
[comparator]: https://minecraft.gamepedia.com/Redstone_Comparator#Subtract_signal_strength "Redstone Comparator on the Minecraft wiki."
|
||||
]]
|
||||
|
||||
--- Returns a table containing the six sides of the computer. Namely, "top",
|
||||
-- "bottom", "left", "right", "front" and "back".
|
||||
--
|
||||
-- @treturn { string... } A table of valid sides.
|
||||
function getSides() end
|
||||
|
||||
--- Turn the redstone signal of a specific side on or off.
|
||||
--
|
||||
-- @tparam string side The side to set.
|
||||
-- @tparam boolean on Whether the redstone signal should be on or off. When on,
|
||||
-- a signal strength of 15 is emitted.
|
||||
function setOutput(side, on) end
|
||||
|
||||
--- Get the current redstone output of a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn boolean Whether the redstone output is on or off.
|
||||
-- @see setOutput
|
||||
function getOutput(side) end
|
||||
|
||||
--- Get the current redstone input of a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn boolean Whether the redstone input is on or off.
|
||||
function getInput(side) end
|
||||
function setBundledOutput(side, output) end
|
||||
function getBundledOutput(side) end
|
||||
function getBundledInput(side) end
|
||||
function testBundledInput(side, mask) end
|
||||
|
||||
--- Set the redstone signal strength for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to set.
|
||||
-- @tparam number value The signal strength, between 0 and 15.
|
||||
-- @throws If `value` is not between 0 and 15.
|
||||
function setAnalogOutput(side, value) end
|
||||
setAnalogueOutput = setAnalogOutput
|
||||
|
||||
--- Get the redstone output signal strength for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The output signal strength, between 0 and 15.
|
||||
-- @see setAnalogueOutput
|
||||
function getAnalogOutput(sid) end
|
||||
getAnalogueOutput = getAnalogOutput
|
||||
|
||||
--- Get the redstone input signal strength for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The input signal strength, between 0 and 15.
|
||||
function getAnalogInput(side) end
|
||||
getAnalogueInput = getAnaloguInput
|
||||
getAnalogueInput = getAnalogInput
|
||||
|
||||
--- Set the bundled cable output for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to set.
|
||||
-- @tparam number The colour bitmask to set.
|
||||
-- @see colors.subtract For removing a colour from the bitmask.
|
||||
-- @see colors.combine For adding a colour to the bitmask.
|
||||
function setBundledOutput(side, output) end
|
||||
|
||||
--- Get the bundled cable output for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The bundled cable's output.
|
||||
function getBundledOutput(side) end
|
||||
|
||||
--- Get the bundled cable input for a specific side.
|
||||
--
|
||||
-- @tparam string side The side to get.
|
||||
-- @treturn number The bundled cable's input.
|
||||
-- @see testBundledInput To determine if a specific colour is set.
|
||||
function getBundledInput(side) end
|
||||
|
||||
--- Determine if a specific combination of colours are on for the given side.
|
||||
--
|
||||
-- @tparam string side The side to test.
|
||||
-- @tparam number mask The mask to test.
|
||||
-- @see getBundledInput
|
||||
-- @see colors.combine For adding a colour to the bitmask.
|
||||
-- @usage Check if @{colors.white} and @{colors.black} are on for above the
|
||||
-- computer.
|
||||
--
|
||||
-- print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black)))
|
||||
function testBundledInput(side, mask) end
|
||||
|
@@ -15,14 +15,14 @@ isColor = isColour
|
||||
function getTextColour() end
|
||||
getTextColor = getTextColor
|
||||
function getBackgroundColour() end
|
||||
getBackgroundColour = getBackgroundColour
|
||||
getBackgroundColor = getBackgroundColour
|
||||
function blit(text, text_colours, background_colours) end
|
||||
function setPaletteColour(colour, ...) end
|
||||
setPaletteColour = setPaletteColour
|
||||
setPaletteColor = setPaletteColour
|
||||
function getPaletteColour(colour, ...) end
|
||||
getPaletteColour = getPaletteColour
|
||||
getPaletteColor = getPaletteColour
|
||||
function nativePaletteColour(colour) end
|
||||
nativePaletteColour = nativePaletteColour
|
||||
nativePaletteColor = nativePaletteColour
|
||||
|
||||
--- @type Redirect
|
||||
local Redirect = {}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Mod properties
|
||||
mod_version=1.87.0
|
||||
mod_version=1.89.2
|
||||
|
||||
# Minecraft properties
|
||||
mc_version=1.12.2
|
||||
|
@@ -33,17 +33,27 @@
|
||||
|
||||
;; 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)
|
||||
|
||||
;; Some APIS (keys, colour and os mainly) are incomplete right now.
|
||||
-var:unresolved-member)
|
||||
(lint
|
||||
(bracket-spaces
|
||||
(call no-space)
|
||||
(function-args no-space)
|
||||
(parens no-space)
|
||||
(table space)
|
||||
(index no-space))))
|
||||
(index no-space))
|
||||
|
||||
;; colours imports from colors, and we don't handle that right now.
|
||||
;; keys is entirely dynamic, so we skip it.
|
||||
(dynamic-modules colours keys)
|
||||
|
||||
(globals
|
||||
:max
|
||||
_CC_DEFAULT_SETTINGS
|
||||
_CC_DISABLE_LUA51_FEATURES
|
||||
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
|
||||
;; isn't smart enough.
|
||||
sleep write printError read rs)))
|
||||
|
||||
;; We disable the unused global linter in bios.lua and the APIs. In the future
|
||||
;; hopefully we'll get illuaminate to handle this.
|
||||
@@ -60,17 +70,13 @@
|
||||
|
||||
;; Suppress warnings for currently undocumented modules.
|
||||
(at
|
||||
(/doc/stub/commands.lua
|
||||
/doc/stub/fs.lua
|
||||
(/doc/stub/fs.lua
|
||||
/doc/stub/http.lua
|
||||
/doc/stub/os.lua
|
||||
/doc/stub/redstone.lua
|
||||
/doc/stub/term.lua
|
||||
/doc/stub/turtle.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/command/commands.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
|
||||
|
||||
(linters -doc:undocumented -doc:undocumented-arg))
|
||||
|
||||
@@ -79,6 +85,11 @@
|
||||
(/src/main/resources/*/computercraft/lua/rom/apis/textutils.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/completion.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/programs/advanced/multishell.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/programs/shell.lua)
|
||||
(linters -doc:unresolved-reference))
|
||||
|
||||
(at /src/test/resources/test-rom
|
||||
(lint
|
||||
(globals
|
||||
:max sleep write
|
||||
cct_test describe expect howlci fail it pending stub)))
|
||||
|
@@ -112,7 +112,8 @@ public class ComputerCraft
|
||||
public static boolean disable_lua51_features = false;
|
||||
public static String default_computer_settings = "";
|
||||
public static boolean debug_enable = true;
|
||||
public static boolean logPeripheralErrors = false;
|
||||
public static boolean logPeripheralErrors = true;
|
||||
public static boolean commandRequireCreative = true;
|
||||
|
||||
public static int computer_threads = 1;
|
||||
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
|
||||
@@ -137,6 +138,7 @@ public class ComputerCraft
|
||||
public static int modem_highAltitudeRangeDuringStorm = 384;
|
||||
public static int maxNotesPerTick = 8;
|
||||
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
|
||||
public static long monitorBandwidth = 1_000_000;
|
||||
|
||||
public static boolean turtlesNeedFuel = true;
|
||||
public static int turtleFuelLimit = 20000;
|
||||
@@ -536,7 +538,7 @@ public class ComputerCraft
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
if( zipFile != null ) IoUtil.closeQuietly( zipFile );
|
||||
IoUtil.closeQuietly( zipFile );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -50,15 +50,14 @@ public final class FixedWidthFontRenderer
|
||||
{
|
||||
}
|
||||
|
||||
private static float toGreyscale( double[] rgb )
|
||||
public static float toGreyscale( double[] rgb )
|
||||
{
|
||||
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
|
||||
}
|
||||
|
||||
private static int getColour( char c )
|
||||
public static int getColour( char c, Colour def )
|
||||
{
|
||||
int i = "0123456789abcdef".indexOf( c );
|
||||
return i < 0 ? 0 : 15 - i;
|
||||
return 15 - Terminal.getColour( c, def );
|
||||
}
|
||||
|
||||
private static void drawChar( BufferBuilder buffer, float x, float y, int index, float r, float g, float b )
|
||||
@@ -92,7 +91,7 @@ public final class FixedWidthFontRenderer
|
||||
|
||||
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 ) );
|
||||
double[] colour = palette.getColour( getColour( colourIndex, Colour.Black ) );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
@@ -160,7 +159,7 @@ public final class FixedWidthFontRenderer
|
||||
|
||||
for( int i = 0; i < text.length(); i++ )
|
||||
{
|
||||
double[] colour = palette.getColour( getColour( textColour.charAt( i ) ) );
|
||||
double[] colour = palette.getColour( getColour( textColour.charAt( i ), Colour.White ) );
|
||||
float r, g, b;
|
||||
if( greyscale )
|
||||
{
|
||||
|
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.client.render;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
class MonitorTextureBufferShader
|
||||
{
|
||||
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
||||
|
||||
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
|
||||
|
||||
private static int uniformFont;
|
||||
private static int uniformWidth;
|
||||
private static int uniformHeight;
|
||||
private static int uniformTbo;
|
||||
private static int uniformPalette;
|
||||
|
||||
private static boolean initialised;
|
||||
private static boolean ok;
|
||||
private static int program;
|
||||
|
||||
static void setupUniform( int width, int height, Palette palette, boolean greyscale )
|
||||
{
|
||||
OpenGlHelper.glUniform1i( uniformWidth, width );
|
||||
OpenGlHelper.glUniform1i( uniformHeight, height );
|
||||
|
||||
PALETTE_BUFFER.rewind();
|
||||
for( int i = 0; i < 16; i++ )
|
||||
{
|
||||
double[] colour = palette.getColour( i );
|
||||
if( greyscale )
|
||||
{
|
||||
float f = FixedWidthFontRenderer.toGreyscale( colour );
|
||||
PALETTE_BUFFER.put( f ).put( f ).put( f );
|
||||
}
|
||||
else
|
||||
{
|
||||
PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] );
|
||||
}
|
||||
}
|
||||
PALETTE_BUFFER.flip();
|
||||
OpenGlHelper.glUniform3( uniformPalette, PALETTE_BUFFER );
|
||||
}
|
||||
|
||||
static boolean use()
|
||||
{
|
||||
if( initialised )
|
||||
{
|
||||
if( ok ) OpenGlHelper.glUseProgram( program );
|
||||
return ok;
|
||||
}
|
||||
|
||||
if( ok = load() )
|
||||
{
|
||||
GL20.glUseProgram( program );
|
||||
OpenGlHelper.glUniform1i( uniformFont, 0 );
|
||||
OpenGlHelper.glUniform1i( uniformTbo, TEXTURE_INDEX - GL13.GL_TEXTURE0 );
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
private static boolean load()
|
||||
{
|
||||
initialised = true;
|
||||
|
||||
try
|
||||
{
|
||||
int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, "assets/computercraft/shaders/monitor.vert" );
|
||||
int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, "assets/computercraft/shaders/monitor.frag" );
|
||||
|
||||
program = OpenGlHelper.glCreateProgram();
|
||||
OpenGlHelper.glAttachShader( program, vertexShader );
|
||||
OpenGlHelper.glAttachShader( program, fragmentShader );
|
||||
GL20.glBindAttribLocation( program, 0, "v_pos" );
|
||||
|
||||
OpenGlHelper.glLinkProgram( program );
|
||||
boolean ok = OpenGlHelper.glGetProgrami( program, GL20.GL_LINK_STATUS ) != 0;
|
||||
String log = OpenGlHelper.glGetProgramInfoLog( program, Short.MAX_VALUE ).trim();
|
||||
if( !Strings.isNullOrEmpty( log ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "Problems when linking monitor shader: {}", log );
|
||||
}
|
||||
|
||||
GL20.glDetachShader( program, vertexShader );
|
||||
GL20.glDetachShader( program, fragmentShader );
|
||||
OpenGlHelper.glDeleteShader( vertexShader );
|
||||
OpenGlHelper.glDeleteShader( fragmentShader );
|
||||
|
||||
if( !ok ) return false;
|
||||
|
||||
uniformFont = getUniformLocation( program, "u_font" );
|
||||
uniformWidth = getUniformLocation( program, "u_width" );
|
||||
uniformHeight = getUniformLocation( program, "u_height" );
|
||||
uniformTbo = getUniformLocation( program, "u_tbo" );
|
||||
uniformPalette = getUniformLocation( program, "u_palette" );
|
||||
|
||||
ComputerCraft.log.info( "Loaded monitor shader." );
|
||||
return true;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
ComputerCraft.log.error( "Cannot load monitor shaders", e );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int loadShader( int kind, String path ) throws IOException
|
||||
{
|
||||
InputStream stream = TileEntityMonitorRenderer.class.getClassLoader().getResourceAsStream( path );
|
||||
if( stream == null ) throw new IllegalArgumentException( "Cannot find " + path );
|
||||
byte[] contents = IOUtils.toByteArray( new BufferedInputStream( stream ) );
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer( contents.length );
|
||||
buffer.put( contents );
|
||||
buffer.position( 0 );
|
||||
|
||||
int shader = OpenGlHelper.glCreateShader( kind );
|
||||
|
||||
OpenGlHelper.glShaderSource( shader, buffer );
|
||||
OpenGlHelper.glCompileShader( shader );
|
||||
|
||||
boolean ok = OpenGlHelper.glGetShaderi( shader, GL20.GL_COMPILE_STATUS ) != 0;
|
||||
String log = OpenGlHelper.glGetShaderInfoLog( shader, Short.MAX_VALUE ).trim();
|
||||
if( !Strings.isNullOrEmpty( log ) )
|
||||
{
|
||||
ComputerCraft.log.warn( "Problems when loading monitor shader {}: {}", path, log );
|
||||
}
|
||||
|
||||
if( !ok ) throw new IllegalStateException( "Cannot compile shader " + path );
|
||||
return shader;
|
||||
}
|
||||
|
||||
private static int getUniformLocation( int program, String name )
|
||||
{
|
||||
int uniform = OpenGlHelper.glGetUniformLocation( program, name );
|
||||
if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name );
|
||||
return uniform;
|
||||
}
|
||||
}
|
@@ -8,28 +8,34 @@ package dan200.computercraft.client.render;
|
||||
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 net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.*;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
|
||||
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
|
||||
|
||||
public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMonitor>
|
||||
{
|
||||
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
|
||||
private static ByteBuffer tboContents;
|
||||
|
||||
@Override
|
||||
public void render( @Nonnull TileMonitor tileEntity, double posX, double posY, double posZ, float f, int i, float f2 )
|
||||
@@ -97,8 +103,8 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( terminal != null )
|
||||
{
|
||||
// Draw a terminal
|
||||
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
|
||||
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
|
||||
double xScale = xSize / (terminal.getWidth() * FONT_WIDTH);
|
||||
double yScale = ySize / (terminal.getHeight() * FONT_HEIGHT);
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.scale( (float) xScale, (float) -yScale, 1.0f );
|
||||
@@ -147,6 +153,59 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
|
||||
switch( renderer )
|
||||
{
|
||||
case TBO:
|
||||
{
|
||||
if( !MonitorTextureBufferShader.use() ) return;
|
||||
|
||||
Terminal terminal = monitor.getTerminal();
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
|
||||
if( redraw )
|
||||
{
|
||||
int size = width * height * 3;
|
||||
if( tboContents == null || tboContents.capacity() < size )
|
||||
{
|
||||
tboContents = GLAllocation.createDirectByteBuffer( size );
|
||||
}
|
||||
|
||||
ByteBuffer monitorBuffer = tboContents;
|
||||
monitorBuffer.clear();
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
|
||||
for( int x = 0; x < width; x++ )
|
||||
{
|
||||
monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) );
|
||||
monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.White ) );
|
||||
monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.Black ) );
|
||||
}
|
||||
}
|
||||
monitorBuffer.flip();
|
||||
|
||||
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
|
||||
OpenGlHelper.glBufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL15.GL_STATIC_DRAW );
|
||||
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||
}
|
||||
|
||||
// Bind TBO texture and set up the uniforms. We've already set up the main font above.
|
||||
GlStateManager.setActiveTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
|
||||
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
|
||||
GlStateManager.setActiveTexture( GL13.GL_TEXTURE0 );
|
||||
|
||||
MonitorTextureBufferShader.setupUniform( width, height, terminal.getPalette(), !monitor.isColour() );
|
||||
|
||||
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
OpenGlHelper.glUseProgram( 0 );
|
||||
break;
|
||||
}
|
||||
|
||||
case VBO:
|
||||
{
|
||||
VertexBuffer vbo = monitor.buffer;
|
||||
|
@@ -16,11 +16,11 @@ import static dan200.computercraft.api.lua.ArgumentHelper.*;
|
||||
|
||||
public class RedstoneAPI implements ILuaAPI
|
||||
{
|
||||
private IAPIEnvironment m_environment;
|
||||
private final IAPIEnvironment environment;
|
||||
|
||||
public RedstoneAPI( IAPIEnvironment environment )
|
||||
{
|
||||
m_environment = environment;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,31 +63,31 @@ public class RedstoneAPI implements ILuaAPI
|
||||
// setOutput
|
||||
ComputerSide side = parseSide( args );
|
||||
boolean output = getBoolean( args, 1 );
|
||||
m_environment.setOutput( side, output ? 15 : 0 );
|
||||
environment.setOutput( side, output ? 15 : 0 );
|
||||
return null;
|
||||
}
|
||||
case 2: // getOutput
|
||||
return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 };
|
||||
return new Object[] { environment.getOutput( parseSide( args ) ) > 0 };
|
||||
case 3: // getInput
|
||||
return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 };
|
||||
return new Object[] { environment.getInput( parseSide( args ) ) > 0 };
|
||||
case 4:
|
||||
{
|
||||
// setBundledOutput
|
||||
ComputerSide side = parseSide( args );
|
||||
int output = getInt( args, 1 );
|
||||
m_environment.setBundledOutput( side, output );
|
||||
environment.setBundledOutput( side, output );
|
||||
return null;
|
||||
}
|
||||
case 5: // getBundledOutput
|
||||
return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) };
|
||||
return new Object[] { environment.getBundledOutput( parseSide( args ) ) };
|
||||
case 6: // getBundledInput
|
||||
return new Object[] { m_environment.getBundledInput( parseSide( args ) ) };
|
||||
return new Object[] { environment.getBundledInput( parseSide( args ) ) };
|
||||
case 7:
|
||||
{
|
||||
// testBundledInput
|
||||
ComputerSide side = parseSide( args );
|
||||
int mask = getInt( args, 1 );
|
||||
int input = m_environment.getBundledInput( side );
|
||||
int input = environment.getBundledInput( side );
|
||||
return new Object[] { (input & mask) == mask };
|
||||
}
|
||||
case 8:
|
||||
@@ -100,15 +100,15 @@ public class RedstoneAPI implements ILuaAPI
|
||||
{
|
||||
throw new LuaException( "Expected number in range 0-15" );
|
||||
}
|
||||
m_environment.setOutput( side, output );
|
||||
environment.setOutput( side, output );
|
||||
return null;
|
||||
}
|
||||
case 10:
|
||||
case 11: // getAnalogOutput/getAnalogueOutput
|
||||
return new Object[] { m_environment.getOutput( parseSide( args ) ) };
|
||||
return new Object[] { environment.getOutput( parseSide( args ) ) };
|
||||
case 12:
|
||||
case 13: // getAnalogInput/getAnalogueInput
|
||||
return new Object[] { m_environment.getInput( parseSide( args ) ) };
|
||||
return new Object[] { environment.getInput( parseSide( args ) ) };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@@ -37,12 +37,8 @@ public abstract class HandleGeneric implements ILuaObject
|
||||
{
|
||||
m_open = false;
|
||||
|
||||
Closeable closeable = m_closable;
|
||||
if( closeable != null )
|
||||
{
|
||||
IoUtil.closeQuietly( closeable );
|
||||
m_closable = null;
|
||||
}
|
||||
IoUtil.closeQuietly( m_closable );
|
||||
m_closable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -106,7 +106,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
|
||||
|
||||
protected static <T extends Closeable> T closeCloseable( T closeable )
|
||||
{
|
||||
if( closeable != null ) IoUtil.closeQuietly( closeable );
|
||||
IoUtil.closeQuietly( closeable );
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@@ -221,7 +221,7 @@ public class Websocket extends Resource<Websocket>
|
||||
|
||||
WeakReference<WebsocketHandle> websocketHandleRef = websocketHandle;
|
||||
WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get();
|
||||
if( websocketHandle != null ) IoUtil.closeQuietly( websocketHandle );
|
||||
IoUtil.closeQuietly( websocketHandle );
|
||||
this.websocketHandle = null;
|
||||
}
|
||||
|
||||
|
@@ -13,6 +13,7 @@ import javax.annotation.Nullable;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
@@ -49,11 +50,11 @@ import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT;
|
||||
public final class ComputerThread
|
||||
{
|
||||
/**
|
||||
* How often the computer thread monitor should run, in milliseconds.
|
||||
* How often the computer thread monitor should run.
|
||||
*
|
||||
* @see Monitor
|
||||
*/
|
||||
private static final int MONITOR_WAKEUP = 100;
|
||||
private static final long MONITOR_WAKEUP = TimeUnit.MILLISECONDS.toNanos( 100 );
|
||||
|
||||
/**
|
||||
* The target latency between executing two tasks on a single machine.
|
||||
@@ -76,6 +77,13 @@ public final class ComputerThread
|
||||
*/
|
||||
private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD;
|
||||
|
||||
/**
|
||||
* Time difference between reporting crashed threads.
|
||||
*
|
||||
* @see TaskRunner#reportTimeout(ComputerExecutor, long)
|
||||
*/
|
||||
private static final long REPORT_DEBOUNCE = TimeUnit.SECONDS.toNanos( 1 );
|
||||
|
||||
/**
|
||||
* Lock used for modifications to the array of current threads.
|
||||
*/
|
||||
@@ -102,6 +110,8 @@ public final class ComputerThread
|
||||
private static final ReentrantLock computerLock = new ReentrantLock();
|
||||
|
||||
private static final Condition hasWork = computerLock.newCondition();
|
||||
private static final AtomicInteger idleWorkers = new AtomicInteger( 0 );
|
||||
private static final Condition monitorWakeup = computerLock.newCondition();
|
||||
|
||||
/**
|
||||
* Active queues to execute.
|
||||
@@ -135,7 +145,7 @@ public final class ComputerThread
|
||||
|
||||
if( runners == null )
|
||||
{
|
||||
// TODO: Change the runners length on config reloads
|
||||
// TODO: Update this on config reloads. Or possibly on world restarts?
|
||||
runners = new TaskRunner[ComputerCraft.computer_threads];
|
||||
|
||||
// latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for
|
||||
@@ -227,9 +237,14 @@ public final class ComputerThread
|
||||
|
||||
executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime );
|
||||
|
||||
boolean wasBusy = isBusy();
|
||||
// Add to the queue, and signal the workers.
|
||||
computerQueue.add( executor );
|
||||
hasWork.signal();
|
||||
|
||||
// If we've transitioned into a busy state, notify the monitor. This will cause it to sleep for scaledPeriod
|
||||
// instead of the longer wakeup duration.
|
||||
if( !wasBusy && isBusy() ) monitorWakeup.signal();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -346,6 +361,17 @@ public final class ComputerThread
|
||||
return !computerQueue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have more work queued than we have capacity for. Effectively a more fine-grained version of
|
||||
* {@link #hasPendingWork()}.
|
||||
*
|
||||
* @return If the computer threads are busy.
|
||||
*/
|
||||
private static boolean isBusy()
|
||||
{
|
||||
return computerQueue.size() > idleWorkers.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard
|
||||
* abort limit.
|
||||
@@ -357,76 +383,93 @@ public final class ComputerThread
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
while( true )
|
||||
{
|
||||
while( true )
|
||||
computerLock.lock();
|
||||
try
|
||||
{
|
||||
Thread.sleep( MONITOR_WAKEUP );
|
||||
// If we've got more work than we have capacity for it, then we'll need to pause a task soon, so
|
||||
// sleep for a single pause duration. Otherwise we only need to wake up to set the soft/hard abort
|
||||
// flags, which are far less granular.
|
||||
monitorWakeup.awaitNanos( isBusy() ? scaledPeriod() : MONITOR_WAKEUP );
|
||||
}
|
||||
catch( InterruptedException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Monitor thread interrupted. Computers may behave very badly!", e );
|
||||
break;
|
||||
}
|
||||
finally
|
||||
{
|
||||
computerLock.unlock();
|
||||
}
|
||||
|
||||
TaskRunner[] currentRunners = ComputerThread.runners;
|
||||
if( currentRunners != null )
|
||||
checkRunners();
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkRunners()
|
||||
{
|
||||
TaskRunner[] currentRunners = ComputerThread.runners;
|
||||
if( currentRunners == null ) return;
|
||||
|
||||
for( int i = 0; i < currentRunners.length; i++ )
|
||||
{
|
||||
TaskRunner runner = currentRunners[i];
|
||||
// If we've no runner, skip.
|
||||
if( runner == null || runner.owner == null || !runner.owner.isAlive() )
|
||||
{
|
||||
if( !running ) continue;
|
||||
|
||||
// Mark the old runner as dead and start a new one.
|
||||
ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!",
|
||||
runner != null && runner.owner != null ? runner.owner.getName() : runner );
|
||||
if( runner != null ) runner.running = false;
|
||||
runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
|
||||
}
|
||||
|
||||
// If the runner has no work, skip
|
||||
ComputerExecutor executor = runner.currentExecutor.get();
|
||||
if( executor == null ) continue;
|
||||
|
||||
// Refresh the timeout state. Will set the pause/soft timeout flags as appropriate.
|
||||
executor.timeout.refresh();
|
||||
|
||||
// If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT),
|
||||
// then we can let the Lua machine do its work.
|
||||
long afterStart = executor.timeout.nanoCumulative();
|
||||
long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT;
|
||||
if( afterHardAbort < 0 ) continue;
|
||||
|
||||
// Set the hard abort flag.
|
||||
executor.timeout.hardAbort();
|
||||
executor.abort();
|
||||
|
||||
if( afterHardAbort >= ABORT_TIMEOUT * 2 )
|
||||
{
|
||||
// If we've hard aborted and interrupted, and we're still not dead, then mark the runner
|
||||
// as dead, finish off the task, and spawn a new runner.
|
||||
runner.reportTimeout( executor, afterStart );
|
||||
runner.running = false;
|
||||
runner.owner.interrupt();
|
||||
|
||||
ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null );
|
||||
if( thisExecutor != null ) afterWork( runner, executor );
|
||||
|
||||
synchronized( threadLock )
|
||||
{
|
||||
for( int i = 0; i < currentRunners.length; i++ )
|
||||
if( running && runners.length > i && runners[i] == runner )
|
||||
{
|
||||
TaskRunner runner = currentRunners[i];
|
||||
// If we've no runner, skip.
|
||||
if( runner == null || runner.owner == null || !runner.owner.isAlive() )
|
||||
{
|
||||
if( !running ) continue;
|
||||
|
||||
// Mark the old runner as dead and start a new one.
|
||||
ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!",
|
||||
runner != null && runner.owner != null ? runner.owner.getName() : runner );
|
||||
if( runner != null ) runner.running = false;
|
||||
runnerFactory.newThread( runners[i] = new TaskRunner() ).start();
|
||||
}
|
||||
|
||||
// If the runner has no work, skip
|
||||
ComputerExecutor executor = runner.currentExecutor.get();
|
||||
if( executor == null ) continue;
|
||||
|
||||
// If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT),
|
||||
// then we can let the Lua machine do its work.
|
||||
long afterStart = executor.timeout.nanoCumulative();
|
||||
long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT;
|
||||
if( afterHardAbort < 0 ) continue;
|
||||
|
||||
// Set the hard abort flag.
|
||||
executor.timeout.hardAbort();
|
||||
executor.abort();
|
||||
|
||||
if( afterHardAbort >= ABORT_TIMEOUT )
|
||||
{
|
||||
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt
|
||||
// the task.
|
||||
timeoutTask( executor, runner.owner, afterStart );
|
||||
runner.owner.interrupt();
|
||||
}
|
||||
else if( afterHardAbort >= ABORT_TIMEOUT * 2 )
|
||||
{
|
||||
// If we've hard aborted and interrupted, and we're still not dead, then mark the runner
|
||||
// as dead, finish off the task, and spawn a new runner.
|
||||
timeoutTask( executor, runner.owner, afterStart );
|
||||
runner.running = false;
|
||||
runner.owner.interrupt();
|
||||
|
||||
ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null );
|
||||
if( thisExecutor != null ) afterWork( runner, executor );
|
||||
|
||||
synchronized( threadLock )
|
||||
{
|
||||
if( running && runners.length > i && runners[i] == runner )
|
||||
{
|
||||
runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( InterruptedException ignored )
|
||||
{
|
||||
else if( afterHardAbort >= ABORT_TIMEOUT )
|
||||
{
|
||||
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt
|
||||
// the task.
|
||||
runner.reportTimeout( executor, afterStart );
|
||||
runner.owner.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,6 +484,7 @@ public final class ComputerThread
|
||||
private static final class TaskRunner implements Runnable
|
||||
{
|
||||
Thread owner;
|
||||
long lastReport = Long.MIN_VALUE;
|
||||
volatile boolean running = true;
|
||||
|
||||
final AtomicReference<ComputerExecutor> currentExecutor = new AtomicReference<>();
|
||||
@@ -460,6 +504,7 @@ public final class ComputerThread
|
||||
computerLock.lockInterruptibly();
|
||||
try
|
||||
{
|
||||
idleWorkers.incrementAndGet();
|
||||
while( computerQueue.isEmpty() ) hasWork.await();
|
||||
executor = computerQueue.pollFirst();
|
||||
assert executor != null : "hasWork should ensure we never receive null work";
|
||||
@@ -467,6 +512,7 @@ public final class ComputerThread
|
||||
finally
|
||||
{
|
||||
computerLock.unlock();
|
||||
idleWorkers.decrementAndGet();
|
||||
}
|
||||
}
|
||||
catch( InterruptedException ignored )
|
||||
@@ -514,27 +560,32 @@ public final class ComputerThread
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void timeoutTask( ComputerExecutor executor, Thread thread, long time )
|
||||
{
|
||||
if( !ComputerCraft.logPeripheralErrors ) return;
|
||||
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append( "Terminating computer #" ).append( executor.getComputer().getID() )
|
||||
.append( " due to timeout (running for " ).append( time * 1e-9 )
|
||||
.append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " )
|
||||
.append( thread.getName() )
|
||||
.append( " is currently " )
|
||||
.append( thread.getState() );
|
||||
Object blocking = LockSupport.getBlocker( thread );
|
||||
if( blocking != null ) builder.append( "\n on " ).append( blocking );
|
||||
|
||||
for( StackTraceElement element : thread.getStackTrace() )
|
||||
private void reportTimeout( ComputerExecutor executor, long time )
|
||||
{
|
||||
builder.append( "\n at " ).append( element );
|
||||
}
|
||||
if( !ComputerCraft.logPeripheralErrors ) return;
|
||||
|
||||
ComputerCraft.log.warn( builder.toString() );
|
||||
// Attempt to debounce stack trace reporting, limiting ourselves to one every second.
|
||||
long now = System.nanoTime();
|
||||
if( lastReport != Long.MIN_VALUE && now - lastReport - REPORT_DEBOUNCE <= 0 ) return;
|
||||
lastReport = now;
|
||||
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append( "Terminating computer #" ).append( executor.getComputer().getID() )
|
||||
.append( " due to timeout (running for " ).append( time * 1e-9 )
|
||||
.append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " )
|
||||
.append( owner.getName() )
|
||||
.append( " is currently " )
|
||||
.append( owner.getState() );
|
||||
Object blocking = LockSupport.getBlocker( owner );
|
||||
if( blocking != null ) builder.append( "\n on " ).append( blocking );
|
||||
|
||||
for( StackTraceElement element : owner.getStackTrace() )
|
||||
{
|
||||
builder.append( "\n at " ).append( element );
|
||||
}
|
||||
|
||||
ComputerCraft.log.warn( builder.toString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -86,7 +86,7 @@ public final class TimeoutState
|
||||
/**
|
||||
* Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags.
|
||||
*/
|
||||
public void refresh()
|
||||
public synchronized void refresh()
|
||||
{
|
||||
// Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we
|
||||
// need to handle overflow.
|
||||
@@ -153,7 +153,7 @@ public final class TimeoutState
|
||||
*
|
||||
* @see #nanoCumulative()
|
||||
*/
|
||||
void pauseTimer()
|
||||
synchronized void pauseTimer()
|
||||
{
|
||||
// We set the cumulative time to difference between current time and "nominal start time".
|
||||
cumulativeElapsed = System.nanoTime() - cumulativeStart;
|
||||
@@ -163,7 +163,7 @@ public final class TimeoutState
|
||||
/**
|
||||
* Resets the cumulative time and resets the abort flags.
|
||||
*/
|
||||
void stopTimer()
|
||||
synchronized void stopTimer()
|
||||
{
|
||||
cumulativeElapsed = 0;
|
||||
paused = softAbort = hardAbort = false;
|
||||
|
@@ -366,8 +366,7 @@ public class FileSystem
|
||||
Reference<?> ref;
|
||||
while( (ref = m_openFileQueue.poll()) != null )
|
||||
{
|
||||
Closeable file = m_openFiles.remove( ref );
|
||||
if( file != null ) IoUtil.closeQuietly( file );
|
||||
IoUtil.closeQuietly( m_openFiles.remove( ref ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -445,24 +445,9 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
// We check our current pause/abort state every 128 instructions.
|
||||
if( (count = (count + 1) & 127) == 0 )
|
||||
{
|
||||
// If we've been hard aborted or closed then abort.
|
||||
if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE;
|
||||
|
||||
timeout.refresh();
|
||||
if( timeout.isPaused() )
|
||||
{
|
||||
// Preserve the current state
|
||||
isPaused = true;
|
||||
oldInHook = ds.inhook;
|
||||
oldFlags = di.flags;
|
||||
|
||||
// Suspend the state. This will probably throw, but we need to handle the case where it won't.
|
||||
di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED;
|
||||
LuaThread.suspend( ds.getLuaState() );
|
||||
resetPaused( ds, di );
|
||||
}
|
||||
|
||||
handleSoftAbort();
|
||||
if( timeout.isPaused() ) handlePause( ds, di );
|
||||
if( timeout.isSoftAborted() ) handleSoftAbort();
|
||||
}
|
||||
|
||||
super.onInstruction( ds, di, pc );
|
||||
@@ -471,13 +456,10 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
@Override
|
||||
public void poll() throws LuaError
|
||||
{
|
||||
// If we've been hard aborted or closed then abort.
|
||||
LuaState state = m_state;
|
||||
if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE;
|
||||
|
||||
timeout.refresh();
|
||||
if( timeout.isPaused() ) LuaThread.suspendBlocking( state );
|
||||
handleSoftAbort();
|
||||
if( timeout.isSoftAborted() ) handleSoftAbort();
|
||||
}
|
||||
|
||||
private void resetPaused( DebugState ds, DebugFrame di )
|
||||
@@ -491,11 +473,24 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
private void handleSoftAbort() throws LuaError
|
||||
{
|
||||
// If we already thrown our soft abort error then don't do it again.
|
||||
if( !timeout.isSoftAborted() || thrownSoftAbort ) return;
|
||||
if( thrownSoftAbort ) return;
|
||||
|
||||
thrownSoftAbort = true;
|
||||
throw new LuaError( TimeoutState.ABORT_MESSAGE );
|
||||
}
|
||||
|
||||
private void handlePause( DebugState ds, DebugFrame di ) throws LuaError, UnwindThrowable
|
||||
{
|
||||
// Preserve the current state
|
||||
isPaused = true;
|
||||
oldInHook = ds.inhook;
|
||||
oldFlags = di.flags;
|
||||
|
||||
// Suspend the state. This will probably throw, but we need to handle the case where it won't.
|
||||
di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED;
|
||||
LuaThread.suspend( ds.getLuaState() );
|
||||
resetPaused( ds, di );
|
||||
}
|
||||
}
|
||||
|
||||
private class CobaltLuaContext implements ILuaContext
|
||||
|
@@ -5,18 +5,20 @@
|
||||
*/
|
||||
package dan200.computercraft.core.terminal;
|
||||
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
public class Terminal
|
||||
{
|
||||
private static final String base16 = "0123456789abcdef";
|
||||
|
||||
private int m_cursorX;
|
||||
private int m_cursorY;
|
||||
private boolean m_cursorBlink;
|
||||
private int m_cursorColour;
|
||||
private int m_cursorBackgroundColour;
|
||||
private int m_cursorX = 0;
|
||||
private int m_cursorY = 0;
|
||||
private boolean m_cursorBlink = false;
|
||||
private int m_cursorColour = 0;
|
||||
private int m_cursorBackgroundColour = 15;
|
||||
|
||||
private int m_width;
|
||||
private int m_height;
|
||||
@@ -25,9 +27,9 @@ public class Terminal
|
||||
private TextBuffer[] m_textColour;
|
||||
private TextBuffer[] m_backgroundColour;
|
||||
|
||||
private final Palette m_palette;
|
||||
private final Palette m_palette = new Palette();
|
||||
|
||||
private boolean m_changed;
|
||||
private boolean m_changed = false;
|
||||
private final Runnable onChanged;
|
||||
|
||||
public Terminal( int width, int height )
|
||||
@@ -41,9 +43,6 @@ public class Terminal
|
||||
m_height = height;
|
||||
onChanged = changedCallback;
|
||||
|
||||
m_cursorColour = 0;
|
||||
m_cursorBackgroundColour = 15;
|
||||
|
||||
m_text = new TextBuffer[m_height];
|
||||
m_textColour = new TextBuffer[m_height];
|
||||
m_backgroundColour = new TextBuffer[m_height];
|
||||
@@ -53,14 +52,6 @@ public class Terminal
|
||||
m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width );
|
||||
m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width );
|
||||
}
|
||||
|
||||
m_cursorX = 0;
|
||||
m_cursorY = 0;
|
||||
m_cursorBlink = false;
|
||||
|
||||
m_changed = false;
|
||||
|
||||
m_palette = new Palette();
|
||||
}
|
||||
|
||||
public synchronized void reset()
|
||||
@@ -336,6 +327,62 @@ public class Terminal
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
public synchronized void write( PacketBuffer buffer )
|
||||
{
|
||||
buffer.writeInt( m_cursorX );
|
||||
buffer.writeInt( m_cursorY );
|
||||
buffer.writeBoolean( m_cursorBlink );
|
||||
buffer.writeByte( m_cursorBackgroundColour << 4 | m_cursorColour );
|
||||
|
||||
for( int y = 0; y < m_height; y++ )
|
||||
{
|
||||
TextBuffer text = m_text[y];
|
||||
TextBuffer textColour = m_textColour[y];
|
||||
TextBuffer backColour = m_backgroundColour[y];
|
||||
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
buffer.writeByte( text.charAt( x ) & 0xFF );
|
||||
buffer.writeByte( getColour(
|
||||
backColour.charAt( x ), Colour.Black ) << 4 |
|
||||
getColour( textColour.charAt( x ), Colour.White )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
m_palette.write( buffer );
|
||||
}
|
||||
|
||||
public synchronized void read( PacketBuffer buffer )
|
||||
{
|
||||
m_cursorX = buffer.readInt();
|
||||
m_cursorY = buffer.readInt();
|
||||
m_cursorBlink = buffer.readBoolean();
|
||||
|
||||
byte cursorColour = buffer.readByte();
|
||||
m_cursorBackgroundColour = (cursorColour >> 4) & 0xF;
|
||||
m_cursorColour = cursorColour & 0xF;
|
||||
|
||||
for( int y = 0; y < m_height; y++ )
|
||||
{
|
||||
TextBuffer text = m_text[y];
|
||||
TextBuffer textColour = m_textColour[y];
|
||||
TextBuffer backColour = m_backgroundColour[y];
|
||||
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
text.setChar( x, (char) (buffer.readByte() & 0xFF) );
|
||||
|
||||
byte colour = buffer.readByte();
|
||||
backColour.setChar( x, base16.charAt( (colour >> 4) & 0xF ) );
|
||||
textColour.setChar( x, base16.charAt( colour & 0xF ) );
|
||||
}
|
||||
}
|
||||
|
||||
m_palette.read( buffer );
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbt )
|
||||
{
|
||||
nbt.setInteger( "term_cursorX", m_cursorX );
|
||||
@@ -349,10 +396,8 @@ public class Terminal
|
||||
nbt.setString( "term_textColour_" + n, m_textColour[n].toString() );
|
||||
nbt.setString( "term_textBgColour_" + n, m_backgroundColour[n].toString() );
|
||||
}
|
||||
if( m_palette != null )
|
||||
{
|
||||
m_palette.writeToNBT( nbt );
|
||||
}
|
||||
|
||||
m_palette.writeToNBT( nbt );
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@@ -382,10 +427,15 @@ public class Terminal
|
||||
m_backgroundColour[n].write( nbt.getString( "term_textBgColour_" + n ) );
|
||||
}
|
||||
}
|
||||
if( m_palette != null )
|
||||
{
|
||||
m_palette.readFromNBT( nbt );
|
||||
}
|
||||
|
||||
m_palette.readFromNBT( nbt );
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public static int getColour( char c, Colour def )
|
||||
{
|
||||
if( c >= '0' && c <= '9' ) return c - '0';
|
||||
if( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
|
||||
return 15 - def.ordinal();
|
||||
}
|
||||
}
|
||||
|
@@ -47,6 +47,7 @@ public final class Config
|
||||
private static Property defaultComputerSettings;
|
||||
private static Property debugEnabled;
|
||||
private static Property logComputerErrors;
|
||||
private static Property commandRequireCreative;
|
||||
|
||||
private static Property computerThreads;
|
||||
private static Property maxMainGlobalTime;
|
||||
@@ -71,6 +72,7 @@ public final class Config
|
||||
private static Property modemHighAltitudeRangeDuringStorm;
|
||||
private static Property maxNotesPerTick;
|
||||
private static Property monitorRenderer;
|
||||
private static Property monitorBandwidth;
|
||||
|
||||
private static Property turtlesNeedFuel;
|
||||
private static Property turtleFuelLimit;
|
||||
@@ -119,10 +121,14 @@ public final class Config
|
||||
logComputerErrors.setComment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
|
||||
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." );
|
||||
|
||||
commandRequireCreative = config.get( CATEGORY_GENERAL, "command_require_creative", ComputerCraft.commandRequireCreative );
|
||||
commandRequireCreative.setComment( "Require players to be in creative mode and be opped in order to interact with command computers." +
|
||||
"This is the default behaviour for vanilla's Command blocks." );
|
||||
|
||||
setOrder(
|
||||
CATEGORY_GENERAL,
|
||||
computerSpaceLimit, floppySpaceLimit, maximumFilesOpen,
|
||||
disableLua51Features, defaultComputerSettings, debugEnabled, logComputerErrors
|
||||
disableLua51Features, defaultComputerSettings, debugEnabled, logComputerErrors, commandRequireCreative
|
||||
);
|
||||
}
|
||||
|
||||
@@ -271,10 +277,21 @@ public final class Config
|
||||
"monitors have performance issues, you may wish to experiment with alternative renderers." );
|
||||
monitorRenderer.setValidValues( MonitorRenderer.NAMES );
|
||||
|
||||
monitorBandwidth = config.get( CATEGORY_PERIPHERAL, "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth );
|
||||
monitorBandwidth.setComment( "The limit to how much monitor data can be sent *per tick*. Note:\n" +
|
||||
" - Bandwidth is measured before compression, so the data sent to the client is smaller.\n" +
|
||||
" - This ignores the number of players a packet is sent to. Updating a monitor for one player consumes " +
|
||||
"the same bandwidth limit as sending to 20.\n" +
|
||||
" - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40 monitors to be updated " +
|
||||
"in a single tick. \n" +
|
||||
"Set to 0 to disable." );
|
||||
monitorBandwidth.setValidValues( MonitorRenderer.NAMES );
|
||||
monitorBandwidth.setMinValue( 0 );
|
||||
|
||||
setOrder(
|
||||
CATEGORY_PERIPHERAL,
|
||||
commandBlockEnabled, modemRange, modemHighAltitudeRange, modemRangeDuringStorm, modemHighAltitudeRangeDuringStorm, maxNotesPerTick,
|
||||
monitorRenderer
|
||||
monitorRenderer, monitorBandwidth
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,6 +458,7 @@ public final class Config
|
||||
ComputerCraft.default_computer_settings = defaultComputerSettings.getString();
|
||||
ComputerCraft.debug_enable = debugEnabled.getBoolean();
|
||||
ComputerCraft.logPeripheralErrors = logComputerErrors.getBoolean();
|
||||
ComputerCraft.commandRequireCreative = commandRequireCreative.getBoolean();
|
||||
|
||||
// Execution
|
||||
ComputerCraft.computer_threads = computerThreads.getInt();
|
||||
@@ -468,6 +486,7 @@ public final class Config
|
||||
ComputerCraft.modem_rangeDuringStorm = Math.min( modemRangeDuringStorm.getInt(), MODEM_MAX_RANGE );
|
||||
ComputerCraft.modem_highAltitudeRangeDuringStorm = Math.min( modemHighAltitudeRangeDuringStorm.getInt(), MODEM_MAX_RANGE );
|
||||
ComputerCraft.monitorRenderer = MonitorRenderer.ofString( monitorRenderer.getString() );
|
||||
ComputerCraft.monitorBandwidth = Math.max( 0, monitorBandwidth.getLong() );
|
||||
|
||||
// Turtles
|
||||
ComputerCraft.turtlesNeedFuel = turtlesNeedFuel.getBoolean();
|
||||
|
@@ -117,4 +117,3 @@ public interface TableFormatter
|
||||
return rowId - table.getId();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
|
||||
public class ClientTerminal implements ITerminal
|
||||
{
|
||||
@@ -46,14 +46,13 @@ public class ClientTerminal implements ITerminal
|
||||
return m_colour;
|
||||
}
|
||||
|
||||
public void readDescription( NBTTagCompound nbt )
|
||||
public void read( TerminalState state )
|
||||
{
|
||||
m_colour = nbt.getBoolean( "colour" );
|
||||
if( nbt.hasKey( "terminal" ) )
|
||||
m_colour = state.colour;
|
||||
if( state.hasTerminal() )
|
||||
{
|
||||
NBTTagCompound terminal = nbt.getCompoundTag( "terminal" );
|
||||
resizeTerminal( terminal.getInteger( "term_width" ), terminal.getInteger( "term_height" ) );
|
||||
m_terminal.readFromNBT( terminal );
|
||||
resizeTerminal( state.width, state.height );
|
||||
state.apply( m_terminal );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -6,7 +6,7 @@
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -69,8 +69,6 @@ public class ServerTerminal implements ITerminal
|
||||
return m_terminalChangedLastFrame;
|
||||
}
|
||||
|
||||
// ITerminal implementation
|
||||
|
||||
@Override
|
||||
public Terminal getTerminal()
|
||||
{
|
||||
@@ -83,18 +81,8 @@ public class ServerTerminal implements ITerminal
|
||||
return m_colour;
|
||||
}
|
||||
|
||||
// Networking stuff
|
||||
|
||||
public void writeDescription( NBTTagCompound nbt )
|
||||
public TerminalState write()
|
||||
{
|
||||
nbt.setBoolean( "colour", m_colour );
|
||||
if( m_terminal != null )
|
||||
{
|
||||
NBTTagCompound terminal = new NBTTagCompound();
|
||||
terminal.setInteger( "term_width", m_terminal.getWidth() );
|
||||
terminal.setInteger( "term_height", m_terminal.getHeight() );
|
||||
m_terminal.writeToNBT( terminal );
|
||||
nbt.setTag( "terminal", terminal );
|
||||
}
|
||||
return new TerminalState( m_colour, m_terminal );
|
||||
}
|
||||
}
|
||||
|
@@ -128,7 +128,7 @@ public abstract class BlockComputerBase extends BlockDirectional
|
||||
if( tile instanceof TileComputerBase )
|
||||
{
|
||||
TileComputerBase computer = (TileComputerBase) tile;
|
||||
if( !player.capabilities.isCreativeMode || computer.getLabel() != null )
|
||||
if( !player.capabilities.isCreativeMode || computer.getLabel() != null || computer.getComputerID() != -1 )
|
||||
{
|
||||
spawnAsEntity( world, pos, getItem( computer ) );
|
||||
}
|
||||
|
@@ -45,4 +45,3 @@ public enum ComputerState implements IStringSerializable
|
||||
return ordinal < 0 || ordinal >= VALUES.length ? ComputerState.Off : VALUES[ordinal];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -167,6 +168,11 @@ public class TileCommandComputer extends TileComputer
|
||||
|
||||
@Override
|
||||
public boolean isUsable( EntityPlayer player, boolean ignoreRange )
|
||||
{
|
||||
return isUsable( player ) && super.isUsable( player, ignoreRange );
|
||||
}
|
||||
|
||||
public static boolean isUsable( EntityPlayer player )
|
||||
{
|
||||
MinecraftServer server = player.getServer();
|
||||
if( server == null || !server.isCommandBlockEnabled() )
|
||||
@@ -174,14 +180,12 @@ public class TileCommandComputer extends TileComputer
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
|
||||
return false;
|
||||
}
|
||||
else if( !player.canUseCommandBlock() )
|
||||
else if( ComputerCraft.commandRequireCreative ? !player.canUseCommandBlock() : !server.getPlayerList().canSendCommands( player.getGameProfile() ) )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.isUsable( player, ignoreRange );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -155,9 +155,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
|
||||
|
||||
protected IMessage createTerminalPacket()
|
||||
{
|
||||
NBTTagCompound tagCompound = new NBTTagCompound();
|
||||
writeDescription( tagCompound );
|
||||
return new ComputerTerminalClientMessage( getInstanceID(), tagCompound );
|
||||
return new ComputerTerminalClientMessage( getInstanceID(), write() );
|
||||
}
|
||||
|
||||
public void broadcastState( boolean force )
|
||||
|
@@ -6,11 +6,10 @@
|
||||
package dan200.computercraft.shared.computer.inventory;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
|
||||
import dan200.computercraft.shared.computer.core.*;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.Container;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -46,19 +45,9 @@ public class ContainerViewComputer extends Container implements IContainerComput
|
||||
}
|
||||
|
||||
// If we're a command computer then ensure we're in creative
|
||||
if( serverComputer.getFamily() == ComputerFamily.Command )
|
||||
if( serverComputer.getFamily() == ComputerFamily.Command && !TileCommandComputer.isUsable( player ) )
|
||||
{
|
||||
MinecraftServer server = player.getServer();
|
||||
if( server == null || !server.isCommandBlockEnabled() )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
|
||||
return false;
|
||||
}
|
||||
else if( !player.canUseCommandBlock() )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,9 +19,7 @@ public final class ComputerItemFactory
|
||||
@Nonnull
|
||||
public static ItemStack create( TileComputer tile )
|
||||
{
|
||||
String label = tile.getLabel();
|
||||
int id = label != null ? tile.getComputerID() : -1;
|
||||
return create( id, label, tile.getFamily() );
|
||||
return create( tile.getComputerID(), tile.getLabel(), tile.getFamily() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -39,7 +39,7 @@ public abstract class ItemComputerBase extends ItemBlock implements IComputerIte
|
||||
@Override
|
||||
public void addInformation( @Nonnull ItemStack stack, @Nullable World world, @Nonnull List<String> list, @Nonnull ITooltipFlag flag )
|
||||
{
|
||||
if( flag.isAdvanced() )
|
||||
if( flag.isAdvanced() || getLabel( stack ) == null )
|
||||
{
|
||||
int id = getComputerID( stack );
|
||||
if( id >= 0 ) list.add( StringUtil.translateFormatted( "gui.computercraft.tooltip.computer_id", id ) );
|
||||
|
@@ -80,7 +80,7 @@ public class ItemDiskLegacy extends Item implements IMedia, IColouredItem
|
||||
@Override
|
||||
public void addInformation( @Nonnull ItemStack stack, World world, List<String> list, ITooltipFlag flag )
|
||||
{
|
||||
if( flag.isAdvanced() )
|
||||
if( flag.isAdvanced() || getLabel( stack ) == null )
|
||||
{
|
||||
int id = getDiskID( stack );
|
||||
if( id >= 0 ) list.add( StringUtil.translateFormatted( "gui.computercraft.tooltip.disk_id", id ) );
|
||||
|
@@ -45,6 +45,7 @@ public final class NetworkHandler
|
||||
registerMainThread( 12, Side.CLIENT, ComputerDeletedClientMessage::new );
|
||||
registerMainThread( 13, Side.CLIENT, ComputerTerminalClientMessage::new );
|
||||
registerMainThread( 14, Side.CLIENT, PlayRecordClientMessage::new );
|
||||
registerMainThread( 15, Side.CLIENT, MonitorClientMessage::new );
|
||||
}
|
||||
|
||||
public static void sendToPlayer( EntityPlayer player, IMessage packet )
|
||||
@@ -67,6 +68,11 @@ public final class NetworkHandler
|
||||
network.sendToAllAround( packet, point );
|
||||
}
|
||||
|
||||
public static void sendToAllTracking( IMessage packet, NetworkRegistry.TargetPoint point )
|
||||
{
|
||||
network.sendToAllTracking( packet, point );
|
||||
}
|
||||
|
||||
/**
|
||||
* /**
|
||||
* Register packet, and a thread-unsafe handler for it.
|
||||
|
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
|
||||
|
||||
@@ -14,12 +12,12 @@ import javax.annotation.Nonnull;
|
||||
|
||||
public class ComputerTerminalClientMessage extends ComputerClientMessage
|
||||
{
|
||||
private NBTTagCompound tag;
|
||||
private TerminalState state;
|
||||
|
||||
public ComputerTerminalClientMessage( int instanceId, NBTTagCompound tag )
|
||||
public ComputerTerminalClientMessage( int instanceId, TerminalState state )
|
||||
{
|
||||
super( instanceId );
|
||||
this.tag = tag;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public ComputerTerminalClientMessage()
|
||||
@@ -30,19 +28,19 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
super.toBytes( buf );
|
||||
buf.writeCompoundTag( tag ); // TODO: Do we need to compress this?
|
||||
state.write( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
super.fromBytes( buf );
|
||||
tag = NBTUtil.readCompoundTag( buf );
|
||||
state = new TerminalState( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle( MessageContext context )
|
||||
{
|
||||
getComputer().readDescription( tag );
|
||||
getComputer().read( state );
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityPlayerSP;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class MonitorClientMessage implements NetworkMessage
|
||||
{
|
||||
private BlockPos pos;
|
||||
private TerminalState state;
|
||||
|
||||
public MonitorClientMessage( BlockPos pos, TerminalState state )
|
||||
{
|
||||
this.pos = pos;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public MonitorClientMessage()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
buf.writeBlockPos( pos );
|
||||
state.write( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
pos = buf.readBlockPos();
|
||||
state = new TerminalState( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle( MessageContext context )
|
||||
{
|
||||
EntityPlayerSP player = Minecraft.getMinecraft().player;
|
||||
if( player == null || player.world == null ) return;
|
||||
|
||||
TileEntity te = player.world.getTileEntity( pos );
|
||||
if( !(te instanceof TileMonitor) ) return;
|
||||
|
||||
((TileMonitor) te).read( state );
|
||||
}
|
||||
}
|
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.network.client;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.IoUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* A snapshot of a terminal's state.
|
||||
*
|
||||
* This is somewhat memory inefficient (we build a buffer, only to write it elsewhere), however it means we get a
|
||||
* complete and accurate description of a terminal, which avoids a lot of complexities with resizing terminals, dirty
|
||||
* states, etc...
|
||||
*/
|
||||
public class TerminalState
|
||||
{
|
||||
public final boolean colour;
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
|
||||
private final boolean compress;
|
||||
|
||||
@Nullable
|
||||
private final ByteBuf buffer;
|
||||
|
||||
private ByteBuf compressed;
|
||||
|
||||
public TerminalState( boolean colour, @Nullable Terminal terminal )
|
||||
{
|
||||
this( colour, terminal, true );
|
||||
}
|
||||
|
||||
public TerminalState( boolean colour, @Nullable Terminal terminal, boolean compress )
|
||||
{
|
||||
this.colour = colour;
|
||||
this.compress = compress;
|
||||
|
||||
if( terminal == null )
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = terminal.getWidth();
|
||||
this.height = terminal.getHeight();
|
||||
|
||||
ByteBuf buf = this.buffer = Unpooled.buffer();
|
||||
terminal.write( new PacketBuffer( buf ) );
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalState( PacketBuffer buf )
|
||||
{
|
||||
this.colour = buf.readBoolean();
|
||||
this.compress = buf.readBoolean();
|
||||
|
||||
if( buf.readBoolean() )
|
||||
{
|
||||
this.width = buf.readVarInt();
|
||||
this.height = buf.readVarInt();
|
||||
|
||||
int length = buf.readVarInt();
|
||||
this.buffer = readCompressed( buf, length, compress );
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void write( PacketBuffer buf )
|
||||
{
|
||||
buf.writeBoolean( colour );
|
||||
buf.writeBoolean( compress );
|
||||
|
||||
buf.writeBoolean( buffer != null );
|
||||
if( buffer != null )
|
||||
{
|
||||
buf.writeVarInt( width );
|
||||
buf.writeVarInt( height );
|
||||
|
||||
ByteBuf sendBuffer = getCompressed();
|
||||
buf.writeVarInt( sendBuffer.readableBytes() );
|
||||
buf.writeBytes( sendBuffer, sendBuffer.readerIndex(), sendBuffer.readableBytes() );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTerminal()
|
||||
{
|
||||
return buffer != null;
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return buffer == null ? 0 : buffer.readableBytes();
|
||||
}
|
||||
|
||||
public void apply( Terminal terminal )
|
||||
{
|
||||
if( buffer == null ) throw new NullPointerException( "buffer" );
|
||||
terminal.read( new PacketBuffer( buffer ) );
|
||||
}
|
||||
|
||||
private ByteBuf getCompressed()
|
||||
{
|
||||
if( buffer == null ) throw new NullPointerException( "buffer" );
|
||||
if( !compress ) return buffer;
|
||||
if( compressed != null ) return compressed;
|
||||
|
||||
ByteBuf compressed = Unpooled.directBuffer();
|
||||
OutputStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new GZIPOutputStream( new ByteBufOutputStream( compressed ) );
|
||||
stream.write( buffer.array(), buffer.arrayOffset(), buffer.readableBytes() );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new UncheckedIOException( e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
IoUtil.closeQuietly( stream );
|
||||
}
|
||||
|
||||
return this.compressed = compressed;
|
||||
}
|
||||
|
||||
private static ByteBuf readCompressed( ByteBuf buf, int length, boolean compress )
|
||||
{
|
||||
if( compress )
|
||||
{
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
InputStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new GZIPInputStream( new ByteBufInputStream( buf, length ) );
|
||||
byte[] swap = new byte[8192];
|
||||
while( true )
|
||||
{
|
||||
int bytes = stream.read( swap );
|
||||
if( bytes == -1 ) break;
|
||||
buffer.writeBytes( swap, 0, bytes );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new UncheckedIOException( e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
IoUtil.closeQuietly( stream );
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuf buffer = Unpooled.buffer( length );
|
||||
buf.readBytes( buffer, length );
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
@@ -444,4 +445,11 @@ public class TileCable extends TileGeneric implements IPeripheralTile
|
||||
IBlockState state = getBlockState();
|
||||
return BlockCable.getPeripheralType( state );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public AxisAlignedBB getRenderBoundingBox()
|
||||
{
|
||||
return Block.FULL_BLOCK_AABB.offset( getPos() );
|
||||
}
|
||||
}
|
||||
|
@@ -8,10 +8,16 @@ package dan200.computercraft.shared.peripheral.monitor;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.common.ClientTerminal;
|
||||
import net.minecraft.client.renderer.GLAllocation;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@@ -26,6 +32,8 @@ public final class ClientMonitor extends ClientTerminal
|
||||
public long lastRenderFrame = -1;
|
||||
public BlockPos lastRenderPos = null;
|
||||
|
||||
public int tboBuffer;
|
||||
public int tboTexture;
|
||||
public VertexBuffer buffer;
|
||||
public int displayList = 0;
|
||||
|
||||
@@ -43,7 +51,7 @@ public final class ClientMonitor extends ClientTerminal
|
||||
/**
|
||||
* Create the appropriate buffer if needed.
|
||||
*
|
||||
* @param renderer The renderer to use. This can be fetched from {@link #renderer()}.
|
||||
* @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.
|
||||
*/
|
||||
@@ -52,6 +60,26 @@ public final class ClientMonitor extends ClientTerminal
|
||||
{
|
||||
switch( renderer )
|
||||
{
|
||||
case TBO:
|
||||
{
|
||||
if( tboBuffer != 0 ) return false;
|
||||
|
||||
deleteBuffers();
|
||||
|
||||
tboBuffer = OpenGlHelper.glGenBuffers();
|
||||
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, tboBuffer );
|
||||
GL15.glBufferData( GL31.GL_TEXTURE_BUFFER, 0, GL15.GL_STATIC_DRAW );
|
||||
tboTexture = GlStateManager.generateTexture();
|
||||
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, tboTexture );
|
||||
GL31.glTexBuffer( GL31.GL_TEXTURE_BUFFER, GL30.GL_R8, tboBuffer );
|
||||
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||
|
||||
OpenGlHelper.glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||
|
||||
addMonitor();
|
||||
return true;
|
||||
}
|
||||
|
||||
case VBO:
|
||||
if( buffer != null ) return false;
|
||||
|
||||
@@ -59,6 +87,7 @@ public final class ClientMonitor extends ClientTerminal
|
||||
buffer = new VertexBuffer( FixedWidthFontRenderer.POSITION_COLOR_TEX );
|
||||
addMonitor();
|
||||
return true;
|
||||
|
||||
case DISPLAY_LIST:
|
||||
if( displayList != 0 ) return false;
|
||||
|
||||
@@ -82,6 +111,19 @@ public final class ClientMonitor extends ClientTerminal
|
||||
|
||||
private void deleteBuffers()
|
||||
{
|
||||
|
||||
if( tboBuffer != 0 )
|
||||
{
|
||||
OpenGlHelper.glDeleteBuffers( tboBuffer );
|
||||
tboBuffer = 0;
|
||||
}
|
||||
|
||||
if( tboTexture != 0 )
|
||||
{
|
||||
GlStateManager.deleteTexture( tboTexture );
|
||||
tboTexture = 0;
|
||||
}
|
||||
|
||||
if( buffer != null )
|
||||
{
|
||||
buffer.deleteGlBuffers();
|
||||
@@ -98,7 +140,7 @@ public final class ClientMonitor extends ClientTerminal
|
||||
@SideOnly( Side.CLIENT )
|
||||
public void destroy()
|
||||
{
|
||||
if( buffer != null || displayList != 0 )
|
||||
if( tboBuffer != 0 || buffer != null || displayList != 0 )
|
||||
{
|
||||
synchronized( allMonitors )
|
||||
{
|
||||
|
@@ -9,6 +9,7 @@ package dan200.computercraft.shared.peripheral.monitor;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import org.lwjgl.opengl.GLContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Locale;
|
||||
@@ -27,7 +28,14 @@ public enum MonitorRenderer
|
||||
BEST,
|
||||
|
||||
/**
|
||||
* Render using VBOs. This is the default when supported.
|
||||
* Render using texture buffer objects.
|
||||
*
|
||||
* @see org.lwjgl.opengl.GL31#glTexBuffer(int, int, int)
|
||||
*/
|
||||
TBO,
|
||||
|
||||
/**
|
||||
* Render using VBOs.
|
||||
*
|
||||
* @see net.minecraft.client.renderer.vertex.VertexBuffer
|
||||
*/
|
||||
@@ -84,6 +92,16 @@ public enum MonitorRenderer
|
||||
{
|
||||
case BEST:
|
||||
return best();
|
||||
case TBO:
|
||||
checkCapabilities();
|
||||
if( !textureBuffer )
|
||||
{
|
||||
ComputerCraft.log.warn( "Texture buffers are not supported on your graphics card. Falling back to default." );
|
||||
ComputerCraft.monitorRenderer = BEST;
|
||||
return best();
|
||||
}
|
||||
|
||||
return TBO;
|
||||
case VBO:
|
||||
if( !OpenGlHelper.vboSupported )
|
||||
{
|
||||
@@ -100,6 +118,20 @@ public enum MonitorRenderer
|
||||
|
||||
private static MonitorRenderer best()
|
||||
{
|
||||
return OpenGlHelper.vboSupported ? VBO : DISPLAY_LIST;
|
||||
checkCapabilities();
|
||||
if( textureBuffer ) return TBO;
|
||||
if( OpenGlHelper.vboSupported ) return VBO;
|
||||
return DISPLAY_LIST;
|
||||
}
|
||||
|
||||
private static boolean initialised = false;
|
||||
private static boolean textureBuffer = false;
|
||||
|
||||
private static void checkCapabilities()
|
||||
{
|
||||
if( initialised ) return;
|
||||
|
||||
textureBuffer = GLContext.getCapabilities().OpenGL31;
|
||||
initialised = true;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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 dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.MonitorClientMessage;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
import net.minecraft.server.management.PlayerChunkMapEntry;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraftforge.common.DimensionManager;
|
||||
import net.minecraftforge.event.world.ChunkWatchEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
import net.minecraftforge.fml.common.network.NetworkRegistry;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
|
||||
public final class MonitorWatcher
|
||||
{
|
||||
private static final Queue<TileMonitor> watching = new ArrayDeque<>();
|
||||
|
||||
private MonitorWatcher()
|
||||
{
|
||||
}
|
||||
|
||||
static void enqueue( TileMonitor monitor )
|
||||
{
|
||||
if( monitor.enqueued ) return;
|
||||
|
||||
monitor.enqueued = true;
|
||||
monitor.cached = null;
|
||||
watching.add( monitor );
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWatch( ChunkWatchEvent.Watch event )
|
||||
{
|
||||
Chunk chunk = event.getChunkInstance();
|
||||
if( chunk == null ) return;
|
||||
|
||||
for( TileEntity te : chunk.getTileEntityMap().values() )
|
||||
{
|
||||
// Find all origin monitors who are not already on the queue.
|
||||
if( !(te instanceof TileMonitor) ) continue;
|
||||
|
||||
TileMonitor monitor = (TileMonitor) te;
|
||||
ServerMonitor serverMonitor = getMonitor( monitor );
|
||||
if( serverMonitor == null || monitor.enqueued ) continue;
|
||||
|
||||
// We use the cached terminal state if available - this is guaranteed to
|
||||
TerminalState state = monitor.cached;
|
||||
if( state == null ) state = monitor.cached = serverMonitor.write();
|
||||
NetworkHandler.sendToPlayer( event.getPlayer(), new MonitorClientMessage( monitor.getPos(), state ) );
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onTick( TickEvent.ServerTickEvent event )
|
||||
{
|
||||
if( event.phase != TickEvent.Phase.END ) return;
|
||||
|
||||
long limit = ComputerCraft.monitorBandwidth;
|
||||
boolean obeyLimit = limit > 0;
|
||||
|
||||
TileMonitor tile;
|
||||
while( (!obeyLimit || limit > 0) && (tile = watching.poll()) != null )
|
||||
{
|
||||
tile.enqueued = false;
|
||||
ServerMonitor monitor = getMonitor( tile );
|
||||
if( monitor == null ) continue;
|
||||
|
||||
BlockPos pos = tile.getPos();
|
||||
World world = tile.getWorld();
|
||||
WorldServer serverWorld = world instanceof WorldServer ? (WorldServer) world : DimensionManager.getWorld( world.provider.getDimension() );
|
||||
PlayerChunkMapEntry entry = serverWorld.getPlayerChunkMap().getEntry( pos.getX() >> 4, pos.getZ() >> 4 );
|
||||
if( entry == null || entry.getWatchingPlayers().isEmpty() ) continue;
|
||||
|
||||
NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint( world.provider.getDimension(), pos.getX(), pos.getY(), pos.getZ(), 0 );
|
||||
TerminalState state = tile.cached = monitor.write();
|
||||
NetworkHandler.sendToAllTracking( new MonitorClientMessage( pos, state ), point );
|
||||
|
||||
limit -= state.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static ServerMonitor getMonitor( TileMonitor monitor )
|
||||
{
|
||||
return !monitor.isInvalid() && monitor.getXIndex() == 0 && monitor.getYIndex() == 0 ? monitor.getCachedServerMonitor() : null;
|
||||
}
|
||||
}
|
@@ -5,12 +5,14 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.monitor;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralTile;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.common.ServerTerminal;
|
||||
import dan200.computercraft.shared.common.TileGeneric;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
import dan200.computercraft.shared.peripheral.PeripheralType;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.common.ITilePeripheral;
|
||||
@@ -46,6 +48,10 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
|
||||
private boolean m_destroyed = false;
|
||||
private boolean visiting = false;
|
||||
|
||||
// MonitorWatcher state.
|
||||
boolean enqueued;
|
||||
TerminalState cached;
|
||||
|
||||
private int m_width = 1;
|
||||
private int m_height = 1;
|
||||
private int m_xIndex = 0;
|
||||
@@ -148,7 +154,7 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
|
||||
}
|
||||
}
|
||||
|
||||
if( m_serverMonitor.pollTerminalChanged() ) updateBlock();
|
||||
if( m_serverMonitor.pollTerminalChanged() ) MonitorWatcher.enqueue( this );
|
||||
}
|
||||
|
||||
// IPeripheralTile implementation
|
||||
@@ -239,11 +245,6 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
|
||||
nbt.setInteger( "width", m_width );
|
||||
nbt.setInteger( "height", m_height );
|
||||
nbt.setInteger( "monitorDir", m_dir );
|
||||
|
||||
if( m_xIndex == 0 && m_yIndex == 0 && m_serverMonitor != null )
|
||||
{
|
||||
m_serverMonitor.writeDescription( nbt );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,9 +274,8 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
|
||||
|
||||
if( m_xIndex == 0 && m_yIndex == 0 )
|
||||
{
|
||||
// If we're the origin terminal then read the description
|
||||
// If we're the origin terminal then create it.
|
||||
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( m_advanced, this );
|
||||
m_clientMonitor.readDescription( nbt );
|
||||
}
|
||||
|
||||
if( oldXIndex != m_xIndex || oldYIndex != m_yIndex ||
|
||||
@@ -286,6 +286,19 @@ public class TileMonitor extends TileGeneric implements ITilePeripheral, IPeriph
|
||||
updateBlock();
|
||||
}
|
||||
}
|
||||
|
||||
public final void read( TerminalState state )
|
||||
{
|
||||
if( m_xIndex != 0 || m_yIndex != 0 )
|
||||
{
|
||||
ComputerCraft.log.warn( "Receiving monitor state for non-origin terminal at {}", getPos() );
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( m_advanced, this );
|
||||
m_clientMonitor.read( state );
|
||||
}
|
||||
|
||||
// Sizing and placement stuff
|
||||
|
||||
public EnumFacing getDirection()
|
||||
|
@@ -148,4 +148,3 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -211,7 +211,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
@Override
|
||||
public void addInformation( @Nonnull ItemStack stack, World world, List<String> list, ITooltipFlag flag )
|
||||
{
|
||||
if( flag.isAdvanced() )
|
||||
if( flag.isAdvanced() || getLabel( stack ) == null )
|
||||
{
|
||||
int id = getComputerID( stack );
|
||||
if( id >= 0 ) list.add( StringUtil.translateFormatted( "gui.computercraft.tooltip.computer_id", id ) );
|
||||
|
@@ -88,4 +88,3 @@ public class ItemTurtleLegacy extends ItemTurtleBase
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
package dan200.computercraft.shared.turtle.items;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
@@ -22,18 +23,13 @@ public final class TurtleItemFactory
|
||||
@Nonnull
|
||||
public static ItemStack create( ITurtleTile turtle )
|
||||
{
|
||||
ITurtleUpgrade leftUpgrade = turtle.getAccess().getUpgrade( TurtleSide.Left );
|
||||
ITurtleUpgrade rightUpgrade = turtle.getAccess().getUpgrade( TurtleSide.Right );
|
||||
ITurtleAccess access = turtle.getAccess();
|
||||
|
||||
String label = turtle.getLabel();
|
||||
if( label == null )
|
||||
{
|
||||
return create( -1, null, turtle.getColour(), turtle.getFamily(), leftUpgrade, rightUpgrade, 0, turtle.getOverlay() );
|
||||
}
|
||||
|
||||
int id = turtle.getComputerID();
|
||||
int fuelLevel = turtle.getAccess().getFuelLevel();
|
||||
return create( id, label, turtle.getColour(), turtle.getFamily(), leftUpgrade, rightUpgrade, fuelLevel, turtle.getOverlay() );
|
||||
return create(
|
||||
turtle.getComputerID(), turtle.getLabel(), turtle.getColour(), turtle.getFamily(),
|
||||
access.getUpgrade( TurtleSide.Left ), access.getUpgrade( TurtleSide.Right ),
|
||||
access.getFuelLevel(), turtle.getOverlay()
|
||||
);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -13,11 +13,11 @@ import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDropsEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.eventhandler.EventPriority;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@@ -31,16 +31,16 @@ public final class DropConsumer
|
||||
|
||||
private static Function<ItemStack, ItemStack> dropConsumer;
|
||||
private static List<ItemStack> remainingDrops;
|
||||
private static WeakReference<World> dropWorld;
|
||||
private static World dropWorld;
|
||||
private static AxisAlignedBB dropBounds;
|
||||
private static WeakReference<Entity> dropEntity;
|
||||
private static Entity dropEntity;
|
||||
|
||||
public static void set( Entity entity, Function<ItemStack, ItemStack> consumer )
|
||||
{
|
||||
dropConsumer = consumer;
|
||||
remainingDrops = new ArrayList<>();
|
||||
dropEntity = new WeakReference<>( entity );
|
||||
dropWorld = new WeakReference<>( entity.world );
|
||||
dropEntity = entity;
|
||||
dropWorld = entity.world;
|
||||
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 );
|
||||
|
||||
entity.captureDrops = true;
|
||||
@@ -51,26 +51,12 @@ public final class DropConsumer
|
||||
dropConsumer = consumer;
|
||||
remainingDrops = new ArrayList<>( 2 );
|
||||
dropEntity = null;
|
||||
dropWorld = new WeakReference<>( world );
|
||||
dropWorld = world;
|
||||
dropBounds = new AxisAlignedBB( pos ).grow( 2, 2, 2 );
|
||||
}
|
||||
|
||||
public static List<ItemStack> clear()
|
||||
{
|
||||
if( dropEntity != null )
|
||||
{
|
||||
Entity entity = dropEntity.get();
|
||||
if( entity != null )
|
||||
{
|
||||
entity.captureDrops = false;
|
||||
if( entity.capturedDrops != null )
|
||||
{
|
||||
for( EntityItem entityItem : entity.capturedDrops ) handleDrops( entityItem.getItem() );
|
||||
entity.capturedDrops.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ItemStack> remainingStacks = remainingDrops;
|
||||
|
||||
dropConsumer = null;
|
||||
@@ -92,11 +78,20 @@ public final class DropConsumer
|
||||
public static void onEntitySpawn( EntityJoinWorldEvent event )
|
||||
{
|
||||
// Capture any nearby item spawns
|
||||
if( dropWorld != null && dropWorld.get() == event.getWorld() && event.getEntity() instanceof EntityItem
|
||||
if( dropWorld == event.getWorld() && event.getEntity() instanceof EntityItem
|
||||
&& dropBounds.contains( event.getEntity().getPositionVector() ) )
|
||||
{
|
||||
handleDrops( ((EntityItem) event.getEntity()).getItem() );
|
||||
event.setCanceled( true );
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLivingDrops( LivingDropsEvent drops )
|
||||
{
|
||||
if( dropEntity == null || drops.getEntity() != dropEntity ) return;
|
||||
|
||||
for( EntityItem drop : drops.getDrops() ) handleDrops( drop.getItem() );
|
||||
drops.setCanceled( true );
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -12,11 +13,11 @@ public final class IoUtil
|
||||
{
|
||||
private IoUtil() {}
|
||||
|
||||
public static void closeQuietly( Closeable closeable )
|
||||
public static void closeQuietly( @Nullable Closeable closeable )
|
||||
{
|
||||
try
|
||||
{
|
||||
closeable.close();
|
||||
if( closeable != null ) closeable.close();
|
||||
}
|
||||
catch( IOException ignored )
|
||||
{
|
||||
|
@@ -6,6 +6,7 @@
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
public class Palette
|
||||
{
|
||||
@@ -78,6 +79,22 @@ public class Palette
|
||||
};
|
||||
}
|
||||
|
||||
public void write( PacketBuffer buffer )
|
||||
{
|
||||
for( double[] colour : colours )
|
||||
{
|
||||
for( double channel : colour ) buffer.writeByte( (int) (channel * 0xFF) & 0xFF );
|
||||
}
|
||||
}
|
||||
|
||||
public void read( PacketBuffer buffer )
|
||||
{
|
||||
for( double[] colour : colours )
|
||||
{
|
||||
for( int i = 0; i < colour.length; i++ ) colour[i] = (buffer.readByte() & 0xFF) / 255.0;
|
||||
}
|
||||
}
|
||||
|
||||
public NBTTagCompound writeToNBT( NBTTagCompound nbt )
|
||||
{
|
||||
int[] rgb8 = new int[colours.length];
|
||||
|
@@ -6,4 +6,3 @@
|
||||
"modem=true,peripheral=true": { "model": "computercraft:wired_modem_full_on_peripheral" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -44,5 +44,5 @@ chat.computercraft.wired_modem.peripheral_disconnected=Perifer enhed "%s" koblet
|
||||
|
||||
# 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)
|
||||
gui.computercraft.tooltip.computer_id=Computer-ID: %s
|
||||
gui.computercraft.tooltip.disk_id=Disk-ID: %s
|
||||
|
@@ -148,8 +148,8 @@ tracking_field.computercraft.coroutines_dead.name=Koroutinen gelöscht
|
||||
|
||||
# Misc tooltips
|
||||
gui.computercraft.tooltip.copy=In die Zwischenablage kopieren
|
||||
gui.computercraft.tooltip.computer_id=(Computer ID: %s)
|
||||
gui.computercraft.tooltip.disk_id=(Disketten ID: %s)
|
||||
gui.computercraft.tooltip.computer_id=Computer ID: %s
|
||||
gui.computercraft.tooltip.disk_id=Disketten ID: %s
|
||||
|
||||
# Config options
|
||||
gui.computercraft:config.computer_space_limit=Speicherplatz von Computern (Bytes)
|
||||
|
@@ -148,8 +148,8 @@ tracking_field.computercraft.coroutines_dead.name=Coroutines disposed
|
||||
|
||||
# Misc tooltips
|
||||
gui.computercraft.tooltip.copy=Copy to clipboard
|
||||
gui.computercraft.tooltip.computer_id=(Computer ID: %s)
|
||||
gui.computercraft.tooltip.disk_id=(Disk ID: %s)
|
||||
gui.computercraft.tooltip.computer_id=Computer ID: %s
|
||||
gui.computercraft.tooltip.disk_id=Disk ID: %s
|
||||
|
||||
# Config options
|
||||
gui.computercraft:config.computer_space_limit=Computer space limit (bytes)
|
||||
@@ -159,6 +159,7 @@ gui.computercraft:config.disable_lua51_features=Disable Lua 5.1 features
|
||||
gui.computercraft:config.default_computer_settings=Default Computer settings
|
||||
gui.computercraft:config.debug_enabled=Enable debug library
|
||||
gui.computercraft:config.log_computer_errors=Log computer errors
|
||||
gui.computercraft:config.command_require_creative=Command computers require creative
|
||||
|
||||
gui.computercraft:config.execution=Execution
|
||||
gui.computercraft:config.execution.computer_threads=Computer threads
|
||||
@@ -187,8 +188,10 @@ gui.computercraft:config.peripheral.modem_high_altitude_range_during_storm=Modem
|
||||
gui.computercraft:config.peripheral.max_notes_per_tick=Maximum notes that a computer can play at once
|
||||
gui.computercraft:config.peripheral.monitor_renderer=Monitor renderer
|
||||
gui.computercraft:config.peripheral.monitor_renderer.best=Best
|
||||
gui.computercraft:config.peripheral.monitor_renderer.tbo=Texture Buffers
|
||||
gui.computercraft:config.peripheral.monitor_renderer.vbo=Vertex Buffers
|
||||
gui.computercraft:config.peripheral.monitor_renderer.display_list=Display Lists
|
||||
gui.computercraft:config.peripheral.monitor_bandwidth=Monitor bandwidth
|
||||
|
||||
gui.computercraft:config.turtle=Turtles
|
||||
gui.computercraft:config.turtle.need_fuel=Enable fuel
|
||||
|
@@ -148,8 +148,8 @@ 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)
|
||||
gui.computercraft.tooltip.computer_id=컴퓨터 ID: %s
|
||||
gui.computercraft.tooltip.disk_id=디스크 ID: %s
|
||||
|
||||
# Config options
|
||||
gui.computercraft:config.computer_space_limit=컴퓨터 공간 제한 (바이트)
|
||||
|
@@ -148,8 +148,8 @@ 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)
|
||||
gui.computercraft.tooltip.computer_id=计算机ID: %s
|
||||
gui.computercraft.tooltip.disk_id=磁盘ID: %s
|
||||
|
||||
# Config options
|
||||
gui.computercraft:config.computer_space_limit=计算机空间限制(字节)
|
||||
|
@@ -797,6 +797,12 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
|
||||
return tEmpty
|
||||
end
|
||||
|
||||
function fs.isDriveRoot(sPath)
|
||||
expect(1, sPath, "string")
|
||||
-- Force the root directory to be a mount.
|
||||
return fs.getDir(sPath) == ".." or fs.getDrive(sPath) ~= fs.getDrive(fs.getDir(sPath))
|
||||
end
|
||||
|
||||
-- Load APIs
|
||||
local bAPIError = false
|
||||
local tApis = fs.list("rom/apis")
|
||||
@@ -932,6 +938,23 @@ settings.define("motd.path", {
|
||||
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]],
|
||||
type = "string",
|
||||
})
|
||||
|
||||
settings.define("lua.warn_against_use_of_local", {
|
||||
default = true,
|
||||
description = [[Print a message when input in the Lua REPL starts with the word 'local'. Local variables defined in the Lua REPL are be inaccessable on the next input.]],
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("lua.function_args", {
|
||||
default = true,
|
||||
description = "Show function arguments when printing functions.",
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("lua.function_source", {
|
||||
default = false,
|
||||
description = "Show where a function was defined when printing functions.",
|
||||
type = "boolean",
|
||||
})
|
||||
|
||||
if term.isColour() then
|
||||
settings.define("bios.use_multishell", {
|
||||
default = true,
|
||||
|
@@ -12,6 +12,9 @@
|
||||
-- [mc]: https://minecraft.gamepedia.com/Commands
|
||||
--
|
||||
-- @module commands
|
||||
-- @usage Set the block above this computer to stone:
|
||||
--
|
||||
-- commands.setblock("~", "~1", "~", "minecraft:stone")
|
||||
|
||||
if not commands then
|
||||
error("Cannot load command API on normal computer", 2)
|
||||
@@ -65,4 +68,13 @@ for _, sCommandName in ipairs(tCommands) do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- A table containing asynchronous wrappers for all commands.
|
||||
--
|
||||
-- As with @{commands.execAsync}, this returns the "task id" of the enqueued
|
||||
-- command.
|
||||
-- @see execAsync
|
||||
-- @usage Asynchronously sets the block above the computer to stone.
|
||||
--
|
||||
-- commands.async.setblock("~", "~1", "~", "minecraft:stone")
|
||||
env.async = tAsync
|
||||
|
@@ -75,7 +75,10 @@ handleMetatable = {
|
||||
if not handle.read then return nil, "file is not readable" end
|
||||
|
||||
local args = table.pack(...)
|
||||
return function() return checkResult(self, self:read(table.unpack(args, 1, args.n))) end
|
||||
return function()
|
||||
if self._closed then error("file is already closed", 2) end
|
||||
return checkResult(self, self:read(table.unpack(args, 1, args.n)))
|
||||
end
|
||||
end,
|
||||
|
||||
read = function(self, ...)
|
||||
@@ -259,12 +262,13 @@ end
|
||||
-- instead. In this case, the handle is not used.
|
||||
--
|
||||
-- @tparam[opt] string filename The name of the file to extract lines from
|
||||
-- @param ... The argument to pass to @{Handle:read} for each line.
|
||||
-- @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)
|
||||
function lines(filename, ...)
|
||||
expect(1, filename, "string", "nil")
|
||||
if filename then
|
||||
local ok, err = open(filename, "rb")
|
||||
@@ -273,9 +277,9 @@ function lines(filename)
|
||||
-- We set this magic flag to mark this file as being opened by io.lines and so should be
|
||||
-- closed automatically
|
||||
ok._autoclose = true
|
||||
return ok:lines()
|
||||
return ok:lines(...)
|
||||
else
|
||||
return currentInput:lines()
|
||||
return currentInput:lines(...)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -313,7 +317,7 @@ end
|
||||
-- @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")
|
||||
local res, err = open(file, "wb")
|
||||
if not res then error(err, 2) end
|
||||
currentOutput = res
|
||||
elseif type_of(file) == "table" and getmetatable(file) == handleMetatable then
|
||||
|
@@ -64,24 +64,33 @@ function isPresent(name)
|
||||
return false
|
||||
end
|
||||
|
||||
--- Get the type of the peripheral with the given name.
|
||||
--- Get the type of a wrapped peripheral, or a peripheral with the given name.
|
||||
--
|
||||
-- @tparam string name The name of the peripheral to find.
|
||||
-- @tparam string|table peripheral The name of the peripheral to find, or a
|
||||
-- wrapped peripheral instance.
|
||||
-- @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)
|
||||
function getType(peripheral)
|
||||
expect(1, peripheral, "string", "table")
|
||||
if type(peripheral) == "string" then -- Peripheral name passed
|
||||
if native.isPresent(peripheral) then
|
||||
return native.getType(peripheral)
|
||||
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", peripheral)
|
||||
then
|
||||
return native.call(side, "getTypeRemote", peripheral)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
else
|
||||
local mt = getmetatable(peripheral)
|
||||
if not mt or mt.__name ~= "peripheral" or type(mt.type) ~= "string" then
|
||||
error("bad argument #1 (table is not a peripheral)", 2)
|
||||
end
|
||||
return mt.type
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get all available methods for the peripheral with the given name.
|
||||
@@ -105,6 +114,19 @@ function getMethods(name)
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the name of a peripheral wrapped with @{peripheral.wrap}.
|
||||
--
|
||||
-- @tparam table peripheral The peripheral to get the name of.
|
||||
-- @treturn string The name of the given peripheral.
|
||||
function getName(peripheral)
|
||||
expect(1, peripheral, "table")
|
||||
local mt = getmetatable(peripheral)
|
||||
if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then
|
||||
error("bad argument #1 (table is not a peripheral)", 2)
|
||||
end
|
||||
return mt.name
|
||||
end
|
||||
|
||||
--- Call a method on the peripheral with the given name.
|
||||
--
|
||||
-- @tparam string name The name of the peripheral to invoke the method on.
|
||||
@@ -148,7 +170,11 @@ function wrap(name)
|
||||
return nil
|
||||
end
|
||||
|
||||
local result = {}
|
||||
local result = setmetatable({}, {
|
||||
__name = "peripheral",
|
||||
name = name,
|
||||
type = peripheral.getType(name),
|
||||
})
|
||||
for _, method in ipairs(methods) do
|
||||
result[method] = function(...)
|
||||
return peripheral.call(name, method, ...)
|
||||
|
@@ -29,7 +29,7 @@ local tReceivedMessageTimeouts = {}
|
||||
local tHostnames = {}
|
||||
|
||||
--- Opens a modem with the given @{peripheral} name, allowing it to send and
|
||||
--- receive messages over rednet.
|
||||
-- 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
|
||||
@@ -246,7 +246,7 @@ function host(sProtocol, sHostname)
|
||||
end
|
||||
|
||||
--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
|
||||
--- respond to @{rednet.lookup} requests.
|
||||
-- respond to @{rednet.lookup} requests.
|
||||
--
|
||||
-- @tparam string sProtocol The protocol to unregister your self from.
|
||||
function unhost(sProtocol)
|
||||
@@ -334,7 +334,11 @@ end
|
||||
|
||||
local bRunning = false
|
||||
|
||||
--- @local
|
||||
--- Listen for modem messages and converts them into rednet messages, which may
|
||||
-- then be @{receive|received}.
|
||||
--
|
||||
-- This is automatically started in the background on computer startup, and
|
||||
-- should not be called manually.
|
||||
function run()
|
||||
if bRunning then
|
||||
error("rednet is already running", 2)
|
||||
|
@@ -205,9 +205,9 @@ function load(sPath)
|
||||
end
|
||||
|
||||
for k, v in pairs(tFile) do
|
||||
local ty_v = type(k)
|
||||
local ty_v = type(v)
|
||||
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]
|
||||
local opt = details[k]
|
||||
if not opt or not opt.type or ty_v == opt.type then
|
||||
set_value(k, v)
|
||||
end
|
||||
|
@@ -5,7 +5,12 @@
|
||||
if not turtle then
|
||||
error("Cannot load turtle API on computer", 2)
|
||||
end
|
||||
native = turtle.native or turtle --- @local
|
||||
|
||||
--- The builtin turtle API, without any generated helper functions.
|
||||
--
|
||||
-- Generally you should not need to use this table - it only exists for
|
||||
-- backwards compatibility reasons.
|
||||
native = turtle.native or turtle
|
||||
|
||||
local function addCraftMethod(object)
|
||||
if peripheral.getType("left") == "workbench" then
|
||||
|
@@ -1,3 +1,36 @@
|
||||
# New features in CC: Tweaked 1.89.2
|
||||
|
||||
* Fix dupe bug when killing an entity with a turtle.
|
||||
|
||||
# New features in CC: Tweaked 1.89.1
|
||||
|
||||
* Fix crashes when rendering monitors of varying sizes.
|
||||
|
||||
# New features in CC: Tweaked 1.89.0
|
||||
|
||||
* Compress monitor data, reducing network traffic by a significant amount.
|
||||
* Allow limiting the bandwidth monitor updates use.
|
||||
* Several optimisations to monitor rendering (@Lignum)
|
||||
|
||||
And several bug fixes:
|
||||
* Fix settings.load failing on defined settings.
|
||||
|
||||
# New features in CC: Tweaked 1.88.0
|
||||
|
||||
* Computers and turtles now preserve their ID when broken.
|
||||
* Add `peripheral.getName` - returns the name of a wrapped peripheral.
|
||||
* Reduce network overhead of monitors and terminals.
|
||||
* Add a TBO backend for monitors, with a significant performance boost.
|
||||
* The Lua REPL warns when declaring locals (lupus590, exerro)
|
||||
* Add config to allow using command computers in survival.
|
||||
* Add fs.isDriveRoot - checks if a path is the root of a drive.
|
||||
* `cc.pretty` can now display a function's arguments and where it was defined. The Lua REPL will show arguments by default.
|
||||
* Move the shell's `require`/`package` implementation to a separate `cc.require` module.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix io.lines not accepting arguments.
|
||||
* Fix settings.load using an unknown global (MCJack123).
|
||||
|
||||
# New features in CC: Tweaked 1.87.0
|
||||
|
||||
* Add documentation to many Lua functions. This is published online at https://tweaked.cc/.
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
ComputerCraft was created by Daniel "dan200" Ratcliffe, with additional code by Aaron "Cloudy" Mills.
|
||||
Thanks to nitrogenfingers, GopherATL and RamiLego for program contributions.
|
||||
Thanks to Mojang, the Forge team, and the MCP team.
|
||||
|
@@ -3,6 +3,7 @@ The peripheral API is for interacting with external peripheral devices. Type "he
|
||||
Functions in the peripheral API:
|
||||
peripheral.getNames()
|
||||
peripheral.isPresent( name )
|
||||
peripheral.getName( peripheral )
|
||||
peripheral.getType( name )
|
||||
peripheral.getMethods( name )
|
||||
peripheral.call( name, methodName, param1, param2, etc )
|
||||
|
@@ -1,31 +1,5 @@
|
||||
New features in CC: Tweaked 1.87.0
|
||||
New features in CC: Tweaked 1.89.2
|
||||
|
||||
* Add documentation to many Lua functions. This is published online at https://tweaked.cc/.
|
||||
* Replace to pretty-printer in the Lua REPL. It now supports displaying functions and recursive tables. This printer is may be used within your own code through the `cc.pretty` module.
|
||||
* Add `fs.getCapacity`. A complement to `fs.getFreeSpace`, this returns the capacity of the supplied drive.
|
||||
* Add `fs.getAttributes`. This provides file size and type, as well as creation and modification time.
|
||||
* Update Cobalt version. This backports several features from Lua 5.2 and 5.3:
|
||||
- The `__len` metamethod may now be used by tables.
|
||||
- Add `\z`, hexadecimal (`\x00`) and unicode (`\u0000`) string escape codes.
|
||||
- Add `utf8` lib.
|
||||
- Mirror Lua's behaviour of tail calls more closely. Native functions are no longer tail called, and tail calls are displayed in the stack trace.
|
||||
- `table.unpack` now uses `__len` and `__index` metamethods.
|
||||
- Parser errors now include the token where the error occured.
|
||||
* Add `textutils.unserializeJSON`. This can be used to decode standard JSON and stringified-NBT.
|
||||
* Switch the monitor renderer to use VBOs, providing a _significant_ performance boost in some cases. You can switch back to the display list renderer via the config.
|
||||
* The `settings` API now allows "defining" settings. This allows settings to specify a default value and description.
|
||||
* Enable the motd on non-pocket computers.
|
||||
* Allow using the menu with the mouse in edit and paint (JakobDev).
|
||||
* Add Danish and Korean translations (ChristianLW, mindy15963)
|
||||
* Fire `mouse_up` events in the monitor program.
|
||||
* Allow specifying a timeout to `websocket.receive`.
|
||||
* Increase the maximimum limit for websocket messages.
|
||||
* Optimise capacity checking of computer/disk folders.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix turtle texture being incorrectly oriented (magiczocker10).
|
||||
* Prevent copying folders into themselves.
|
||||
* Fix race condition within ID assignment.
|
||||
* Normalise file paths within shell.setDir (JakobDev)
|
||||
* Fix dupe bug when killing an entity with a turtle.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@@ -19,8 +19,12 @@
|
||||
-- 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
|
||||
local expect = require "cc.expect"
|
||||
local expect, field = expect.expect, expect.field
|
||||
|
||||
local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring
|
||||
local debug_info = type(debug) == "table" and type(debug.getinfo) == "function" and debug.getinfo
|
||||
local debug_local = type(debug) == "table" and type(debug.getlocal) == "function" and debug.getlocal
|
||||
|
||||
--- @{table.insert} alternative, but with the length stored inline.
|
||||
local function append(out, value)
|
||||
@@ -343,13 +347,38 @@ local function key_compare(a, b)
|
||||
return false
|
||||
end
|
||||
|
||||
local function pretty_impl(obj, tracking)
|
||||
local function show_function(fn, options)
|
||||
local info = debug_info and debug_info(fn, "Su")
|
||||
|
||||
-- Include function source position if available
|
||||
local name
|
||||
if options.function_source and info and info.short_src and info.linedefined and info.linedefined >= 1 then
|
||||
name = "function<" .. info.short_src .. ":" .. info.linedefined .. ">"
|
||||
else
|
||||
name = tostring(fn)
|
||||
end
|
||||
|
||||
-- Include arguments if a Lua function and if available. Lua will report "C"
|
||||
-- functions as variadic.
|
||||
if options.function_args and info and info.what == "Lua" and info.nparams and debug_local then
|
||||
local args = {}
|
||||
for i = 1, info.nparams do args[i] = debug_local(fn, i) or "?" end
|
||||
if info.isvararg then args[#args + 1] = "..." end
|
||||
name = name .. "(" .. table.concat(args, ", ") .. ")"
|
||||
end
|
||||
|
||||
return name
|
||||
end
|
||||
|
||||
local function pretty_impl(obj, options, 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 == "function" then
|
||||
return text(show_function(obj, options), colours.lightGrey)
|
||||
elseif obj_type ~= "table" or tracking[obj] then
|
||||
return text(tostring(obj), colours.lightGrey)
|
||||
elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then
|
||||
@@ -371,20 +400,20 @@ local function pretty_impl(obj, tracking)
|
||||
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))
|
||||
append(doc, pretty_impl(v, options, 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))
|
||||
append(doc, pretty_impl(v, options, tracking))
|
||||
else
|
||||
append(doc, obracket)
|
||||
append(doc, pretty_impl(k, tracking))
|
||||
append(doc, pretty_impl(k, options, tracking))
|
||||
append(doc, cbracket)
|
||||
append(doc, pretty_impl(v, tracking))
|
||||
append(doc, pretty_impl(v, options, tracking))
|
||||
end
|
||||
end
|
||||
|
||||
tracking[obj] = nil
|
||||
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, n))), space_line, cbrace))
|
||||
return group(concat(obrace, nest(2, concat(table.unpack(doc, 1, doc.n))), space_line, cbrace))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -393,12 +422,24 @@ end
|
||||
-- This can then be rendered with @{write} or @{print}.
|
||||
--
|
||||
-- @param obj The object to pretty-print.
|
||||
-- @tparam[opt] { function_args = boolean, function_source = boolean } options
|
||||
-- Controls how various properties are displayed.
|
||||
-- - `function_args`: Show the arguments to a function if known (`false` by default).
|
||||
-- - `function_source`: Show where the function was defined, instead of
|
||||
-- `function: xxxxxxxx` (`false` by default).
|
||||
-- @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, {})
|
||||
local function pretty(obj, options)
|
||||
expect(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
|
||||
local actual_options = {
|
||||
function_source = field(options, "function_source", "boolean", "nil") or false,
|
||||
function_args = field(options, "function_args", "boolean", "nil") or false,
|
||||
}
|
||||
return pretty_impl(obj, actual_options, {})
|
||||
end
|
||||
|
||||
return {
|
||||
|
@@ -0,0 +1,121 @@
|
||||
--- This provides a pure Lua implementation of the builtin @{require} function
|
||||
-- and @{package} library.
|
||||
--
|
||||
-- Generally you do not need to use this module - it is injected into the
|
||||
-- every program's environment. However, it may be useful when building a
|
||||
-- custom shell or when running programs yourself.
|
||||
--
|
||||
-- @module cc.require
|
||||
-- @usage Construct the package and require function, and insert them into a
|
||||
-- custom environment.
|
||||
--
|
||||
-- local env = setmetatable({}, { __index = _ENV })
|
||||
-- local r = require "cc.require"
|
||||
-- env.require, env.package = r.make(env, "/")
|
||||
|
||||
local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")
|
||||
local expect = expect.expect
|
||||
|
||||
local function preload(package)
|
||||
return function(name)
|
||||
if package.preload[name] then
|
||||
return package.preload[name]
|
||||
else
|
||||
return nil, "no field package.preload['" .. name .. "']"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function from_file(package, env, dir)
|
||||
return function(name)
|
||||
local fname = string.gsub(name, "%.", "/")
|
||||
local sError = ""
|
||||
for pattern in string.gmatch(package.path, "[^;]+") do
|
||||
local sPath = string.gsub(pattern, "%?", fname)
|
||||
if sPath:sub(1, 1) ~= "/" then
|
||||
sPath = fs.combine(dir, sPath)
|
||||
end
|
||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||
local fnFile, sError = loadfile(sPath, nil, env)
|
||||
if fnFile then
|
||||
return fnFile, sPath
|
||||
else
|
||||
return nil, sError
|
||||
end
|
||||
else
|
||||
if #sError > 0 then
|
||||
sError = sError .. "\n "
|
||||
end
|
||||
sError = sError .. "no file '" .. sPath .. "'"
|
||||
end
|
||||
end
|
||||
return nil, sError
|
||||
end
|
||||
end
|
||||
|
||||
local function make_require(package)
|
||||
local sentinel = {}
|
||||
return function(name)
|
||||
expect(1, name, "string")
|
||||
|
||||
if package.loaded[name] == sentinel then
|
||||
error("loop or previous error loading module '" .. name .. "'", 0)
|
||||
end
|
||||
|
||||
if package.loaded[name] then
|
||||
return package.loaded[name]
|
||||
end
|
||||
|
||||
local sError = "module '" .. name .. "' not found:"
|
||||
for _, searcher in ipairs(package.loaders) do
|
||||
local loader = table.pack(searcher(name))
|
||||
if loader[1] then
|
||||
package.loaded[name] = sentinel
|
||||
local result = loader[1](name, table.unpack(loader, 2, loader.n))
|
||||
if result == nil then result = true end
|
||||
|
||||
package.loaded[name] = result
|
||||
return result
|
||||
else
|
||||
sError = sError .. "\n " .. loader[2]
|
||||
end
|
||||
end
|
||||
error(sError, 2)
|
||||
end
|
||||
end
|
||||
|
||||
--- Build an implementation of Lua's @{package} library, and a @{require}
|
||||
-- function to load modules within it.
|
||||
--
|
||||
-- @tparam table env The environment to load packages into.
|
||||
-- @tparam string dir The directory that relative packages are loaded from.
|
||||
-- @treturn function The new @{require} function.
|
||||
-- @treturn table The new @{package} library.
|
||||
local function make_package(env, dir)
|
||||
expect(1, env, "table")
|
||||
expect(2, dir, "string")
|
||||
|
||||
local package = {}
|
||||
package.loaded = {
|
||||
_G = _G,
|
||||
bit32 = bit32,
|
||||
coroutine = coroutine,
|
||||
math = math,
|
||||
package = package,
|
||||
string = string,
|
||||
table = table,
|
||||
}
|
||||
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
|
||||
if turtle then
|
||||
package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
|
||||
elseif commands then
|
||||
package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
|
||||
end
|
||||
package.config = "/\n;\n?\n!\n-"
|
||||
package.preload = {}
|
||||
package.loaders = { preload(package), from_file(package, env, dir) }
|
||||
|
||||
return make_require(package), package
|
||||
end
|
||||
|
||||
return { make = make_package }
|
@@ -141,12 +141,12 @@ return {
|
||||
program = program,
|
||||
|
||||
-- Re-export various other functions
|
||||
help = wrap(help.completeTopic),
|
||||
choice = wrap(completion.choice),
|
||||
peripheral = wrap(completion.peripheral),
|
||||
side = wrap(completion.side),
|
||||
setting = wrap(completion.setting),
|
||||
command = wrap(completion.command),
|
||||
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function.
|
||||
choice = wrap(completion.choice), --- Wraps @{cc.completion.choice} as a @{build} compatible function.
|
||||
peripheral = wrap(completion.peripheral), --- Wraps @{cc.completion.peripheral} as a @{build} compatible function.
|
||||
side = wrap(completion.side), --- Wraps @{cc.completion.side} as a @{build} compatible function.
|
||||
setting = wrap(completion.setting), --- Wraps @{cc.completion.setting} as a @{build} compatible function.
|
||||
command = wrap(completion.command), --- Wraps @{cc.completion.command} as a @{build} compatible function.
|
||||
|
||||
build = build,
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
if not shell.openTab then
|
||||
printError("Requires multishell")
|
||||
return
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
if not shell.openTab then
|
||||
printError("Requires multishell")
|
||||
return
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 2 then
|
||||
print("Usage: alias <alias> <program>")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tApis = {}
|
||||
for k, v in pairs(_G) do
|
||||
if type(k) == "string" and type(v) == "table" and k ~= "_G" then
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs < 1 then
|
||||
print("Usage: cd <path>")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
if not commands then
|
||||
printError("Requires a Command Computer.")
|
||||
return
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
if not commands then
|
||||
printError("Requires a Command Computer.")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs < 2 then
|
||||
print("Usage: cp <source> <destination>")
|
||||
|
@@ -9,9 +9,18 @@ for i = 1, args.n do
|
||||
local files = fs.find(shell.resolve(args[i]))
|
||||
if #files > 0 then
|
||||
for _, file in ipairs(files) do
|
||||
local ok, err = pcall(fs.delete, file)
|
||||
if not ok then
|
||||
printError((err:gsub("^pcall: ", "")))
|
||||
if fs.isReadOnly(file) then
|
||||
printError("Cannot delete read-only file /" .. file)
|
||||
elseif fs.isDriveRoot(file) then
|
||||
printError("Cannot delete mount /" .. file)
|
||||
if fs.isDir(file) then
|
||||
print("To delete its contents run rm /" .. fs.combine(file, "*"))
|
||||
end
|
||||
else
|
||||
local ok, err = pcall(fs.delete, file)
|
||||
if not ok then
|
||||
printError((err:gsub("^pcall: ", "")))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
-- Get arguments
|
||||
local tArgs = { ... }
|
||||
if #tArgs == 0 then
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tBiomes = {
|
||||
"in a forest",
|
||||
"in a pine forest",
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
-- Display the start screen
|
||||
local w, h = term.getSize()
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local function printUsage()
|
||||
print("Usages:")
|
||||
print("gps host")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local function printUsage()
|
||||
print("Usages:")
|
||||
print("pastebin put <filename>")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local function printUsage()
|
||||
print("Usage:")
|
||||
print("wget <url> [filename]")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local sDrive = nil
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local function printUsage()
|
||||
print("Usages:")
|
||||
print("label get")
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
|
||||
-- Get all the files in the directory
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
print("This is an interactive Lua prompt.")
|
||||
@@ -67,6 +66,13 @@ while bRunning do
|
||||
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
||||
table.insert(tCommandHistory, s)
|
||||
end
|
||||
if settings.get("lua.warn_against_use_of_local") and s:match("^%s*local%s+") then
|
||||
if term.isColour() then
|
||||
term.setTextColour(colours.yellow)
|
||||
end
|
||||
print("To access local variables in later inputs, remove the local keyword.")
|
||||
term.setTextColour(colours.white)
|
||||
end
|
||||
|
||||
local nForcePrint = 0
|
||||
local func, e = load(s, "=lua", "t", tEnv)
|
||||
@@ -89,7 +95,10 @@ while bRunning do
|
||||
local n = 1
|
||||
while n < tResults.n or n <= nForcePrint do
|
||||
local value = tResults[n + 1]
|
||||
local ok, serialised = pcall(pretty.pretty, value)
|
||||
local ok, serialised = pcall(pretty.pretty, value, {
|
||||
function_args = settings.get("lua.function_args"),
|
||||
function_source = settings.get("lua.function_source"),
|
||||
})
|
||||
if ok then
|
||||
pretty.print(serialised)
|
||||
else
|
||||
|
@@ -28,7 +28,7 @@ local monitor = peripheral.wrap(sName)
|
||||
local previousTerm = term.redirect(monitor)
|
||||
|
||||
local co = coroutine.create(function()
|
||||
shell.run(sProgram, table.unpack(tArgs, 3))
|
||||
(shell.execute or shell.run)(sProgram, table.unpack(tArgs, 3))
|
||||
end)
|
||||
|
||||
local function resume(...)
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs < 2 then
|
||||
print("Usage: mv <source> <destination>")
|
||||
@@ -8,12 +7,35 @@ end
|
||||
local sSource = shell.resolve(tArgs[1])
|
||||
local sDest = shell.resolve(tArgs[2])
|
||||
local tFiles = fs.find(sSource)
|
||||
|
||||
local function sanity_checks(source, dest)
|
||||
if fs.exists(dest) then
|
||||
printError("Destination exists")
|
||||
return false
|
||||
elseif fs.isReadOnly(dest) then
|
||||
printError("Destination is read-only")
|
||||
return false
|
||||
elseif fs.isDriveRoot(source) then
|
||||
printError("Cannot move mount /" .. source)
|
||||
return false
|
||||
elseif fs.isReadOnly(source) then
|
||||
printError("Cannot move read-only file /" .. source)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if #tFiles > 0 then
|
||||
for _, sFile in ipairs(tFiles) do
|
||||
if fs.isDir(sDest) then
|
||||
fs.move(sFile, fs.combine(sDest, fs.getName(sFile)))
|
||||
local dest = fs.combine(sDest, fs.getName(sFile))
|
||||
if sanity_checks(sFile, dest) then
|
||||
fs.move(sFile, dest)
|
||||
end
|
||||
elseif #tFiles == 1 then
|
||||
fs.move(sFile, sDest)
|
||||
if sanity_checks(sFile, sDest) then
|
||||
fs.move(sFile, sDest)
|
||||
end
|
||||
else
|
||||
printError("Cannot overwrite file multiple times")
|
||||
return
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local bAll = false
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 and tArgs[1] == "all" then
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
|
||||
local function printUsage()
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
-- Find modems
|
||||
local tModems = {}
|
||||
for _, sModem in ipairs(peripheral.getNames()) do
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
local tArgs = { ... }
|
||||
|
||||
local function printUsage()
|
||||
|
@@ -10,6 +10,12 @@ local sDest = shell.resolve(tArgs[2])
|
||||
if not fs.exists(sSource) then
|
||||
printError("No matching files")
|
||||
return
|
||||
elseif fs.isDriveRoot(sSource) then
|
||||
printError("Can't rename mounts")
|
||||
return
|
||||
elseif fs.isReadOnly(sSource) then
|
||||
printError("Source is read-only")
|
||||
return
|
||||
elseif fs.exists(sDest) then
|
||||
printError("Destination exists")
|
||||
return
|
||||
|
@@ -11,6 +11,7 @@
|
||||
-- @module[module] shell
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
local make_package = dofile("rom/modules/main/cc/require.lua").make
|
||||
|
||||
local multishell = multishell
|
||||
local parentShell = shell
|
||||
@@ -28,94 +29,10 @@ local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {}
|
||||
local tProgramStack = {}
|
||||
|
||||
local shell = {} --- @export
|
||||
local function createShellEnv(sDir)
|
||||
local tEnv = {}
|
||||
tEnv.shell = shell
|
||||
tEnv.multishell = multishell
|
||||
|
||||
local package = {}
|
||||
package.loaded = {
|
||||
_G = _G,
|
||||
bit32 = bit32,
|
||||
coroutine = coroutine,
|
||||
math = math,
|
||||
package = package,
|
||||
string = string,
|
||||
table = table,
|
||||
}
|
||||
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
|
||||
if turtle then
|
||||
package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
|
||||
elseif command then
|
||||
package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
|
||||
end
|
||||
package.config = "/\n;\n?\n!\n-"
|
||||
package.preload = {}
|
||||
package.loaders = {
|
||||
function(name)
|
||||
if package.preload[name] then
|
||||
return package.preload[name]
|
||||
else
|
||||
return nil, "no field package.preload['" .. name .. "']"
|
||||
end
|
||||
end,
|
||||
function(name)
|
||||
local fname = string.gsub(name, "%.", "/")
|
||||
local sError = ""
|
||||
for pattern in string.gmatch(package.path, "[^;]+") do
|
||||
local sPath = string.gsub(pattern, "%?", fname)
|
||||
if sPath:sub(1, 1) ~= "/" then
|
||||
sPath = fs.combine(sDir, sPath)
|
||||
end
|
||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||
local fnFile, sError = loadfile(sPath, nil, tEnv)
|
||||
if fnFile then
|
||||
return fnFile, sPath
|
||||
else
|
||||
return nil, sError
|
||||
end
|
||||
else
|
||||
if #sError > 0 then
|
||||
sError = sError .. "\n "
|
||||
end
|
||||
sError = sError .. "no file '" .. sPath .. "'"
|
||||
end
|
||||
end
|
||||
return nil, sError
|
||||
end,
|
||||
}
|
||||
|
||||
local sentinel = {}
|
||||
local function require(name)
|
||||
expect(1, name, "string")
|
||||
if package.loaded[name] == sentinel then
|
||||
error("loop or previous error loading module '" .. name .. "'", 0)
|
||||
end
|
||||
if package.loaded[name] then
|
||||
return package.loaded[name]
|
||||
end
|
||||
|
||||
local sError = "module '" .. name .. "' not found:"
|
||||
for _, searcher in ipairs(package.loaders) do
|
||||
local loader = table.pack(searcher(name))
|
||||
if loader[1] then
|
||||
package.loaded[name] = sentinel
|
||||
local result = loader[1](name, table.unpack(loader, 2, loader.n))
|
||||
if result == nil then result = true end
|
||||
|
||||
package.loaded[name] = result
|
||||
return result
|
||||
else
|
||||
sError = sError .. "\n " .. loader[2]
|
||||
end
|
||||
end
|
||||
error(sError, 2)
|
||||
end
|
||||
|
||||
tEnv.package = package
|
||||
tEnv.require = require
|
||||
|
||||
return tEnv
|
||||
local function createShellEnv(dir)
|
||||
local env = { shell = shell, multishell = multishell }
|
||||
env.require, env.package = make_package(env, dir)
|
||||
return env
|
||||
end
|
||||
|
||||
-- Colours
|
||||
@@ -130,8 +47,25 @@ else
|
||||
bgColour = colours.black
|
||||
end
|
||||
|
||||
local function run(_sCommand, ...)
|
||||
local sPath = shell.resolveProgram(_sCommand)
|
||||
--- Run a program with the supplied arguments.
|
||||
--
|
||||
-- Unlike @{shell.run}, each argument is passed to the program verbatim. While
|
||||
-- `shell.run("echo", "b c")` runs `echo` with `b` and `c`,
|
||||
-- `shell.execute("echo", "b c")` runs `echo` with a single argument `b c`.
|
||||
--
|
||||
-- @tparam string command The program to execute.
|
||||
-- @tparam string ... Arguments to this program.
|
||||
-- @treturn boolean Whether the program exited successfully.
|
||||
-- @usage Run `paint my-image` from within your program:
|
||||
--
|
||||
-- shell.execute("paint", "my-image")
|
||||
function shell.execute(command, ...)
|
||||
expect(1, command, "string")
|
||||
for i = 1, select('#', ...) do
|
||||
expect(i + 1, select(i, ...), "string")
|
||||
end
|
||||
|
||||
local sPath = shell.resolveProgram(command)
|
||||
if sPath ~= nil then
|
||||
tProgramStack[#tProgramStack + 1] = sPath
|
||||
if multishell then
|
||||
@@ -144,7 +78,7 @@ local function run(_sCommand, ...)
|
||||
|
||||
local sDir = fs.getDir(sPath)
|
||||
local env = createShellEnv(sDir)
|
||||
env.arg = { [0] = _sCommand, ... }
|
||||
env.arg = { [0] = command, ... }
|
||||
local result = os.run(env, sPath, ...)
|
||||
|
||||
tProgramStack[#tProgramStack] = nil
|
||||
@@ -196,11 +130,12 @@ end
|
||||
-- @usage Run `paint my-image` from within your program:
|
||||
--
|
||||
-- shell.run("paint", "my-image")
|
||||
-- @see shell.execute Run a program directly without parsing the arguments.
|
||||
function shell.run(...)
|
||||
local tWords = tokenise(...)
|
||||
local sCommand = tWords[1]
|
||||
if sCommand then
|
||||
return run(sCommand, table.unpack(tWords, 2))
|
||||
return shell.execute(sCommand, table.unpack(tWords, 2))
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user