1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-26 23:42:18 +00:00

Merge pull request #29 from Jummit/port-9

Brings us to CC:T 8494ba8ce29cd8d7b9105eef497fe3fe3f89d350 so... who is gonna complain?
This commit is contained in:
Merith 2021-05-16 09:21:31 -07:00 committed by GitHub
commit b0782ec38b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 3360 additions and 1631 deletions

View File

@ -16,7 +16,7 @@ jobs:
java-version: 8
- name: Cache gradle dependencies
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}

View File

@ -55,8 +55,8 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.2.10"
modRuntime "me.shedaniel:RoughlyEnoughItems:5.2.10"
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.8.9"
modRuntime "me.shedaniel:RoughlyEnoughItems:5.8.9"
}
sourceSets {

21
doc/events/alarm.md Normal file
View File

@ -0,0 +1,21 @@
---
module: [kind=event] alarm
see: os.setAlarm To start an alarm.
---
The @{timer} event is fired when an alarm started with @{os.setAlarm} completes.
## Return Values
1. @{string}: The event name.
2. @{number}: The ID of the alarm that finished.
## Example
Starts a timer and then prints its ID:
```lua
local alarmID = os.setAlarm(os.time() + 0.05)
local event, id
repeat
event, id = os.pullEvent("alarm")
until id == alarmID
print("Alarm with ID " .. id .. " was fired")
```

24
doc/events/char.md Normal file
View File

@ -0,0 +1,24 @@
---
module: [kind=event] char
see: key To listen to any key press.
---
The @{char} event is fired when a character is _typed_ on the keyboard.
The @{char} event is different to a key press. Sometimes multiple key presses may result in one character being
typed (for instance, on some European keyboards). Similarly, some keys (e.g. <kbd>Ctrl</kbd>) do not have any
corresponding character. The @{key} should be used if you want to listen to key presses themselves.
## Return values
1. @{string}: The event name.
2. @{string}: The string representing the character that was pressed.
## Example
Prints each character the user presses:
```lua
while true do
local event, character = os.pullEvent("char")
print(character .. " was pressed.")
end
```

View File

@ -0,0 +1,18 @@
---
module: [kind=event] computer_command
---
The @{computer_command} event is fired when the `/computercraft queue` command is run for the current computer.
## Return Values
1. @{string}: The event name.
... @{string}: The arguments passed to the command.
## Example
Prints the contents of messages sent:
```lua
while true do
local event = {os.pullEvent("computer_command")}
print("Received message:", table.unpack(event, 2))
end
```

19
doc/events/disk.md Normal file
View File

@ -0,0 +1,19 @@
---
module: [kind=event] disk
see: disk_eject For the event sent when a disk is removed.
---
The @{disk} event is fired when a disk is inserted into an adjacent or networked disk drive.
## Return Values
1. @{string}: The event name.
2. @{string}: The side of the disk drive that had a disk inserted.
## Example
Prints a message when a disk is inserted:
```lua
while true do
local event, side = os.pullEvent("disk")
print("Inserted a disk on side " .. side)
end
```

19
doc/events/disk_eject.md Normal file
View File

@ -0,0 +1,19 @@
---
module: [kind=event] disk_eject
see: disk For the event sent when a disk is inserted.
---
The @{disk_eject} event is fired when a disk is removed from an adjacent or networked disk drive.
## Return Values
1. @{string}: The event name.
2. @{string}: The side of the disk drive that had a disk removed.
## Example
Prints a message when a disk is removed:
```lua
while true do
local event, side = os.pullEvent("disk_eject")
print("Removed a disk on side " .. side)
end
```

14
doc/events/http_check.md Normal file
View File

@ -0,0 +1,14 @@
---
module: [kind=event] http_check
see: http.checkURLAsync To check a URL asynchronously.
---
The @{http_check} event is fired when a URL check finishes.
This event is normally handled inside @{http.checkURL}, but it can still be seen when using @{http.checkURLAsync}.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL requested to be checked.
3. @{boolean}: Whether the check succeeded.
4. @{string|nil}: If the check failed, a reason explaining why the check failed.

View File

@ -0,0 +1,39 @@
---
module: [kind=event] http_failure
see: http.request To send an HTTP request.
---
The @{http_failure} event is fired when an HTTP request fails.
This event is normally handled inside @{http.get} and @{http.post}, but it can still be seen when using @{http.request}.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{string}: An error describing the failure.
4. @{http.Response|nil}: A response handle if the connection succeeded, but the server's response indicated failure.
## Example
Prints an error why the website cannot be contacted:
```lua
local myURL = "https://does.not.exist.tweaked.cc"
http.request(myURL)
local event, url, err
repeat
event, url, err = os.pullEvent("http_failure")
until url == myURL
print("The URL " .. url .. " could not be reached: " .. err)
```
Prints the contents of a webpage that does not exist:
```lua
local myURL = "https://tweaked.cc/this/does/not/exist"
http.request(myURL)
local event, url, err, handle
repeat
event, url, err, handle = os.pullEvent("http_failure")
until url == myURL
print("The URL " .. url .. " could not be reached: " .. err)
print(handle.getResponseCode())
handle.close()
```

View File

@ -0,0 +1,27 @@
---
module: [kind=event] http_success
see: http.request To make an HTTP request.
---
The @{http_success} event is fired when an HTTP request returns successfully.
This event is normally handled inside @{http.get} and @{http.post}, but it can still be seen when using @{http.request}.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{http.Response}: The handle for the response text.
## Example
Prints the content of a website (this may fail if the request fails):
```lua
local myURL = "https://tweaked.cc/"
http.request(myURL)
local event, url, handle
repeat
event, url, handle = os.pullEvent("http_success")
until url == myURL
print("Contents of " .. url .. ":")
print(handle.readAll())
handle.close()
```

26
doc/events/key.md Normal file
View File

@ -0,0 +1,26 @@
---
module: [kind=event] key
---
This event is fired when any key is pressed while the terminal is focused.
This event returns a numerical "key code" (for instance, <kbd>F1</kbd> is 290). This value may vary between versions and
so it is recommended to use the constants in the @{keys} API rather than hard coding numeric values.
If the button pressed represented a printable character, then the @{key} event will be followed immediately by a @{char}
event. If you are consuming text input, use a @{char} event instead!
## Return values
1. @{string}: The event name.
2. @{number}: The numerical key value of the key pressed.
3. @{boolean}: Whether the key event was generated while holding the key (@{true}), rather than pressing it the first time (@{false}).
## Example
Prints each key when the user presses it, and if the key is being held.
```lua
while true do
local event, key, is_held = os.pullEvent("key")
print(("%s held=%b"):format(keys.getName(key), is_held))
end
```

24
doc/events/key_up.md Normal file
View File

@ -0,0 +1,24 @@
---
module: [kind=event] key_up
see: keys For a lookup table of the given keys.
---
Fired whenever a key is released (or the terminal is closed while a key was being pressed).
This event returns a numerical "key code" (for instance, <kbd>F1</kbd> is 290). This value may vary between versions and
so it is recommended to use the constants in the @{keys} API rather than hard coding numeric values.
## Return values
1. @{string}: The event name.
2. @{number}: The numerical key value of the key pressed.
## Example
Prints each key released on the keyboard whenever a @{key_up} event is fired.
```lua
while true do
local event, key = os.pullEvent("key_up")
local name = keys.getName(key) or "unknown key"
print(name .. " was released.")
end
```

View File

@ -0,0 +1,22 @@
---
module: [kind=event] modem_message
---
The @{modem_message} event is fired when a message is received on an open channel on any modem.
## Return Values
1. @{string}: The event name.
2. @{string}: The side of the modem that received the message.
3. @{number}: The channel that the message was sent on.
4. @{number}: The reply channel set by the sender.
5. @{any}: The message as sent by the sender.
6. @{number}: The distance between the sender and the receiver, in blocks (decimal).
## Example
Prints a message when one is sent:
```lua
while true do
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message)))
end
```

View File

@ -0,0 +1,18 @@
---
module: [kind=event] monitor_resize
---
The @{monitor_resize} event is fired when an adjacent or networked monitor's size is changed.
## Return Values
1. @{string}: The event name.
2. @{string}: The side or network ID of the monitor that resized.
## Example
Prints a message when a monitor is resized:
```lua
while true do
local event, side = os.pullEvent("monitor_resize")
print("The monitor on side " .. side .. " was resized.")
end
```

View File

@ -0,0 +1,20 @@
---
module: [kind=event] monitor_touch
---
The @{monitor_touch} event is fired when an adjacent or networked Advanced Monitor is right-clicked.
## Return Values
1. @{string}: The event name.
2. @{string}: The side or network ID of the monitor that was touched.
3. @{number}: The X coordinate of the touch, in characters.
4. @{number}: The Y coordinate of the touch, in characters.
## Example
Prints a message when a monitor is touched:
```lua
while true do
local event, side, x, y = os.pullEvent("monitor_touch")
print("The monitor on side " .. side .. " was touched at (" .. x .. ", " .. y .. ")")
end
```

34
doc/events/mouse_click.md Normal file
View File

@ -0,0 +1,34 @@
---
module: [kind=event] mouse_click
---
This event is fired when the terminal is clicked with a mouse. This event is only fired on advanced computers (including
advanced turtles and pocket computers).
## Return values
1. @{string}: The event name.
2. @{number}: The mouse button that was clicked.
3. @{number}: The X-coordinate of the click.
4. @{number}: The Y-coordinate of the click.
## Mouse buttons
Several mouse events (@{mouse_click}, @{mouse_up}, @{mouse_scroll}) contain a "mouse button" code. This takes a
numerical value depending on which button on your mouse was last pressed when this event occurred.
<table class="pretty-table">
<!-- Our markdown parser doesn't work on tables!? Guess I'll have to roll my own soonish :/. -->
<tr><th>Button code</th><th>Mouse button</th></tr>
<tr><td align="right">1</td><td>Left button</td></tr>
<tr><td align="right">2</td><td>Middle button</td></tr>
<tr><td align="right">3</td><td>Right button</td></tr>
</table>
## Example
Print the button and the coordinates whenever the mouse is clicked.
```lua
while true do
local event, button, x, y = os.pullEvent("mouse_click")
print(("The mouse button %s was pressed at %d, %d"):format(button, x, y))
end
```

22
doc/events/mouse_drag.md Normal file
View File

@ -0,0 +1,22 @@
---
module: [kind=event] mouse_drag
see: mouse_click For when a mouse button is initially pressed.
---
This event is fired every time the mouse is moved while a mouse button is being held.
## Return values
1. @{string}: The event name.
2. @{number}: The [mouse button](mouse_click.html#Mouse_buttons) that is being pressed.
3. @{number}: The X-coordinate of the mouse.
4. @{number}: The Y-coordinate of the mouse.
## Example
Print the button and the coordinates whenever the mouse is dragged.
```lua
while true do
local event, button, x, y = os.pullEvent("mouse_drag")
print(("The mouse button %s was dragged at %d, %d"):format(button, x, y))
end
```

View File

@ -0,0 +1,21 @@
---
module: [kind=event] mouse_scroll
---
This event is fired when a mouse wheel is scrolled in the terminal.
## Return values
1. @{string}: The event name.
2. @{number}: The direction of the scroll. (-1 = up, 1 = down)
3. @{number}: The X-coordinate of the mouse when scrolling.
4. @{number}: The Y-coordinate of the mouse when scrolling.
## Example
Prints the direction of each scroll, and the position of the mouse at the time.
```lua
while true do
local event, dir, x, y = os.pullEvent("mouse_scroll")
print(("The mouse was scrolled in direction %s at %d, %d"):format(dir, x, y))
end
```

21
doc/events/mouse_up.md Normal file
View File

@ -0,0 +1,21 @@
---
module: [kind=event] mouse_up
---
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.
## Return values
1. @{string}: The event name.
2. @{number}: The [mouse button](mouse_click.html#Mouse_buttons) that was released.
3. @{number}: The X-coordinate of the mouse.
4. @{number}: The Y-coordinate of the mouse.
## Example
Prints the coordinates and button number whenever the mouse is released.
```lua
while true do
local event, button, x, y = os.pullEvent("mouse_up")
print(("The mouse button %s was released at %d, %d"):format(button, x, y))
end
```

18
doc/events/paste.md Normal file
View File

@ -0,0 +1,18 @@
---
module: [kind=event] paste
---
The @{paste} event is fired when text is pasted into the computer through Ctrl-V (or ⌘V on Mac).
## Return values
1. @{string}: The event name.
2. @{string} The text that was pasted.
## Example
Prints pasted text:
```lua
while true do
local event, text = os.pullEvent("paste")
print('"' .. text .. '" was pasted')
end
```

19
doc/events/peripheral.md Normal file
View File

@ -0,0 +1,19 @@
---
module: [kind=event] peripheral
see: peripheral_detach For the event fired when a peripheral is detached.
---
The @{peripheral} event is fired when a peripheral is attached on a side or to a modem.
## Return Values
1. @{string}: The event name.
2. @{string}: The side the peripheral was attached to.
## Example
Prints a message when a peripheral is attached:
```lua
while true do
local event, side = os.pullEvent("peripheral")
print("A peripheral was attached on side " .. side)
end
```

View File

@ -0,0 +1,19 @@
---
module: [kind=event] peripheral_detach
see: peripheral For the event fired when a peripheral is attached.
---
The @{peripheral_detach} event is fired when a peripheral is detached from a side or from a modem.
## Return Values
1. @{string}: The event name.
2. @{string}: The side the peripheral was detached from.
## Example
Prints a message when a peripheral is detached:
```lua
while true do
local event, side = os.pullEvent("peripheral_detach")
print("A peripheral was detached on side " .. side)
end
```

View File

@ -0,0 +1,30 @@
---
module: [kind=event] rednet_message
see: modem_message For raw modem messages sent outside of Rednet.
see: rednet.receive To wait for a Rednet message with an optional timeout and protocol filter.
---
The @{rednet_message} event is fired when a message is sent over Rednet.
This event is usually handled by @{rednet.receive}, but it can also be pulled manually.
@{rednet_message} events are sent by @{rednet.run} in the top-level coroutine in response to @{modem_message} events. A @{rednet_message} event is always preceded by a @{modem_message} event. They are generated inside CraftOS rather than being sent by the ComputerCraft machine.
## Return Values
1. @{string}: The event name.
2. @{number}: The ID of the sending computer.
3. @{any}: The message sent.
4. @{string|nil}: The protocol of the message, if provided.
## Example
Prints a message when one is sent:
```lua
while true do
local event, sender, message, protocol = os.pullEvent("rednet_message")
if protocol ~= nil then
print("Received message from " .. sender .. " with protocol " .. protocol .. " and message " .. tostring(message))
else
print("Received message from " .. sender .. " with message " .. tostring(message))
end
end
```

14
doc/events/redstone.md Normal file
View File

@ -0,0 +1,14 @@
---
module: [kind=event] redstone
---
The @{redstone} event is fired whenever any redstone inputs on the computer change.
## Example
Prints a message when a redstone input changes:
```lua
while true do
os.pullEvent("redstone")
print("A redstone input has changed!")
end
```

View File

@ -0,0 +1,28 @@
---
module: [kind=event] task_complete
see: commands.execAsync To run a command which fires a task_complete event.
---
The @{task_complete} event is fired when an asynchronous task completes. This is usually handled inside the function call that queued the task; however, functions such as @{commands.execAsync} return immediately so the user can wait for completion.
## Return Values
1. @{string}: The event name.
2. @{number}: The ID of the task that completed.
3. @{boolean}: Whether the command succeeded.
4. @{string}: If the command failed, an error message explaining the failure. (This is not present if the command succeeded.)
...: Any parameters returned from the command.
## Example
Prints the results of an asynchronous command:
```lua
local taskID = commands.execAsync("say Hello")
local event
repeat
event = {os.pullEvent("task_complete")}
until event[2] == taskID
if event[3] == true then
print("Task " .. event[2] .. " succeeded:", table.unpack(event, 4))
else
print("Task " .. event[2] .. " failed: " .. event[4])
end
```

15
doc/events/term_resize.md Normal file
View File

@ -0,0 +1,15 @@
---
module: [kind=event] term_resize
---
The @{term_resize} event is fired when the main terminal is resized, mainly when a new tab is opened or closed in @{multishell}.
## Example
Prints :
```lua
while true do
os.pullEvent("term_resize")
local w, h = term.getSize()
print("The term was resized to (" .. w .. ", " .. h .. ")")
end
```

25
doc/events/terminate.md Normal file
View File

@ -0,0 +1,25 @@
---
module: [kind=event] terminate
---
The @{terminate} event is fired when <kbd>Ctrl-T</kbd> is held down.
This event is normally handled by @{os.pullEvent}, and will not be returned. However, @{os.pullEventRaw} will return this event when fired.
@{terminate} will be sent even when a filter is provided to @{os.pullEventRaw}. When using @{os.pullEventRaw} with a filter, make sure to check that the event is not @{terminate}.
## Example
Prints a message when Ctrl-T is held:
```lua
while true do
local event = os.pullEventRaw("terminate")
if event == "terminate" then print("Terminate requested!") end
end
```
Exits when Ctrl-T is held:
```lua
while true do
os.pullEvent()
end
```

21
doc/events/timer.md Normal file
View File

@ -0,0 +1,21 @@
---
module: [kind=event] timer
see: os.startTimer To start a timer.
---
The @{timer} event is fired when a timer started with @{os.startTimer} completes.
## Return Values
1. @{string}: The event name.
2. @{number}: The ID of the timer that finished.
## Example
Starts a timer and then prints its ID:
```lua
local timerID = os.startTimer(2)
local event, id
repeat
event, id = os.pullEvent("timer")
until id == timerID
print("Timer with ID " .. id .. " was fired")
```

View File

@ -0,0 +1,14 @@
---
module: [kind=event] turtle_inventory
---
The @{turtle_inventory} event is fired when a turtle's inventory is changed.
## Example
Prints a message when the inventory is changed:
```lua
while true do
os.pullEvent("turtle_inventory")
print("The inventory was changed.")
end
```

View File

@ -0,0 +1,21 @@
---
module: [kind=event] websocket_closed
---
The @{websocket_closed} event is fired when an open WebSocket connection is closed.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the WebSocket that was closed.
## Example
Prints a message when a WebSocket is closed (this may take a minute):
```lua
local myURL = "wss://example.tweaked.cc/echo"
local ws = http.websocket(myURL)
local event, url
repeat
event, url = os.pullEvent("websocket_closed")
until url == myURL
print("The WebSocket at " .. url .. " was closed.")
```

View File

@ -0,0 +1,25 @@
---
module: [kind=event] websocket_failure
see: http.websocketAsync To send an HTTP request.
---
The @{websocket_failure} event is fired when a WebSocket connection request fails.
This event is normally handled inside @{http.websocket}, but it can still be seen when using @{http.websocketAsync}.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{string}: An error describing the failure.
## Example
Prints an error why the website cannot be contacted:
```lua
local myURL = "wss://example.tweaked.cc/not-a-websocket"
http.websocketAsync(myURL)
local event, url, err
repeat
event, url, err = os.pullEvent("websocket_failure")
until url == myURL
print("The URL " .. url .. " could not be reached: " .. err)
```

View File

@ -0,0 +1,26 @@
---
module: [kind=event] websocket_message
---
The @{websocket_message} event is fired when a message is received on an open WebSocket connection.
This event is normally handled by @{http.Websocket.receive}, but it can also be pulled manually.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the WebSocket.
3. @{string}: The contents of the message.
## Example
Prints a message sent by a WebSocket:
```lua
local myURL = "wss://example.tweaked.cc/echo"
local ws = http.websocket(myURL)
ws.send("Hello!")
local event, url, message
repeat
event, url, message = os.pullEvent("websocket_message")
until url == myURL
print("Received message from " .. url .. " with contents " .. message)
ws.close()
```

View File

@ -0,0 +1,28 @@
---
module: [kind=event] websocket_success
see: http.websocketAsync To open a WebSocket asynchronously.
---
The @{websocket_success} event is fired when a WebSocket connection request returns successfully.
This event is normally handled inside @{http.websocket}, but it can still be seen when using @{http.websocketAsync}.
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the site.
3. @{http.Websocket}: The handle for the WebSocket.
## Example
Prints the content of a website (this may fail if the request fails):
```lua
local myURL = "wss://example.tweaked.cc/echo"
http.websocketAsync(myURL)
local event, url, handle
repeat
event, url, handle = os.pullEvent("websocket_success")
until url == myURL
print("Connected to " .. url)
handle.send("Hello!")
print(handle.receive())
handle.close()
```

View File

@ -2,16 +2,16 @@
org.gradle.jvmargs=-Xmx1G
# Mod properties
mod_version=1.95.0-beta
mod_version=1.95.3-beta
# Minecraft properties
mc_version=1.16.2
mappings_version=31
mc_version=1.16.4
mappings_version=9
# Dependencies
cloth_config_version=4.8.1
fabric_api_version=0.19.0+build.398-1.16
fabric_loader_version=0.9.2+build.206
fabric_api_version=0.34.2+1.16
fabric_loader_version=0.11.3
jankson_version=1.2.0
modmenu_version=1.14.6+
cloth_api_version=1.4.5

View File

@ -536,4 +536,92 @@ e4b0a5b3ce035eb23feb4191432fc49af5772c5b
2020 -> 2021
```
A huge amount of changes.
A huge amount of changes.
```
542b66c79a9b08e080c39c9a73d74ffe71c0106a
Add back command computer block drops
```
Didn't port some forge-related changes, but it works.
```
dd6f97622e6c18ce0d8988da6a5bede45c94ca5d
Prevent reflection errors crashing the game
```
```
92be0126df63927d07fc695945f8b98e328f945a
Fix disk recipes
```
Dye recipes actually work now.
```
1edb7288b974aec3764b0a820edce7e9eee38e66
Merge branch 'mc-1.15.x' into mc-1.16.x
```
New version: 1.95.1.
```
41226371f3b5fd35f48b6d39c2e8e0c277125b21
Add isReadOnly to fs.attributes (#639)
```
Also changed some lua test files, but made the changes anyway.
```
b2e54014869fac4b819b01b6c24e550ca113ce8a
Added Numpad Enter Support in rom lua programs. (#657)
```
Just lua changes.
```
247c05305d106af430fcdaee41371a152bf7c38c
Fix problem with RepeatArgumentType
```
```
c864576619751077a0d8ac1a18123e14b095ec03
Fix impostor recipes for disks
```
[TODO] [JUMT-03] REI still shows white disks, probably because it doesn' show nbt items.
```
c5694ea9661c7a40021ebd280c378bd7bdc56988
Merge branch 'mc-1.15.x' into mc-1.16.x
```
Update to 1.16.4.
```
1f84480a80677cfaaf19d319290f5b44635eba47
Make rightAlt only close menu, never open it. (#672)
```
Lua changes.
```
1255bd00fd21247a50046020d7d9a396f66bc6bd
Fix mounts being usable after a disk is ejected
```
Reverted a lot of code style changes made by Zundrel, so the diffs are huge.
```
b90611b4b4c176ec1c80df002cc4ac36aa0c4dc8
Preserve registration order of upgrades
```
Again, a huge diff because of code style changes.
```
8494ba8ce29cd8d7b9105eef497fe3fe3f89d350
Improve UX when a resource mount cannot be found
```

View File

@ -108,7 +108,10 @@ public final class ComputerCraftAPI {
* Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a resource folder onto a computer's file
* system.
*
* The files in this mount will be a combination of files in all mod jar, and data packs that contain resources with the same domain and path.
* The files in this mount will be a combination of files in all mod jar, and data packs that contain
* resources with the same domain and path. For instance, ComputerCraft's resources are stored in
* "/data/computercraft/lua/rom". We construct a mount for that with
* {@code createResourceMount("computercraft", "lua/rom")}.
*
* @param domain The domain under which to look for resources. eg: "mymod".
* @param subPath The subPath under which to look for resources. eg: "lua/myfiles".

View File

@ -1,3 +1,8 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api;
import dan200.computercraft.api.pocket.IPocketUpgrade;

View File

@ -48,7 +48,7 @@ import net.minecraft.util.Hand;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.village.TraderOfferList;
import net.minecraft.village.TradeOfferList;
import net.minecraft.world.GameMode;
/**
@ -105,7 +105,7 @@ public class FakePlayer extends ServerPlayerEntity {
}
@Override
public void sendTradeOffers(int id, TraderOfferList list, int level, int experience, boolean levelled, boolean refreshable) { }
public void sendTradeOffers(int id, TradeOfferList list, int level, int experience, boolean levelled, boolean refreshable) { }
@Override
public void openHorseInventory(HorseBaseEntity horse, Inventory inventory) { }
@ -251,10 +251,6 @@ public class FakePlayer extends ServerPlayerEntity {
public void disconnect(Text message) {
}
@Override
public void setupEncryption(SecretKey key) {
}
@Override
public void disableAutoRead() {
}

View File

@ -396,7 +396,8 @@ public class FSAPI implements ILuaAPI {
/**
* 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 returned attributes table contains information about the size of the file, whether it is a directory,
* when it was created and last modified, and whether it is read only.
*
* The creation and modification times are given as the number of milliseconds since the UNIX epoch. This may be given to {@link OSAPI#date} in order to
* convert it to more usable form.
@ -404,7 +405,7 @@ public class FSAPI implements ILuaAPI {
* @param path The path to get attributes for.
* @return The resulting attributes.
* @throws LuaException If the path does not exist.
* @cc.treturn { size = number, isDir = boolean, created = number, modified = number } The resulting attributes.
* @cc.treturn { size = number, isDir = boolean, isReadOnly = boolean, created = number, modified = number } The resulting attributes.
* @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.
*/
@ -413,11 +414,12 @@ public class FSAPI implements ILuaAPI {
try {
BasicFileAttributes attributes = this.fileSystem.getAttributes(path);
Map<String, Object> result = new HashMap<>();
result.put("modification", getFileTime(attributes.lastModifiedTime()));
result.put("modified", getFileTime(attributes.lastModifiedTime()));
result.put("created", getFileTime(attributes.creationTime()));
result.put("size", attributes.isDirectory() ? 0 : attributes.size());
result.put("isDir", attributes.isDirectory());
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
result.put( "modified", getFileTime( attributes.lastModifiedTime() ) );
result.put( "created", getFileTime( attributes.creationTime() ) );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() );
result.put( "isReadOnly", fileSystem.isReadOnly( path ) );
return result;
} catch (FileSystemException e) {
throw new LuaException(e.getMessage());

View File

@ -3,185 +3,206 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import static dan200.computercraft.core.apis.TableHelper.getStringField;
import static dan200.computercraft.core.apis.TableHelper.optBooleanField;
import static dan200.computercraft.core.apis.TableHelper.optStringField;
import static dan200.computercraft.core.apis.TableHelper.optTableField;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.http.CheckUrl;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.Resource;
import dan200.computercraft.core.apis.http.ResourceGroup;
import dan200.computercraft.core.apis.http.ResourceQueue;
import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.request.HttpRequest;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import javax.annotation.Nonnull;
import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.core.apis.TableHelper.*;
/**
* The http library allows communicating with web servers, sending and receiving data from them.
*
* @cc.module http
* @hidden
*/
public class HTTPAPI implements ILuaAPI {
private final IAPIEnvironment m_apiEnvironment;
public class HTTPAPI implements ILuaAPI
{
private final IAPIEnvironment apiEnvironment;
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>();
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>(() -> ComputerCraft.httpMaxRequests);
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>(() -> ComputerCraft.httpMaxWebsockets);
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
public HTTPAPI(IAPIEnvironment environment) {
this.m_apiEnvironment = environment;
public HTTPAPI( IAPIEnvironment environment )
{
apiEnvironment = environment;
}
@Override
public String[] getNames() {
return new String[] {"http"};
public String[] getNames()
{
return new String[] { "http" };
}
@Override
public void startup() {
this.checkUrls.startup();
this.requests.startup();
this.websockets.startup();
public void startup()
{
checkUrls.startup();
requests.startup();
websockets.startup();
}
@Override
public void update() {
public void shutdown()
{
checkUrls.shutdown();
requests.shutdown();
websockets.shutdown();
}
@Override
public void update()
{
// It's rather ugly to run this here, but we need to clean up
// resources as often as possible to reduce blocking.
Resource.cleanup();
}
@Override
public void shutdown() {
this.checkUrls.shutdown();
this.requests.shutdown();
this.websockets.shutdown();
}
@LuaFunction
public final Object[] request(IArguments args) throws LuaException {
public final Object[] request( IArguments args ) throws LuaException
{
String address, postString, requestMethod;
Map<?, ?> headerTable;
boolean binary, redirect;
if (args.get(0) instanceof Map) {
Map<?, ?> options = args.getTable(0);
address = getStringField(options, "url");
postString = optStringField(options, "body", null);
headerTable = optTableField(options, "headers", Collections.emptyMap());
binary = optBooleanField(options, "binary", false);
requestMethod = optStringField(options, "method", null);
redirect = optBooleanField(options, "redirect", true);
if( args.get( 0 ) instanceof Map )
{
Map<?, ?> options = args.getTable( 0 );
address = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", Collections.emptyMap() );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
} else {
}
else
{
// Get URL and post information
address = args.getString(0);
postString = args.optString(1, null);
headerTable = args.optTable(2, Collections.emptyMap());
binary = args.optBoolean(3, false);
address = args.getString( 0 );
postString = args.optString( 1, null );
headerTable = args.optTable( 2, Collections.emptyMap() );
binary = args.optBoolean( 3, false );
requestMethod = null;
redirect = true;
}
HttpHeaders headers = getHeaders(headerTable);
HttpHeaders headers = getHeaders( headerTable );
HttpMethod httpMethod;
if (requestMethod == null) {
if( requestMethod == null )
{
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
} else {
httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase(Locale.ROOT));
if (httpMethod == null || requestMethod.equalsIgnoreCase("CONNECT")) {
throw new LuaException("Unsupported HTTP method");
}
else
{
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
{
throw new LuaException( "Unsupported HTTP method" );
}
}
try {
URI uri = HttpRequest.checkUri(address);
HttpRequest request = new HttpRequest(this.requests, this.m_apiEnvironment, address, postString, headers, binary, redirect);
try
{
URI uri = HttpRequest.checkUri( address );
HttpRequest request = new HttpRequest( requests, apiEnvironment, address, postString, headers, binary, redirect );
// Make the request
request.queue(r -> r.request(uri, httpMethod));
request.queue( r -> r.request( uri, httpMethod ) );
return new Object[] {true};
} catch (HTTPRequestException e) {
return new Object[] {
false,
e.getMessage()
};
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
@LuaFunction
public final Object[] checkURL( String address )
{
try
{
URI uri = HttpRequest.checkUri( address );
new CheckUrl( checkUrls, apiEnvironment, address, uri ).queue( CheckUrl::run );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
@LuaFunction
public final Object[] websocket( String address, Optional<Map<?, ?>> headerTbl ) throws LuaException
{
if( !ComputerCraft.http_websocket_enable )
{
throw new LuaException( "Websocket connections are disabled" );
}
HttpHeaders headers = getHeaders( headerTbl.orElse( Collections.emptyMap() ) );
try
{
URI uri = Websocket.checkUri( address );
if( !new Websocket( websockets, apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
{
throw new LuaException( "Too many websockets already open" );
}
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
@Nonnull
private static HttpHeaders getHeaders(@Nonnull Map<?, ?> headerTable) throws LuaException {
private HttpHeaders getHeaders( @Nonnull Map<?, ?> headerTable ) throws LuaException
{
HttpHeaders headers = new DefaultHttpHeaders();
for (Map.Entry<?, ?> entry : headerTable.entrySet()) {
for( Map.Entry<?, ?> entry : headerTable.entrySet() )
{
Object value = entry.getValue();
if (entry.getKey() instanceof String && value instanceof String) {
try {
headers.add((String) entry.getKey(), value);
} catch (IllegalArgumentException e) {
throw new LuaException(e.getMessage());
if( entry.getKey() instanceof String && value instanceof String )
{
try
{
headers.add( (String) entry.getKey(), value );
}
catch( IllegalArgumentException e )
{
throw new LuaException( e.getMessage() );
}
}
}
if( !headers.contains( HttpHeaderNames.USER_AGENT ) )
{
headers.set( HttpHeaderNames.USER_AGENT, apiEnvironment.getComputerEnvironment().getUserAgent() );
}
return headers;
}
@LuaFunction
public final Object[] checkURL(String address) {
try {
URI uri = HttpRequest.checkUri(address);
new CheckUrl(this.checkUrls, this.m_apiEnvironment, address, uri).queue(CheckUrl::run);
return new Object[] {true};
} catch (HTTPRequestException e) {
return new Object[] {
false,
e.getMessage()
};
}
}
@LuaFunction
public final Object[] websocket(String address, Optional<Map<?, ?>> headerTbl) throws LuaException {
if (!ComputerCraft.http_websocket_enable) {
throw new LuaException("Websocket connections are disabled");
}
HttpHeaders headers = getHeaders(headerTbl.orElse(Collections.emptyMap()));
try {
URI uri = Websocket.checkUri(address);
if (!new Websocket(this.websockets, this.m_apiEnvironment, uri, address, headers).queue(Websocket::connect)) {
throw new LuaException("Too many websockets already open");
}
return new Object[] {true};
} catch (HTTPRequestException e) {
return new Object[] {
false,
e.getMessage()
};
}
}
}
}

View File

@ -185,8 +185,9 @@ public class RedstoneAPI implements ILuaAPI {
* @see #testBundledInput To determine if a specific colour is set.
*/
@LuaFunction
public final int getBundledInput(ComputerSide side) {
return this.environment.getBundledOutput(side);
public final int getBundledInput( ComputerSide side )
{
return environment.getBundledInput( side );
}
/**

View File

@ -3,7 +3,6 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import java.nio.ByteBuffer;
@ -15,83 +14,81 @@ import java.util.Objects;
/**
* A seekable, readable byte channel which is backed by a simple byte array.
*/
public class ArrayByteChannel implements SeekableByteChannel {
private final byte[] backing;
public class ArrayByteChannel implements SeekableByteChannel
{
private boolean closed = false;
private int position = 0;
public ArrayByteChannel(byte[] backing) {
private final byte[] backing;
public ArrayByteChannel( byte[] backing )
{
this.backing = backing;
}
@Override
public int read(ByteBuffer destination) throws ClosedChannelException {
if (this.closed) {
throw new ClosedChannelException();
}
Objects.requireNonNull(destination, "destination");
public int read( ByteBuffer destination ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
Objects.requireNonNull( destination, "destination" );
if (this.position >= this.backing.length) {
return -1;
}
if( position >= backing.length ) return -1;
int remaining = Math.min(this.backing.length - this.position, destination.remaining());
destination.put(this.backing, this.position, remaining);
this.position += remaining;
int remaining = Math.min( backing.length - position, destination.remaining() );
destination.put( backing, position, remaining );
position += remaining;
return remaining;
}
@Override
public int write(ByteBuffer src) throws ClosedChannelException {
if (this.closed) {
throw new ClosedChannelException();
}
public int write( ByteBuffer src ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public long position() throws ClosedChannelException {
if (this.closed) {
throw new ClosedChannelException();
}
return this.position;
public long position() throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
return position;
}
@Override
public SeekableByteChannel position(long newPosition) throws ClosedChannelException {
if (this.closed) {
throw new ClosedChannelException();
public SeekableByteChannel position( long newPosition ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
if( newPosition < 0 || newPosition > Integer.MAX_VALUE )
{
throw new IllegalArgumentException( "Position out of bounds" );
}
if (newPosition < 0 || newPosition > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Position out of bounds");
}
this.position = (int) newPosition;
position = (int) newPosition;
return this;
}
@Override
public long size() throws ClosedChannelException {
if (this.closed) {
throw new ClosedChannelException();
}
return this.backing.length;
public long size() throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
return backing.length;
}
@Override
public SeekableByteChannel truncate(long size) throws ClosedChannelException {
if (this.closed) {
throw new ClosedChannelException();
}
public SeekableByteChannel truncate( long size ) throws ClosedChannelException
{
if( closed ) throw new ClosedChannelException();
throw new NonWritableChannelException();
}
@Override
public boolean isOpen() {
return !this.closed;
public boolean isOpen()
{
return !closed;
}
@Override
public void close() {
this.closed = true;
public void close()
{
closed = true;
}
}

View File

@ -3,11 +3,13 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
@ -16,40 +18,43 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
/**
* A file handle opened with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)} with the {@code "rb"} mode.
* A file handle opened with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)} with the {@code "rb"}
* mode.
*
* @cc.module fs.BinaryReadHandle
*/
public class BinaryReadableHandle extends HandleGeneric {
public class BinaryReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
final SeekableByteChannel seekable;
private final ReadableByteChannel reader;
private final ByteBuffer single = ByteBuffer.allocate(1);
BinaryReadableHandle(ReadableByteChannel reader, SeekableByteChannel seekable, Closeable closeable) {
super(closeable);
private final ReadableByteChannel reader;
final SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
BinaryReadableHandle( ReadableByteChannel reader, SeekableByteChannel seekable, TrackingCloseable closeable )
{
super( closeable );
this.reader = reader;
this.seekable = seekable;
}
public static BinaryReadableHandle of(ReadableByteChannel channel) {
return of(channel, channel);
public static BinaryReadableHandle of( ReadableByteChannel channel, TrackingCloseable closeable )
{
SeekableByteChannel seekable = asSeekable( channel );
return seekable == null ? new BinaryReadableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
}
public static BinaryReadableHandle of(ReadableByteChannel channel, Closeable closeable) {
SeekableByteChannel seekable = asSeekable(channel);
return seekable == null ? new BinaryReadableHandle(channel, null, closeable) : new Seekable(seekable, closeable);
public static BinaryReadableHandle of( ReadableByteChannel channel )
{
return of( channel, new TrackingCloseable.Impl( channel ) );
}
/**
* Read a number of bytes from this file.
*
* @param countArg The number of bytes to read. When absent, a single byte will be read <em>as a number</em>. This may be 0 to determine we are at
* the end of the file.
* @param countArg The number of bytes to read. When absent, a single byte will be read <em>as a number</em>. This
* may be 0 to determine we are at the end of the file.
* @return The read bytes.
* @throws LuaException When trying to read a negative number of bytes.
* @throws LuaException If the file has been closed.
@ -58,72 +63,78 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn [3] string The bytes read as a string. This is returned when the {@code count} is given.
*/
@LuaFunction
public final Object[] read(Optional<Integer> countArg) throws LuaException {
this.checkOpen();
try {
if (countArg.isPresent()) {
public final Object[] read( Optional<Integer> countArg ) throws LuaException
{
checkOpen();
try
{
if( countArg.isPresent() )
{
int count = countArg.get();
if (count < 0) {
throw new LuaException("Cannot read a negative number of bytes");
}
if (count == 0 && this.seekable != null) {
return this.seekable.position() >= this.seekable.size() ? null : new Object[] {""};
if( count < 0 ) throw new LuaException( "Cannot read a negative number of bytes" );
if( count == 0 && seekable != null )
{
return seekable.position() >= seekable.size() ? null : new Object[] { "" };
}
if (count <= BUFFER_SIZE) {
ByteBuffer buffer = ByteBuffer.allocate(count);
if( count <= BUFFER_SIZE )
{
ByteBuffer buffer = ByteBuffer.allocate( count );
int read = this.reader.read(buffer);
if (read < 0) {
return null;
}
int read = reader.read( buffer );
if( read < 0 ) return null;
buffer.flip();
return new Object[] {buffer};
} else {
return new Object[] { buffer };
}
else
{
// Read the initial set of characters, failing if none are read.
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int read = this.reader.read(buffer);
if (read < 0) {
return null;
}
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
int read = reader.read( buffer );
if( read < 0 ) return null;
// If we failed to read "enough" here, let's just abort
if (read >= count || read < BUFFER_SIZE) {
if( read >= count || read < BUFFER_SIZE )
{
buffer.flip();
return new Object[] {buffer};
return new Object[] { buffer };
}
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
// than doubling up the buffer each time.
int totalRead = read;
List<ByteBuffer> parts = new ArrayList<>(4);
parts.add(buffer);
while (read >= BUFFER_SIZE && totalRead < count) {
buffer = ByteBuffer.allocate(Math.min(BUFFER_SIZE, count - totalRead));
read = this.reader.read(buffer);
if (read < 0) {
break;
}
List<ByteBuffer> parts = new ArrayList<>( 4 );
parts.add( buffer );
while( read >= BUFFER_SIZE && totalRead < count )
{
buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
read = reader.read( buffer );
if( read < 0 ) break;
totalRead += read;
parts.add(buffer);
parts.add( buffer );
}
// Now just copy all the bytes across!
byte[] bytes = new byte[totalRead];
int pos = 0;
for (ByteBuffer part : parts) {
System.arraycopy(part.array(), 0, bytes, pos, part.position());
for( ByteBuffer part : parts )
{
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
pos += part.position();
}
return new Object[] {bytes};
return new Object[] { bytes };
}
} else {
this.single.clear();
int b = this.reader.read(this.single);
return b == -1 ? null : new Object[] {this.single.get(0) & 0xFF};
}
} catch (IOException e) {
else
{
single.clear();
int b = reader.read( single );
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
}
}
catch( IOException e )
{
return null;
}
}
@ -136,29 +147,30 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} if we are at the end.
*/
@LuaFunction
public final Object[] readAll() throws LuaException {
this.checkOpen();
try {
public final Object[] readAll() throws LuaException
{
checkOpen();
try
{
int expected = 32;
if (this.seekable != null) {
expected = Math.max(expected, (int) (this.seekable.size() - this.seekable.position()));
}
ByteArrayOutputStream stream = new ByteArrayOutputStream(expected);
if( seekable != null ) expected = Math.max( expected, (int) (seekable.size() - seekable.position()) );
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
ByteBuffer buf = ByteBuffer.allocate(8192);
ByteBuffer buf = ByteBuffer.allocate( 8192 );
boolean readAnything = false;
while (true) {
while( true )
{
buf.clear();
int r = this.reader.read(buf);
if (r == -1) {
break;
}
int r = reader.read( buf );
if( r == -1 ) break;
readAnything = true;
stream.write(buf.array(), 0, r);
stream.write( buf.array(), 0, r );
}
return readAnything ? new Object[] {stream.toByteArray()} : null;
} catch (IOException e) {
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{
return null;
}
}
@ -172,65 +184,70 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The read line or {@code nil} if at the end of the file.
*/
@LuaFunction
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
this.checkOpen();
boolean withTrailing = withTrailingArg.orElse(false);
try {
public final Object[] readLine( Optional<Boolean> withTrailingArg ) throws LuaException
{
checkOpen();
boolean withTrailing = withTrailingArg.orElse( false );
try
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean readAnything = false, readRc = false;
while (true) {
this.single.clear();
int read = this.reader.read(this.single);
if (read <= 0) {
while( true )
{
single.clear();
int read = reader.read( single );
if( read <= 0 )
{
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
// back.
if (readRc) {
stream.write('\r');
}
return readAnything ? new Object[] {stream.toByteArray()} : null;
if( readRc ) stream.write( '\r' );
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
readAnything = true;
byte chr = this.single.get(0);
if (chr == '\n') {
if (withTrailing) {
if (readRc) {
stream.write('\r');
}
stream.write(chr);
byte chr = single.get( 0 );
if( chr == '\n' )
{
if( withTrailing )
{
if( readRc ) stream.write( '\r' );
stream.write( chr );
}
return new Object[] {stream.toByteArray()};
} else {
return new Object[] { stream.toByteArray() };
}
else
{
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
// previous behaviour of the io library.
if (readRc) {
stream.write('\r');
}
if( readRc ) stream.write( '\r' );
readRc = chr == '\r';
if (!readRc) {
stream.write(chr);
}
if( !readRc ) stream.write( chr );
}
}
} catch (IOException e) {
}
catch( IOException e )
{
return null;
}
}
public static class Seekable extends BinaryReadableHandle {
Seekable(SeekableByteChannel seekable, Closeable closeable) {
super(seekable, seekable, closeable);
public static class Seekable extends BinaryReadableHandle
{
Seekable( SeekableByteChannel seekable, TrackingCloseable closeable )
{
super( seekable, seekable, closeable );
}
/**
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset given by {@code offset}, relative to a
* start position determined by {@code whence}:
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset
* given by {@code offset}, relative to a start position determined by {@code whence}:
*
* - {@code "set"}: {@code offset} is relative to the beginning of the file. - {@code "cur"}: Relative to the current position. This is the default.
* - {@code "set"}: {@code offset} is relative to the beginning of the file.
* - {@code "cur"}: Relative to the current position. This is the default.
* - {@code "end"}: Relative to the end of the file.
*
* In case of success, {@code seek} returns the new file position from the beginning of the file.
@ -244,9 +261,10 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn string The reason seeking failed.
*/
@LuaFunction
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
this.checkOpen();
return handleSeek(this.seekable, whence, offset);
public final Object[] seek( Optional<String> whence, Optional<Long> offset ) throws LuaException
{
checkOpen();
return handleSeek( seekable, whence, offset );
}
}
}

View File

@ -3,10 +3,14 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import java.io.Closeable;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.LuaValues;
import dan200.computercraft.core.filesystem.TrackingCloseable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@ -14,34 +18,34 @@ import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Optional;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.LuaValues;
/**
* A file handle opened by {@link dan200.computercraft.core.apis.FSAPI#open} using the {@code "wb"} or {@code "ab"} modes.
* A file handle opened by {@link dan200.computercraft.core.apis.FSAPI#open} using the {@code "wb"} or {@code "ab"}
* modes.
*
* @cc.module fs.BinaryWriteHandle
*/
public class BinaryWritableHandle extends HandleGeneric {
final SeekableByteChannel seekable;
public class BinaryWritableHandle extends HandleGeneric
{
private final WritableByteChannel writer;
private final ByteBuffer single = ByteBuffer.allocate(1);
final SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
protected BinaryWritableHandle(WritableByteChannel writer, SeekableByteChannel seekable, Closeable closeable) {
super(closeable);
protected BinaryWritableHandle( WritableByteChannel writer, SeekableByteChannel seekable, TrackingCloseable closeable )
{
super( closeable );
this.writer = writer;
this.seekable = seekable;
}
public static BinaryWritableHandle of(WritableByteChannel channel) {
return of(channel, channel);
public static BinaryWritableHandle of( WritableByteChannel channel, TrackingCloseable closeable )
{
SeekableByteChannel seekable = asSeekable( channel );
return seekable == null ? new BinaryWritableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
}
public static BinaryWritableHandle of(WritableByteChannel channel, Closeable closeable) {
SeekableByteChannel seekable = asSeekable(channel);
return seekable == null ? new BinaryWritableHandle(channel, null, closeable) : new Seekable(seekable, closeable);
public static BinaryWritableHandle of( WritableByteChannel channel )
{
return of( channel, new TrackingCloseable.Impl( channel ) );
}
/**
@ -53,24 +57,33 @@ public class BinaryWritableHandle extends HandleGeneric {
* @cc.tparam [2] string The string to write.
*/
@LuaFunction
public final void write(IArguments arguments) throws LuaException {
this.checkOpen();
try {
Object arg = arguments.get(0);
if (arg instanceof Number) {
public final void write( IArguments arguments ) throws LuaException
{
checkOpen();
try
{
Object arg = arguments.get( 0 );
if( arg instanceof Number )
{
int number = ((Number) arg).intValue();
this.single.clear();
this.single.put((byte) number);
this.single.flip();
single.clear();
single.put( (byte) number );
single.flip();
this.writer.write(this.single);
} else if (arg instanceof String) {
this.writer.write(arguments.getBytes(0));
} else {
throw LuaValues.badArgumentOf(0, "string or number", arg);
writer.write( single );
}
} catch (IOException e) {
throw new LuaException(e.getMessage());
else if( arg instanceof String )
{
writer.write( arguments.getBytes( 0 ) );
}
else
{
throw LuaValues.badArgumentOf( 0, "string or number", arg );
}
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
@ -80,27 +93,32 @@ public class BinaryWritableHandle extends HandleGeneric {
* @throws LuaException If the file has been closed.
*/
@LuaFunction
public final void flush() throws LuaException {
this.checkOpen();
try {
public final void flush() throws LuaException
{
checkOpen();
try
{
// Technically this is not needed
if (this.writer instanceof FileChannel) {
((FileChannel) this.writer).force(false);
}
} catch (IOException ignored) {
if( writer instanceof FileChannel ) ((FileChannel) writer).force( false );
}
catch( IOException ignored )
{
}
}
public static class Seekable extends BinaryWritableHandle {
public Seekable(SeekableByteChannel seekable, Closeable closeable) {
super(seekable, seekable, closeable);
public static class Seekable extends BinaryWritableHandle
{
public Seekable( SeekableByteChannel seekable, TrackingCloseable closeable )
{
super( seekable, seekable, closeable );
}
/**
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset given by {@code offset}, relative to a
* start position determined by {@code whence}:
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset
* given by {@code offset}, relative to a start position determined by {@code whence}:
*
* - {@code "set"}: {@code offset} is relative to the beginning of the file. - {@code "cur"}: Relative to the current position. This is the default.
* - {@code "set"}: {@code offset} is relative to the beginning of the file.
* - {@code "cur"}: Relative to the current position. This is the default.
* - {@code "end"}: Relative to the end of the file.
*
* In case of success, {@code seek} returns the new file position from the beginning of the file.
@ -114,9 +132,10 @@ public class BinaryWritableHandle extends HandleGeneric {
* @cc.treturn string The reason seeking failed.
*/
@LuaFunction
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
this.checkOpen();
return handleSeek(this.seekable, whence, offset);
public final Object[] seek( Optional<String> whence, Optional<Long> offset ) throws LuaException
{
checkOpen();
return handleSeek( seekable, whence, offset );
}
}
}

View File

@ -3,11 +3,14 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
@ -17,41 +20,27 @@ import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import javax.annotation.Nonnull;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
/**
* A file handle opened with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)} with the {@code "r"} mode.
* A file handle opened with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)} with the {@code "r"}
* mode.
*
* @cc.module fs.ReadHandle
*/
public class EncodedReadableHandle extends HandleGeneric {
public class EncodedReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final BufferedReader reader;
public EncodedReadableHandle(@Nonnull BufferedReader reader) {
this(reader, reader);
}
public EncodedReadableHandle(@Nonnull BufferedReader reader, @Nonnull Closeable closable) {
super(closable);
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull TrackingCloseable closable )
{
super( closable );
this.reader = reader;
}
public static BufferedReader openUtf8(ReadableByteChannel channel) {
return open(channel, StandardCharsets.UTF_8);
}
public static BufferedReader open(ReadableByteChannel channel, Charset charset) {
// Create a charset decoder with the same properties as StreamDecoder does for
// InputStreams: namely, replace everything instead of erroring.
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
return new BufferedReader(Channels.newReader(channel, decoder, -1));
public EncodedReadableHandle( @Nonnull BufferedReader reader )
{
this( reader, new TrackingCloseable.Impl( reader ) );
}
/**
@ -63,21 +52,26 @@ public class EncodedReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The read line or {@code nil} if at the end of the file.
*/
@LuaFunction
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
this.checkOpen();
boolean withTrailing = withTrailingArg.orElse(false);
try {
String line = this.reader.readLine();
if (line != null) {
public final Object[] readLine( Optional<Boolean> withTrailingArg ) throws LuaException
{
checkOpen();
boolean withTrailing = withTrailingArg.orElse( false );
try
{
String line = reader.readLine();
if( line != null )
{
// While this is technically inaccurate, it's better than nothing
if (withTrailing) {
line += "\n";
}
return new Object[] {line};
} else {
if( withTrailing ) line += "\n";
return new Object[] { line };
}
else
{
return null;
}
} catch (IOException e) {
}
catch( IOException e )
{
return null;
}
}
@ -90,20 +84,26 @@ public class EncodedReadableHandle extends HandleGeneric {
* @cc.treturn nil|string The remaining contents of the file, or {@code nil} if we are at the end.
*/
@LuaFunction
public final Object[] readAll() throws LuaException {
this.checkOpen();
try {
public final Object[] readAll() throws LuaException
{
checkOpen();
try
{
StringBuilder result = new StringBuilder();
String line = this.reader.readLine();
while (line != null) {
result.append(line);
line = this.reader.readLine();
if (line != null) {
result.append("\n");
String line = reader.readLine();
while( line != null )
{
result.append( line );
line = reader.readLine();
if( line != null )
{
result.append( "\n" );
}
}
return new Object[] {result.toString()};
} catch (IOException e) {
return new Object[] { result.toString() };
}
catch( IOException e )
{
return null;
}
}
@ -118,50 +118,71 @@ public class EncodedReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The read characters, or {@code nil} if at the of the file.
*/
@LuaFunction
public final Object[] read(Optional<Integer> countA) throws LuaException {
this.checkOpen();
try {
int count = countA.orElse(1);
if (count < 0) {
public final Object[] read( Optional<Integer> countA ) throws LuaException
{
checkOpen();
try
{
int count = countA.orElse( 1 );
if( count < 0 )
{
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException("Cannot read a negative number of characters");
} else if (count <= BUFFER_SIZE) {
throw new LuaException( "Cannot read a negative number of characters" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
char[] chars = new char[count];
int read = this.reader.read(chars);
int read = reader.read( chars );
return read < 0 ? null : new Object[] {new String(chars, 0, read)};
} else {
return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
}
else
{
// If we've got a large count, read in bunches of 8192.
char[] buffer = new char[BUFFER_SIZE];
// Read the initial set of characters, failing if none are read.
int read = this.reader.read(buffer, 0, Math.min(buffer.length, count));
if (read < 0) {
return null;
}
int read = reader.read( buffer, 0, Math.min( buffer.length, count ) );
if( read < 0 ) return null;
StringBuilder out = new StringBuilder(read);
StringBuilder out = new StringBuilder( read );
int totalRead = read;
out.append(buffer, 0, read);
out.append( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while (read >= BUFFER_SIZE && totalRead < count) {
read = this.reader.read(buffer, 0, Math.min(BUFFER_SIZE, count - totalRead));
if (read < 0) {
break;
}
while( read >= BUFFER_SIZE && totalRead < count )
{
read = reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
if( read < 0 ) break;
totalRead += read;
out.append(buffer, 0, read);
out.append( buffer, 0, read );
}
return new Object[] {out.toString()};
return new Object[] { out.toString() };
}
} catch (IOException e) {
}
catch( IOException e )
{
return null;
}
}
public static BufferedReader openUtf8( ReadableByteChannel channel )
{
return open( channel, StandardCharsets.UTF_8 );
}
public static BufferedReader open( ReadableByteChannel channel, Charset charset )
{
// Create a charset decoder with the same properties as StreamDecoder does for
// InputStreams: namely, replace everything instead of erroring.
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput( CodingErrorAction.REPLACE )
.onUnmappableCharacter( CodingErrorAction.REPLACE );
return new BufferedReader( Channels.newReader( channel, decoder, -1 ) );
}
}

View File

@ -3,11 +3,16 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
@ -16,39 +21,21 @@ import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.StringUtil;
/**
* A file handle opened by {@link dan200.computercraft.core.apis.FSAPI#open} using the {@code "w"} or {@code "a"} modes.
*
* @cc.module fs.WriteHandle
*/
public class EncodedWritableHandle extends HandleGeneric {
public class EncodedWritableHandle extends HandleGeneric
{
private final BufferedWriter writer;
public EncodedWritableHandle(@Nonnull BufferedWriter writer, @Nonnull Closeable closable) {
super(closable);
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull TrackingCloseable closable )
{
super( closable );
this.writer = writer;
}
public static BufferedWriter openUtf8(WritableByteChannel channel) {
return open(channel, StandardCharsets.UTF_8);
}
public static BufferedWriter open(WritableByteChannel channel, Charset charset) {
// Create a charset encoder with the same properties as StreamEncoder does for
// OutputStreams: namely, replace everything instead of erroring.
CharsetEncoder encoder = charset.newEncoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
return new BufferedWriter(Channels.newWriter(channel, encoder, -1));
}
/**
* Write a string of characters to the file.
*
@ -57,13 +44,17 @@ public class EncodedWritableHandle extends HandleGeneric {
* @cc.param value The value to write to the file.
*/
@LuaFunction
public final void write(IArguments args) throws LuaException {
this.checkOpen();
String text = StringUtil.toString(args.get(0));
try {
this.writer.write(text, 0, text.length());
} catch (IOException e) {
throw new LuaException(e.getMessage());
public final void write( IArguments args ) throws LuaException
{
checkOpen();
String text = StringUtil.toString( args.get( 0 ) );
try
{
writer.write( text, 0, text.length() );
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
@ -75,14 +66,18 @@ public class EncodedWritableHandle extends HandleGeneric {
* @cc.param value The value to write to the file.
*/
@LuaFunction
public final void writeLine(IArguments args) throws LuaException {
this.checkOpen();
String text = StringUtil.toString(args.get(0));
try {
this.writer.write(text, 0, text.length());
this.writer.newLine();
} catch (IOException e) {
throw new LuaException(e.getMessage());
public final void writeLine( IArguments args ) throws LuaException
{
checkOpen();
String text = StringUtil.toString( args.get( 0 ) );
try
{
writer.write( text, 0, text.length() );
writer.newLine();
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
@ -92,11 +87,30 @@ public class EncodedWritableHandle extends HandleGeneric {
* @throws LuaException If the file has been closed.
*/
@LuaFunction
public final void flush() throws LuaException {
this.checkOpen();
try {
this.writer.flush();
} catch (IOException ignored) {
public final void flush() throws LuaException
{
checkOpen();
try
{
writer.flush();
}
catch( IOException ignored )
{
}
}
public static BufferedWriter openUtf8( WritableByteChannel channel )
{
return open( channel, StandardCharsets.UTF_8 );
}
public static BufferedWriter open( WritableByteChannel channel, Charset charset )
{
// Create a charset encoder with the same properties as StreamEncoder does for
// OutputStreams: namely, replace everything instead of erroring.
CharsetEncoder encoder = charset.newEncoder()
.onMalformedInput( CodingErrorAction.REPLACE )
.onUnmappableCharacter( CodingErrorAction.REPLACE );
return new BufferedWriter( Channels.newWriter( channel, encoder, -1 ) );
}
}

View File

@ -3,79 +3,38 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.handles;
import java.io.Closeable;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.SeekableByteChannel;
import java.util.Optional;
import javax.annotation.Nonnull;
public abstract class HandleGeneric
{
private TrackingCloseable closeable;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.IoUtil;
public abstract class HandleGeneric {
private Closeable closable;
private boolean open = true;
protected HandleGeneric(@Nonnull Closeable closable) {
this.closable = closable;
protected HandleGeneric( @Nonnull TrackingCloseable closeable )
{
this.closeable = closeable;
}
/**
* Shared implementation for various file handle types.
*
* @param channel The channel to seek in
* @param whence The seeking mode.
* @param offset The offset to seek to.
* @return The new position of the file, or null if some error occurred.
* @throws LuaException If the arguments were invalid
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
*/
protected static Object[] handleSeek(SeekableByteChannel channel, Optional<String> whence, Optional<Long> offset) throws LuaException {
long actualOffset = offset.orElse(0L);
try {
switch (whence.orElse("cur")) {
case "set":
channel.position(actualOffset);
break;
case "cur":
channel.position(channel.position() + actualOffset);
break;
case "end":
channel.position(channel.size() + actualOffset);
break;
default:
throw new LuaException("bad argument #1 to 'seek' (invalid option '" + whence + "'");
}
return new Object[] {channel.position()};
} catch (IllegalArgumentException e) {
return new Object[] {
null,
"Position is negative"
};
} catch (IOException e) {
return null;
}
protected void checkOpen() throws LuaException
{
TrackingCloseable closeable = this.closeable;
if( closeable == null || !closeable.isOpen() ) throw new LuaException( "attempt to use a closed file" );
}
protected static SeekableByteChannel asSeekable(Channel channel) {
if (!(channel instanceof SeekableByteChannel)) {
return null;
}
SeekableByteChannel seekable = (SeekableByteChannel) channel;
try {
seekable.position(seekable.position());
return seekable;
} catch (IOException | UnsupportedOperationException e) {
return null;
}
protected final void close()
{
IoUtil.closeQuietly( closeable );
closeable = null;
}
/**
@ -85,22 +44,69 @@ public abstract class HandleGeneric {
*
* @throws LuaException If the file has already been closed.
*/
@LuaFunction ("close")
public final void doClose() throws LuaException {
this.checkOpen();
this.close();
@LuaFunction( "close" )
public final void doClose() throws LuaException
{
checkOpen();
close();
}
protected void checkOpen() throws LuaException {
if (!this.open) {
throw new LuaException("attempt to use a closed file");
/**
* Shared implementation for various file handle types.
*
* @param channel The channel to seek in
* @param whence The seeking mode.
* @param offset The offset to seek to.
* @return The new position of the file, or null if some error occurred.
* @throws LuaException If the arguments were invalid
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
*/
protected static Object[] handleSeek( SeekableByteChannel channel, Optional<String> whence, Optional<Long> offset ) throws LuaException
{
long actualOffset = offset.orElse( 0L );
try
{
switch( whence.orElse( "cur" ) )
{
case "set":
channel.position( actualOffset );
break;
case "cur":
channel.position( channel.position() + actualOffset );
break;
case "end":
channel.position( channel.size() + actualOffset );
break;
default:
throw new LuaException( "bad argument #1 to 'seek' (invalid option '" + whence + "'" );
}
return new Object[] { channel.position() };
}
catch( IllegalArgumentException e )
{
return new Object[] { null, "Position is negative" };
}
catch( IOException e )
{
return null;
}
}
protected final void close() {
this.open = false;
protected static SeekableByteChannel asSeekable( Channel channel )
{
if( !(channel instanceof SeekableByteChannel) ) return null;
IoUtil.closeQuietly(this.closable);
this.closable = null;
SeekableByteChannel seekable = (SeekableByteChannel) channel;
try
{
seekable.position( seekable.position() );
return seekable;
}
catch( IOException | UnsupportedOperationException e )
{
return null;
}
}
}

View File

@ -3,19 +3,8 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http.request;
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
import java.io.Closeable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
@ -29,22 +18,20 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.*;
public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpObject> implements Closeable {
import java.io.Closeable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpObject> implements Closeable
{
/**
* Same as {@link io.netty.handler.codec.MessageAggregator}.
*/
@ -53,16 +40,19 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
private static final byte[] EMPTY_BYTES = new byte[0];
private final HttpRequest request;
private boolean closed = false;
private final URI uri;
private final HttpMethod method;
private final Options options;
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
private boolean closed = false;
private Charset responseCharset;
private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
private HttpResponseStatus responseStatus;
private CompositeByteBuf responseBody;
HttpRequestHandler(HttpRequest request, URI uri, HttpMethod method, Options options) {
HttpRequestHandler( HttpRequest request, URI uri, HttpMethod method, Options options )
{
this.request = request;
this.uri = uri;
@ -71,203 +61,199 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (this.request.checkClosed()) {
return;
}
public void channelActive( ChannelHandlerContext ctx ) throws Exception
{
if( request.checkClosed() ) return;
ByteBuf body = this.request.body();
body.resetReaderIndex()
.retain();
ByteBuf body = request.body();
body.resetReaderIndex().retain();
String requestUri = this.uri.getRawPath();
if (this.uri.getRawQuery() != null) {
requestUri += "?" + this.uri.getRawQuery();
}
String requestUri = uri.getRawPath();
if( uri.getRawQuery() != null ) requestUri += "?" + uri.getRawQuery();
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body);
request.setMethod(this.method);
request.headers()
.set(this.request.headers());
FullHttpRequest request = new DefaultFullHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body );
request.setMethod( method );
request.headers().set( this.request.headers() );
// We force some headers to be always applied
if (!request.headers()
.contains(HttpHeaderNames.ACCEPT_CHARSET)) {
request.headers()
.set(HttpHeaderNames.ACCEPT_CHARSET, "UTF-8");
if( !request.headers().contains( HttpHeaderNames.ACCEPT_CHARSET ) )
{
request.headers().set( HttpHeaderNames.ACCEPT_CHARSET, "UTF-8" );
}
if (!request.headers()
.contains(HttpHeaderNames.USER_AGENT)) {
request.headers()
.set(HttpHeaderNames.USER_AGENT,
this.request.environment()
.getComputerEnvironment()
.getUserAgent());
}
request.headers()
.set(HttpHeaderNames.HOST, this.uri.getPort() < 0 ? this.uri.getHost() : this.uri.getHost() + ":" + this.uri.getPort());
request.headers()
.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
request.headers().set( HttpHeaderNames.HOST, uri.getPort() < 0 ? uri.getHost() : uri.getHost() + ":" + uri.getPort() );
request.headers().set( HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE );
ctx.channel()
.writeAndFlush(request);
ctx.channel().writeAndFlush( request );
super.channelActive(ctx);
super.channelActive( ctx );
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (!this.closed) {
this.request.failure("Could not connect");
}
super.channelInactive(ctx);
public void channelInactive( ChannelHandlerContext ctx ) throws Exception
{
if( !closed ) request.failure( "Could not connect" );
super.channelInactive( ctx );
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (ComputerCraft.logPeripheralErrors) {
ComputerCraft.log.error("Error handling HTTP response", cause);
}
this.request.failure(cause);
}
public void channelRead0( ChannelHandlerContext ctx, HttpObject message )
{
if( closed || request.checkClosed() ) return;
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject message) {
if (this.closed || this.request.checkClosed()) {
return;
}
if (message instanceof HttpResponse) {
if( message instanceof HttpResponse )
{
HttpResponse response = (HttpResponse) message;
if (this.request.redirects.get() > 0) {
URI redirect = this.getRedirect(response.status(), response.headers());
if (redirect != null && !this.uri.equals(redirect) && this.request.redirects.getAndDecrement() > 0) {
if( request.redirects.get() > 0 )
{
URI redirect = getRedirect( response.status(), response.headers() );
if( redirect != null && !uri.equals( redirect ) && request.redirects.getAndDecrement() > 0 )
{
// If we have a redirect, and don't end up at the same place, then follow it.
// We mark ourselves as disposed first though, to avoid firing events when the channel
// becomes inactive or disposed.
this.closed = true;
closed = true;
ctx.close();
try {
HttpRequest.checkUri(redirect);
} catch (HTTPRequestException e) {
try
{
HttpRequest.checkUri( redirect );
}
catch( HTTPRequestException e )
{
// If we cannot visit this uri, then fail.
this.request.failure(e.getMessage());
request.failure( e.getMessage() );
return;
}
this.request.request(redirect,
response.status()
.code() == 303 ? HttpMethod.GET : this.method);
request.request( redirect, response.status().code() == 303 ? HttpMethod.GET : method );
return;
}
}
this.responseCharset = HttpUtil.getCharset(response, StandardCharsets.UTF_8);
this.responseStatus = response.status();
this.responseHeaders.add(response.headers());
responseCharset = HttpUtil.getCharset( response, StandardCharsets.UTF_8 );
responseStatus = response.status();
responseHeaders.add( response.headers() );
}
if (message instanceof HttpContent) {
if( message instanceof HttpContent )
{
HttpContent content = (HttpContent) message;
if (this.responseBody == null) {
this.responseBody = ctx.alloc()
.compositeBuffer(DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS);
if( responseBody == null )
{
responseBody = ctx.alloc().compositeBuffer( DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS );
}
ByteBuf partial = content.content();
if (partial.isReadable()) {
if( partial.isReadable() )
{
// If we've read more than we're allowed to handle, abort as soon as possible.
if (this.options.maxDownload != 0 && this.responseBody.readableBytes() + partial.readableBytes() > this.options.maxDownload) {
this.closed = true;
if( options.maxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > options.maxDownload )
{
closed = true;
ctx.close();
this.request.failure("Response is too large");
request.failure( "Response is too large" );
return;
}
this.responseBody.addComponent(true, partial.retain());
responseBody.addComponent( true, partial.retain() );
}
if (message instanceof LastHttpContent) {
if( message instanceof LastHttpContent )
{
LastHttpContent last = (LastHttpContent) message;
this.responseHeaders.add(last.trailingHeaders());
responseHeaders.add( last.trailingHeaders() );
// Set the content length, if not already given.
if (this.responseHeaders.contains(HttpHeaderNames.CONTENT_LENGTH)) {
this.responseHeaders.set(HttpHeaderNames.CONTENT_LENGTH, this.responseBody.readableBytes());
if( responseHeaders.contains( HttpHeaderNames.CONTENT_LENGTH ) )
{
responseHeaders.set( HttpHeaderNames.CONTENT_LENGTH, responseBody.readableBytes() );
}
ctx.close();
this.sendResponse();
sendResponse();
}
}
}
@Override
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
request.failure( cause );
}
private void sendResponse()
{
// Read the ByteBuf into a channel.
CompositeByteBuf body = responseBody;
byte[] bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes( body );
// Decode the headers
HttpResponseStatus status = responseStatus;
Map<String, String> headers = new HashMap<>();
for( Map.Entry<String, String> header : responseHeaders )
{
String existing = headers.get( header.getKey() );
headers.put( header.getKey(), existing == null ? header.getValue() : existing + "," + header.getValue() );
}
// Fire off a stats event
request.environment().addTrackingChange( TrackingField.HTTP_DOWNLOAD, getHeaderSize( responseHeaders ) + bytes.length );
// Prepare to queue an event
ArrayByteChannel contents = new ArrayByteChannel( bytes );
HandleGeneric reader = request.isBinary()
? BinaryReadableHandle.of( contents )
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, responseCharset ) );
HttpResponseHandle stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers );
if( status.code() >= 200 && status.code() < 400 )
{
request.success( stream );
}
else
{
request.failure( status.reasonPhrase(), stream );
}
}
/**
* Determine the redirect from this response.
*
* @param status The status of the HTTP response.
* @param status The status of the HTTP response.
* @param headers The headers of the HTTP response.
* @return The URI to redirect to, or {@code null} if no redirect should occur.
*/
private URI getRedirect(HttpResponseStatus status, HttpHeaders headers) {
private URI getRedirect( HttpResponseStatus status, HttpHeaders headers )
{
int code = status.code();
if (code < 300 || code > 307 || code == 304 || code == 306) {
if( code < 300 || code > 307 || code == 304 || code == 306 ) return null;
String location = headers.get( HttpHeaderNames.LOCATION );
if( location == null ) return null;
try
{
return uri.resolve( new URI( location ) );
}
catch( IllegalArgumentException | URISyntaxException e )
{
return null;
}
String location = headers.get(HttpHeaderNames.LOCATION);
if (location == null) {
return null;
}
try {
return this.uri.resolve(new URI( location ));
} catch( IllegalArgumentException | URISyntaxException e ) {
return null;
}
}
private void sendResponse() {
// Read the ByteBuf into a channel.
CompositeByteBuf body = this.responseBody;
byte[] bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes(body);
// Decode the headers
HttpResponseStatus status = this.responseStatus;
Map<String, String> headers = new HashMap<>();
for (Map.Entry<String, String> header : this.responseHeaders) {
String existing = headers.get(header.getKey());
headers.put(header.getKey(), existing == null ? header.getValue() : existing + "," + header.getValue());
}
// Fire off a stats event
this.request.environment()
.addTrackingChange(TrackingField.HTTP_DOWNLOAD, getHeaderSize(this.responseHeaders) + bytes.length);
// Prepare to queue an event
ArrayByteChannel contents = new ArrayByteChannel(bytes);
HandleGeneric reader = this.request.isBinary() ? BinaryReadableHandle.of(contents) : new EncodedReadableHandle(EncodedReadableHandle.open(contents,
this.responseCharset));
HttpResponseHandle stream = new HttpResponseHandle(reader, status.code(), status.reasonPhrase(), headers);
if (status.code() >= 200 && status.code() < 400) {
this.request.success(stream);
} else {
this.request.failure(status.reasonPhrase(), stream);
}
}
@Override
public void close() {
this.closed = true;
if (this.responseBody != null) {
this.responseBody.release();
this.responseBody = null;
public void close()
{
closed = true;
if( responseBody != null )
{
responseBody.release();
responseBody = null;
}
}
}
}

View File

@ -64,9 +64,9 @@ public final class Generator<T> {
private final Function<T, T> wrap;
private final LoadingCache<Method, Optional<T>> methodCache = CacheBuilder.newBuilder()
.build(CacheLoader.from(this::build));
.build(CacheLoader.from(catching(this::build, Optional.empty())));
private final LoadingCache<Class<?>, List<NamedMethod<T>>> classCache = CacheBuilder.newBuilder()
.build(CacheLoader.from(this::build));
.build(CacheLoader.from(catching(this::build, Collections.emptyList())));
Generator(Class<T> base, List<Class<?>> context, Function<T, T> wrap) {
this.base = base;
@ -374,4 +374,22 @@ public final class Generator<T> {
method.getName());
return null;
}
@SuppressWarnings( "Guava" )
private static <T, U> com.google.common.base.Function<T, U> catching( Function<T, U> function, U def )
{
return x -> {
try
{
return function.apply( x );
}
catch( Exception | LinkageError e )
{
// LinkageError due to possible codegen bugs and NoClassDefFoundError. The latter occurs when fetching
// methods on a class which references non-existent (i.e. client-only) types.
ComputerCraft.log.error( "Error generating @LuaFunctions", e );
return def;
}
};
}
}

View File

@ -3,7 +3,6 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import java.io.Closeable;
@ -13,30 +12,38 @@ import java.nio.channels.Channel;
/**
* Wraps some closeable object such as a buffered writer, and the underlying stream.
*
* When flushing a buffer before closing, some implementations will not close the buffer if an exception is thrown this causes us to release the channel,
* but not actually close it. This wrapper will attempt to close the wrapper (and so hopefully flush the channel), and then close the underlying channel.
* When flushing a buffer before closing, some implementations will not close the buffer if an exception is thrown
* this causes us to release the channel, but not actually close it. This wrapper will attempt to close the wrapper (and
* so hopefully flush the channel), and then close the underlying channel.
*
* @param <T> The type of the closeable object to write.
*/
class ChannelWrapper<T extends Closeable> implements Closeable {
class ChannelWrapper<T extends Closeable> implements Closeable
{
private final T wrapper;
private final Channel channel;
ChannelWrapper(T wrapper, Channel channel) {
ChannelWrapper( T wrapper, Channel channel )
{
this.wrapper = wrapper;
this.channel = channel;
}
@Override
public void close() throws IOException {
try {
this.wrapper.close();
} finally {
this.channel.close();
public void close() throws IOException
{
try
{
wrapper.close();
}
finally
{
channel.close();
}
}
public T get() {
return this.wrapper;
T get()
{
return wrapper;
}
}

View File

@ -3,48 +3,68 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import javax.annotation.Nonnull;
/**
* An alternative closeable implementation that will free up resources in the filesystem.
*
* The {@link FileSystem} maps weak references of this to its underlying object. If the wrapper has been disposed of (say, the Lua object referencing it has
* gone), then the wrapped object will be closed by the filesystem.
* The {@link FileSystem} maps weak references of this to its underlying object. If the wrapper has been disposed of
* (say, the Lua object referencing it has gone), then the wrapped object will be closed by the filesystem.
*
* Closing this will stop the filesystem tracking it, reducing the current descriptor count.
*
* In an ideal world, we'd just wrap the closeable. However, as we do some {@code instanceof} checks on the stream, it's not really possible as it'd require
* numerous instances.
* In an ideal world, we'd just wrap the closeable. However, as we do some {@code instanceof} checks
* on the stream, it's not really possible as it'd require numerous instances.
*
* @param <T> The type of writer or channel to wrap.
*/
public class FileSystemWrapper<T extends Closeable> implements Closeable {
final WeakReference<FileSystemWrapper<?>> self;
public class FileSystemWrapper<T extends Closeable> implements TrackingCloseable
{
private final FileSystem fileSystem;
final MountWrapper mount;
private final ChannelWrapper<T> closeable;
final WeakReference<FileSystemWrapper<?>> self;
private boolean isOpen = true;
FileSystemWrapper(FileSystem fileSystem, ChannelWrapper<T> closeable, ReferenceQueue<FileSystemWrapper<?>> queue) {
FileSystemWrapper( FileSystem fileSystem, MountWrapper mount, ChannelWrapper<T> closeable, ReferenceQueue<FileSystemWrapper<?>> queue )
{
this.fileSystem = fileSystem;
this.mount = mount;
this.closeable = closeable;
this.self = new WeakReference<>(this, queue);
self = new WeakReference<>( this, queue );
}
@Override
public void close() throws IOException {
this.fileSystem.removeFile(this);
this.closeable.close();
public void close() throws IOException
{
isOpen = false;
fileSystem.removeFile( this );
closeable.close();
}
void closeExternally()
{
isOpen = false;
IoUtil.closeQuietly( closeable );
}
@Override
public boolean isOpen()
{
return isOpen;
}
@Nonnull
public T get() {
return this.closeable.get();
public T get()
{
return closeable.get();
}
}

View File

@ -90,19 +90,30 @@ public final class ResourceMount implements IMount {
private void load() {
boolean hasAny = false;
FileEntry newRoot = new FileEntry(new Identifier(this.namespace, this.subPath));
for (Identifier file : this.manager.findResources(this.subPath, s -> true)) {
if (!file.getNamespace()
.equals(this.namespace)) {
continue;
}
String existingNamespace = null;
FileEntry newRoot = new FileEntry( new Identifier( namespace, subPath ) );
for( Identifier file : manager.findResources( subPath, s -> true ) )
{
existingNamespace = file.getNamespace();
if( !file.getNamespace().equals( namespace ) ) continue;
String localPath = FileSystem.toLocal(file.getPath(), this.subPath);
this.create(newRoot, localPath);
hasAny = true;
}
this.root = hasAny ? newRoot : null;
root = hasAny ? newRoot : null;
if( !hasAny )
{
ComputerCraft.log.warn("Cannot find any files under /data/{}/{} for resource mount.", namespace, subPath);
if( newRoot != null )
{
ComputerCraft.log.warn("There are files under /data/{}/{} though. Did you get the wrong namespace?", existingNamespace, subPath);
}
}
}
private void create(FileEntry lastEntry, String path) {

View File

@ -0,0 +1,44 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import java.io.Closeable;
import java.io.IOException;
/**
* A {@link Closeable} which knows when it has been closed.
*
* This is a quick (though racey) way of providing more friendly (and more similar to Lua)
* error messages to the user.
*/
public interface TrackingCloseable extends Closeable
{
boolean isOpen();
class Impl implements TrackingCloseable
{
private final Closeable object;
private boolean isOpen = true;
public Impl( Closeable object )
{
this.object = object;
}
@Override
public boolean isOpen()
{
return isOpen;
}
@Override
public void close() throws IOException
{
isOpen = false;
object.close();
}
}
}

View File

@ -3,168 +3,84 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.terminal;
public class TextBuffer {
private final char[] m_text;
public class TextBuffer
{
private final char[] text;
public TextBuffer(char c, int length) {
this.m_text = new char[length];
for (int i = 0; i < length; i++) {
this.m_text[i] = c;
}
public TextBuffer( char c, int length )
{
text = new char[length];
this.fill( c );
}
public TextBuffer(String text) {
this(text, 1);
public TextBuffer( String text )
{
this.text = text.toCharArray();
}
public TextBuffer(String text, int repetitions) {
int textLength = text.length();
this.m_text = new char[textLength * repetitions];
for (int i = 0; i < repetitions; i++) {
for (int j = 0; j < textLength; j++) {
this.m_text[j + i * textLength] = text.charAt(j);
}
}
public int length()
{
return text.length;
}
public TextBuffer(TextBuffer text) {
this(text, 1);
public void write( String text )
{
write( text, 0 );
}
public TextBuffer(TextBuffer text, int repetitions) {
int textLength = text.length();
this.m_text = new char[textLength * repetitions];
for (int i = 0; i < repetitions; i++) {
for (int j = 0; j < textLength; j++) {
this.m_text[j + i * textLength] = text.charAt(j);
}
}
}
public int length() {
return this.m_text.length;
}
public char charAt(int i) {
return this.m_text[i];
}
public String read() {
return this.read(0, this.m_text.length);
}
public String read(int start, int end) {
start = Math.max(start, 0);
end = Math.min(end, this.m_text.length);
int textLength = Math.max(end - start, 0);
return new String(this.m_text, start, textLength);
}
public String read(int start) {
return this.read(start, this.m_text.length);
}
public void write(String text) {
this.write(text, 0, text.length());
}
public void write(String text, int start, int end) {
public void write( String text, int start )
{
int pos = start;
start = Math.max(start, 0);
end = Math.min(end, pos + text.length());
end = Math.min(end, this.m_text.length);
for (int i = start; i < end; i++) {
this.m_text[i] = text.charAt(i - pos);
start = Math.max( start, 0 );
int end = Math.min( start + text.length(), pos + text.length() );
end = Math.min( end, this.text.length );
for( int i = start; i < end; i++ )
{
this.text[i] = text.charAt( i - pos );
}
}
public void write(String text, int start) {
this.write(text, start, start + text.length());
}
public void write(TextBuffer text) {
this.write(text, 0, text.length());
}
public void write(TextBuffer text, int start, int end) {
int pos = start;
start = Math.max(start, 0);
end = Math.min(end, pos + text.length());
end = Math.min(end, this.m_text.length);
for (int i = start; i < end; i++) {
this.m_text[i] = text.charAt(i - pos);
public void write( TextBuffer text )
{
int end = Math.min( text.length(), this.text.length );
for( int i = 0; i < end; i++ )
{
this.text[i] = text.charAt( i );
}
}
public void write(TextBuffer text, int start) {
this.write(text, start, start + text.length());
public void fill( char c )
{
fill( c, 0, text.length );
}
public void fill(char c) {
this.fill(c, 0, this.m_text.length);
}
public void fill(char c, int start, int end) {
start = Math.max(start, 0);
end = Math.min(end, this.m_text.length);
for (int i = start; i < end; i++) {
this.m_text[i] = c;
public void fill( char c, int start, int end )
{
start = Math.max( start, 0 );
end = Math.min( end, text.length );
for( int i = start; i < end; i++ )
{
text[i] = c;
}
}
public void fill(char c, int start) {
this.fill(c, start, this.m_text.length);
public char charAt( int i )
{
return text[i];
}
public void fill(String text) {
this.fill(text, 0, this.m_text.length);
}
public void fill(String text, int start, int end) {
int pos = start;
start = Math.max(start, 0);
end = Math.min(end, this.m_text.length);
int textLength = text.length();
for (int i = start; i < end; i++) {
this.m_text[i] = text.charAt((i - pos) % textLength);
public void setChar( int i, char c )
{
if( i >= 0 && i < text.length )
{
text[i] = c;
}
}
public void fill(String text, int start) {
this.fill(text, start, this.m_text.length);
public String toString()
{
return new String( text );
}
public void fill(TextBuffer text) {
this.fill(text, 0, this.m_text.length);
}
public void fill(TextBuffer text, int start, int end) {
int pos = start;
start = Math.max(start, 0);
end = Math.min(end, this.m_text.length);
int textLength = text.length();
for (int i = start; i < end; i++) {
this.m_text[i] = text.charAt((i - pos) % textLength);
}
}
public void fill(TextBuffer text, int start) {
this.fill(text, start, this.m_text.length);
}
public void setChar(int i, char c) {
if (i >= 0 && i < this.m_text.length) {
this.m_text[i] = c;
}
}
@Override
public String toString() {
return new String(this.m_text);
}
}
}

View File

@ -32,7 +32,7 @@ public class MixinWorld {
@Inject (method = "setBlockEntity", at = @At ("HEAD"))
public void setBlockEntity(BlockPos pos, @Nullable BlockEntity entity, CallbackInfo info) {
if (!World.isHeightInvalid(pos) && entity != null && !entity.isRemoved() && this.iteratingTickingBlockEntities) {
if (!World.isOutOfBuildLimitVertically(pos) && entity != null && !entity.isRemoved() && this.iteratingTickingBlockEntities) {
setWorld(entity, this);
}
}

View File

@ -31,7 +31,7 @@ public final class BundledRedstone {
}
public static int getDefaultOutput(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side) {
return World.method_24794(pos) ? DefaultBundledRedstoneProvider.getDefaultBundledRedstoneOutput(world, pos, side) : -1;
return World.isValid(pos) ? DefaultBundledRedstoneProvider.getDefaultBundledRedstoneOutput(world, pos, side) : -1;
}
public static int getOutput(World world, BlockPos pos, Direction side) {
@ -40,7 +40,7 @@ public final class BundledRedstone {
}
private static int getUnmaskedOutput(World world, BlockPos pos, Direction side) {
if (!World.method_24794(pos)) {
if (!World.isValid(pos)) {
return -1;
}

View File

@ -35,13 +35,11 @@ public final class Peripherals {
@Nullable
public static IPeripheral getPeripheral(World world, BlockPos pos, Direction side) {
return World.method_24794(pos) && !world.isClient ? getPeripheralAt(world, pos, side) : null;
return World.isValid(pos) && !world.isClient ? getPeripheralAt(world, pos, side) : null;
}
@Nullable
private static IPeripheral getPeripheralAt(World world, BlockPos pos, Direction side) {
BlockEntity block = world.getBlockEntity(pos);
// Try the handlers in order:
for (IPeripheralProvider peripheralProvider : providers) {
try {

View File

@ -3,61 +3,59 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Util;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.item.ItemStack;
public final class PocketUpgrades {
public final class PocketUpgrades
{
private static final Map<String, IPocketUpgrade> upgrades = new HashMap<>();
private static final IdentityHashMap<IPocketUpgrade, String> upgradeOwners = new IdentityHashMap<>();
private static final Map<IPocketUpgrade, String> upgradeOwners = new Object2ObjectLinkedOpenCustomHashMap<>( Util.identityHashStrategy() );
private PocketUpgrades() {}
public static synchronized void register(@Nonnull IPocketUpgrade upgrade) {
Objects.requireNonNull(upgrade, "upgrade cannot be null");
public static synchronized void register( @Nonnull IPocketUpgrade upgrade )
{
Objects.requireNonNull( upgrade, "upgrade cannot be null" );
String id = upgrade.getUpgradeID()
.toString();
IPocketUpgrade existing = upgrades.get(id);
if (existing != null) {
throw new IllegalStateException("Error registering '" + upgrade.getUnlocalisedAdjective() + " pocket computer'. UpgradeID '" + id + "' is " +
"already registered by '" + existing.getUnlocalisedAdjective() + " pocket computer'");
String id = upgrade.getUpgradeID().toString();
IPocketUpgrade existing = upgrades.get( id );
if( existing != null )
{
throw new IllegalStateException( "Error registering '" + upgrade.getUnlocalisedAdjective() + " pocket computer'. UpgradeID '" + id + "' is already registered by '" + existing.getUnlocalisedAdjective() + " pocket computer'" );
}
upgrades.put(id, upgrade);
upgrades.put( id, upgrade );
// Infer the mod id by the identifier of the upgrade. This is not how the forge api works, so it may break peripheral mods using the api.
// TODO: get the mod id of the mod that is currently being loaded.
ModContainer mc = FabricLoader.getInstance().getModContainer(upgrade.getUpgradeID().getNamespace()).orElseGet(null);
if( mc != null && mc.getMetadata().getId() != null ) upgradeOwners.put( upgrade, mc.getMetadata().getId() );
}
public static IPocketUpgrade get(String id) {
public static IPocketUpgrade get( String id )
{
// Fix a typo in the advanced modem upgrade's name. I'm sorry, I realise this is horrible.
if (id.equals("computercraft:advanved_modem")) {
id = "computercraft:advanced_modem";
}
if( id.equals( "computercraft:advanved_modem" ) ) id = "computercraft:advanced_modem";
return upgrades.get(id);
return upgrades.get( id );
}
public static IPocketUpgrade get(@Nonnull ItemStack stack) {
if (stack.isEmpty()) {
return null;
}
public static IPocketUpgrade get( @Nonnull ItemStack stack )
{
if( stack.isEmpty() ) return null;
for (IPocketUpgrade upgrade : upgrades.values()) {
for( IPocketUpgrade upgrade : upgrades.values() )
{
ItemStack craftingStack = upgrade.getCraftingItem();
if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && upgrade.isItemSuitable( stack ) )
{
@ -69,19 +67,22 @@ public final class PocketUpgrades {
}
@Nullable
public static String getOwner(IPocketUpgrade upgrade) {
return upgradeOwners.get(upgrade);
public static String getOwner( IPocketUpgrade upgrade )
{
return upgradeOwners.get( upgrade );
}
public static Iterable<IPocketUpgrade> getVanillaUpgrades() {
public static Iterable<IPocketUpgrade> getVanillaUpgrades()
{
List<IPocketUpgrade> vanilla = new ArrayList<>();
vanilla.add(ComputerCraftRegistry.PocketUpgrades.wirelessModemNormal);
vanilla.add(ComputerCraftRegistry.PocketUpgrades.wirelessModemAdvanced);
vanilla.add(ComputerCraftRegistry.PocketUpgrades.speaker);
vanilla.add( ComputerCraftRegistry.PocketUpgrades.wirelessModemNormal );
vanilla.add( ComputerCraftRegistry.PocketUpgrades.wirelessModemAdvanced );
vanilla.add( ComputerCraftRegistry.PocketUpgrades.speaker );
return vanilla;
}
public static Iterable<IPocketUpgrade> getUpgrades() {
return Collections.unmodifiableCollection(upgrades.values());
public static Iterable<IPocketUpgrade> getUpgrades()
{
return Collections.unmodifiableCollection( upgrades.values() );
}
}
}

View File

@ -3,9 +3,15 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
@ -13,86 +19,72 @@ import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public final class TurtleUpgrades
{
private static class Wrapper
{
final ITurtleUpgrade upgrade;
final String id;
final String modId;
boolean enabled;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.computer.core.ComputerFamily;
Wrapper( ITurtleUpgrade upgrade )
{
this.upgrade = upgrade;
this.id = upgrade.getUpgradeID()
.toString();
// TODO This should be the mod id of the mod the peripheral comes from
this.modId = ComputerCraft.MOD_ID;
this.enabled = true;
}
}
import net.minecraft.item.ItemStack;
private static ITurtleUpgrade[] vanilla;
public final class TurtleUpgrades {
private static final Map<String, ITurtleUpgrade> upgrades = new HashMap<>();
private static final IdentityHashMap<ITurtleUpgrade, Wrapper> wrappers = new IdentityHashMap<>();
private static ITurtleUpgrade[] vanilla;
private static boolean needsRebuild;
private TurtleUpgrades() {}
public static void register(@Nonnull ITurtleUpgrade upgrade) {
Objects.requireNonNull(upgrade, "upgrade cannot be null");
public static void register( @Nonnull ITurtleUpgrade upgrade )
{
Objects.requireNonNull( upgrade, "upgrade cannot be null" );
rebuild();
Wrapper wrapper = new Wrapper(upgrade);
Wrapper wrapper = new Wrapper( upgrade );
String id = wrapper.id;
ITurtleUpgrade existing = upgrades.get(id);
if (existing != null) {
throw new IllegalStateException("Error registering '" + upgrade.getUnlocalisedAdjective() + " Turtle'. Upgrade ID '" + id + "' is already " +
"registered by '" + existing.getUnlocalisedAdjective() + " Turtle'");
ITurtleUpgrade existing = upgrades.get( id );
if( existing != null )
{
throw new IllegalStateException( "Error registering '" + upgrade.getUnlocalisedAdjective() + " Turtle'. Upgrade ID '" + id + "' is already registered by '" + existing.getUnlocalisedAdjective() + " Turtle'" );
}
upgrades.put(id, upgrade);
wrappers.put(upgrade, wrapper);
}
/**
* Rebuild the cache of turtle upgrades. This is done before querying the cache or registering new upgrades.
*/
private static void rebuild() {
if (!needsRebuild) {
return;
}
upgrades.clear();
for (Wrapper wrapper : wrappers.values()) {
if (!wrapper.enabled) {
continue;
}
ITurtleUpgrade existing = upgrades.get(wrapper.id);
if (existing != null) {
ComputerCraft.log.error("Error registering '" + wrapper.upgrade.getUnlocalisedAdjective() + " Turtle'." + " Upgrade ID '" + wrapper.id +
"' is already registered by '" + existing.getUnlocalisedAdjective() + " Turtle'");
continue;
}
upgrades.put(wrapper.id, wrapper.upgrade);
}
needsRebuild = false;
upgrades.put( id, upgrade );
wrappers.put( upgrade, wrapper );
}
@Nullable
public static ITurtleUpgrade get(String id) {
public static ITurtleUpgrade get( String id )
{
rebuild();
return upgrades.get(id);
return upgrades.get( id );
}
@Nullable
public static String getOwner(@Nonnull ITurtleUpgrade upgrade) {
Wrapper wrapper = wrappers.get(upgrade);
public static String getOwner( @Nonnull ITurtleUpgrade upgrade )
{
Wrapper wrapper = wrappers.get( upgrade );
return wrapper != null ? wrapper.modId : null;
}
public static ITurtleUpgrade get(@Nonnull ItemStack stack) {
if (stack.isEmpty()) {
return null;
}
public static ITurtleUpgrade get( @Nonnull ItemStack stack )
{
if( stack.isEmpty() ) return null;
for (Wrapper wrapper : wrappers.values()) {
if (!wrapper.enabled) {
continue;
}
for( Wrapper wrapper : wrappers.values() )
{
if( !wrapper.enabled ) continue;
ItemStack craftingStack = wrapper.upgrade.getCraftingItem();
if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade.isItemSuitable( stack ) )
@ -104,8 +96,10 @@ public final class TurtleUpgrades {
return null;
}
public static Stream<ITurtleUpgrade> getVanillaUpgrades() {
if (vanilla == null) {
public static Stream<ITurtleUpgrade> getVanillaUpgrades()
{
if( vanilla == null )
{
vanilla = new ITurtleUpgrade[] {
// ComputerCraft upgrades
ComputerCraftRegistry.TurtleUpgrades.wirelessModemNormal,
@ -119,64 +113,69 @@ public final class TurtleUpgrades {
ComputerCraftRegistry.TurtleUpgrades.diamondShovel,
ComputerCraftRegistry.TurtleUpgrades.diamondHoe,
ComputerCraftRegistry.TurtleUpgrades.craftingTable,
ComputerCraftRegistry.TurtleUpgrades.netheritePickaxe,
};
}
return Arrays.stream(vanilla)
.filter(x -> x != null && wrappers.get(x).enabled);
return Arrays.stream( vanilla ).filter( x -> x != null && wrappers.get( x ).enabled );
}
public static Stream<ITurtleUpgrade> getUpgrades() {
return wrappers.values()
.stream()
.filter(x -> x.enabled)
.map(x -> x.upgrade);
public static Stream<ITurtleUpgrade> getUpgrades()
{
return wrappers.values().stream().filter( x -> x.enabled ).map( x -> x.upgrade );
}
public static boolean suitableForFamily(ComputerFamily family, ITurtleUpgrade upgrade) {
public static boolean suitableForFamily( ComputerFamily family, ITurtleUpgrade upgrade )
{
return true;
}
public static void enable(ITurtleUpgrade upgrade) {
Wrapper wrapper = wrappers.get(upgrade);
if (wrapper.enabled) {
return;
/**
* Rebuild the cache of turtle upgrades. This is done before querying the cache or registering new upgrades.
*/
private static void rebuild()
{
if( !needsRebuild ) return;
upgrades.clear();
for( Wrapper wrapper : wrappers.values() )
{
if( !wrapper.enabled ) continue;
ITurtleUpgrade existing = upgrades.get( wrapper.id );
if( existing != null )
{
ComputerCraft.log.error( "Error registering '" + wrapper.upgrade.getUnlocalisedAdjective() + " Turtle'." +
" Upgrade ID '" + wrapper.id + "' is already registered by '" + existing.getUnlocalisedAdjective() + " Turtle'" );
continue;
}
upgrades.put( wrapper.id, wrapper.upgrade );
}
needsRebuild = false;
}
public static void enable( ITurtleUpgrade upgrade )
{
Wrapper wrapper = wrappers.get( upgrade );
if( wrapper.enabled ) return;
wrapper.enabled = true;
needsRebuild = true;
}
public static void disable(ITurtleUpgrade upgrade) {
Wrapper wrapper = wrappers.get(upgrade);
if (!wrapper.enabled) {
return;
}
public static void disable( ITurtleUpgrade upgrade )
{
Wrapper wrapper = wrappers.get( upgrade );
if( !wrapper.enabled ) return;
wrapper.enabled = false;
upgrades.remove(wrapper.id);
upgrades.remove( wrapper.id );
}
public static void remove(ITurtleUpgrade upgrade) {
wrappers.remove(upgrade);
public static void remove( ITurtleUpgrade upgrade )
{
wrappers.remove( upgrade );
needsRebuild = true;
}
private static class Wrapper {
final ITurtleUpgrade upgrade;
final String id;
final String modId;
boolean enabled;
Wrapper(ITurtleUpgrade upgrade) {
this.upgrade = upgrade;
this.id = upgrade.getUpgradeID()
.toString();
// TODO This should be the mod id of the mod the peripheral comes from
this.modId = ComputerCraft.MOD_ID;
this.enabled = true;
}
}
}
}

View File

@ -17,7 +17,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import dan200.computercraft.api.turtle.FakePlayer;
import net.minecraft.entity.Entity;
import net.minecraft.server.command.CommandSource;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;

View File

@ -55,8 +55,9 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
this.some = some;
}
public static <T> RepeatArgumentType<T, T> some(ArgumentType<T> appender, SimpleCommandExceptionType missing) {
return new RepeatArgumentType<>(appender, List::add, true, missing);
public static <T> RepeatArgumentType<T, T> some( ArgumentType<T> appender, SimpleCommandExceptionType missing )
{
return new RepeatArgumentType<>( appender, List::add, false, missing );
}
public static <T> RepeatArgumentType<T, List<T>> someFlat(ArgumentType<List<T>> appender, SimpleCommandExceptionType missing) {

View File

@ -8,7 +8,6 @@ package dan200.computercraft.shared.common;
import javax.annotation.Nonnull;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.ColourTracker;
import dan200.computercraft.shared.util.ColourUtils;
@ -66,17 +65,10 @@ public final class ColourableRecipe extends SpecialCraftingRecipe {
if (stack.isEmpty()) {
continue;
}
if (stack.getItem() instanceof IColouredItem) {
colourable = stack;
} else {
DyeColor dye = ColourUtils.getStackColour(stack);
if (dye == null) {
continue;
}
Colour colour = Colour.fromInt(15 - dye.getId());
tracker.addColour(colour.getR(), colour.getG(), colour.getB());
else
{
DyeColor dye = ColourUtils.getStackColour( stack );
if( dye != null ) tracker.addColour( dye );
}
}

View File

@ -207,7 +207,7 @@ public class CommandAPI implements ILuaAPI {
World world = this.computer.getWorld();
BlockPos min = new BlockPos(Math.min(minX, maxX), Math.min(minY, maxY), Math.min(minZ, maxZ));
BlockPos max = new BlockPos(Math.max(minX, maxX), Math.max(minY, maxY), Math.max(minZ, maxZ));
if (!World.method_24794(min) || !World.method_24794(max)) {
if (!World.isValid(min) || !World.isValid(max)) {
throw new LuaException("Co-ordinates out of range");
}
@ -284,7 +284,7 @@ public class CommandAPI implements ILuaAPI {
// Get the details of the block
World world = this.computer.getWorld();
BlockPos position = new BlockPos(x, y, z);
if (World.method_24794(position)) {
if (World.isValid(position)) {
return getBlockInfo(world, position);
} else {
throw new LuaException("Co-ordinates out of range");

View File

@ -316,7 +316,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
@Nonnull
@Override
public String getHostString() {
return String.format("ComputerCraft %s (Minecraft %s)", ComputerCraftAPI.getInstalledVersion(), "1.16.2");
return String.format("ComputerCraft %s (Minecraft %s)", ComputerCraftAPI.getInstalledVersion(), "1.16.4");
}
@Nonnull

View File

@ -53,7 +53,9 @@ public class DiskRecipe extends SpecialCraftingRecipe {
return false;
}
redstoneFound = true;
} else if (ColourUtils.getStackColour(stack) != null) {
}
else if( ColourUtils.getStackColour( stack ) == null )
{
return false;
}
}
@ -74,14 +76,10 @@ public class DiskRecipe extends SpecialCraftingRecipe {
continue;
}
if (!this.paper.test(stack) && !this.redstone.test(stack)) {
DyeColor dye = ColourUtils.getStackColour(stack);
if (dye == null) {
continue;
}
Colour colour = Colour.VALUES[dye.getId()];
tracker.addColour(colour.getR(), colour.getG(), colour.getB());
if( !paper.test( stack ) && !redstone.test( stack ) )
{
DyeColor dye = ColourUtils.getStackColour( stack );
if( dye != null ) tracker.addColour( dye );
}
}

View File

@ -85,8 +85,8 @@ public class InventoryMethods implements GenericSource
* @link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail includes. More information can be fetched
* with {@link #getItemDetail}.
*
* The table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs` rather than
* `ipairs`.
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs`
* rather than `ipairs`.
*
* @param inventory The current inventory.
* @return All items in this inventory.

View File

@ -191,6 +191,10 @@ public class TurtleAPI implements ILuaAPI {
/**
* Place a block or item into the world in front of the turtle.
*
* "Placing" an item allows it to interact with blocks and entities in front of the turtle. For instance, buckets
* can pick up and place down fluids, and wheat can be used to breed cows. However, you cannot use {@link #place} to
* perform arbitrary block interactions, such as clicking buttons or flipping levers.
*
* @param args Arguments to place.
* @return The turtle command result.
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
@ -210,6 +214,7 @@ public class TurtleAPI implements ILuaAPI {
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
*/
@LuaFunction
public final MethodResult placeUp(IArguments args) {
@ -224,6 +229,7 @@ public class TurtleAPI implements ILuaAPI {
* @cc.tparam [opt] string text When placing a sign, set its contents to this text.
* @cc.treturn boolean Whether the block could be placed.
* @cc.treturn string|nil The reason the block was not placed.
* @see #place For more information about placing items.
*/
@LuaFunction
public final MethodResult placeDown(IArguments args) {
@ -380,16 +386,34 @@ public class TurtleAPI implements ILuaAPI {
return this.trackCommand(new TurtleDetectCommand(InteractDirection.DOWN));
}
/**
* Check if the block in front of the turtle is equal to the item in the currently selected slot.
*
* @return If the block and item are equal.
* @cc.treturn boolean If the block and item are equal.
*/
@LuaFunction
public final MethodResult compare() {
return this.trackCommand(new TurtleCompareCommand(InteractDirection.FORWARD));
}
/**
* Check if the block above the turtle is equal to the item in the currently selected slot.
*
* @return If the block and item are equal.
* @cc.treturn boolean If the block and item are equal.
*/
@LuaFunction
public final MethodResult compareUp() {
return this.trackCommand(new TurtleCompareCommand(InteractDirection.UP));
}
/**
* Check if the block below the turtle is equal to the item in the currently selected slot.
*
* @return If the block and item are equal.
* @cc.treturn boolean If the block and item are equal.
*/
@LuaFunction
public final MethodResult compareDown() {
return this.trackCommand(new TurtleCompareCommand(InteractDirection.DOWN));
@ -478,11 +502,56 @@ public class TurtleAPI implements ILuaAPI {
return this.trackCommand(new TurtleSuckCommand(InteractDirection.DOWN, checkCount(count)));
}
/**
* Get the maximum amount of fuel this turtle currently holds.
*
* @return The fuel level, or "unlimited".
* @cc.treturn[1] number The current amount of fuel a turtle this turtle has.
* @cc.treturn[2] "unlimited" If turtles do not consume fuel when moving.
* @see #getFuelLimit()
* @see #refuel(Optional)
*/
@LuaFunction
public final Object getFuelLevel() {
return this.turtle.isFuelNeeded() ? this.turtle.getFuelLevel() : "unlimited";
}
/**
* Refuel this turtle.
*
* While most actions a turtle can perform (such as digging or placing blocks), moving consumes fuel from the
* turtle's internal buffer. If a turtle has no fuel, it will not move.
*
* {@link #refuel} refuels the turtle, consuming fuel items (such as coal or lava buckets) from the currently
* selected slot and converting them into energy. This finishes once the turtle is fully refuelled or all items have
* been consumed.
*
* @param countA The maximum number of items to consume. One can pass `0` to check if an item is combustable or not.
* @return If this turtle could be refuelled.
* @throws LuaException If the refuel count is out of range.
* @cc.treturn[1] true If the turtle was refuelled.
* @cc.treturn[2] false If the turtle was not refuelled.
* @cc.treturn[2] string The reason the turtle was not refuelled (
* @cc.usage Refuel a turtle from the currently selected slot.
* <pre>{@code
* local level = turtle.getFuelLevel()
* if new_level == "unlimited" then error("Turtle does not need fuel", 0) end
*
* local ok, err = turtle.refuel()
* if ok then
* local new_level = turtle.getFuelLevel()
* print(("Refuelled %d, current level is %d"):format(new_level - level, new_level))
* else
* printError(err)
* end}</pre>
* @cc.usage Check if the current item is a valid fuel source.
* <pre>{@code
* local is_fuel, reason = turtle.refuel(0)
* if not is_fuel then printError(reason) end
* }</pre>
* @see #getFuelLevel()
* @see #getFuelLimit()
*/
@LuaFunction
public final MethodResult refuel(Optional<Integer> countA) throws LuaException {
int count = countA.orElse(Integer.MAX_VALUE);
@ -492,11 +561,29 @@ public class TurtleAPI implements ILuaAPI {
return this.trackCommand(new TurtleRefuelCommand(count));
}
/**
* Compare the item in the currently selected slot to the item in another slot.
*
* @param slot The slot to compare to.
* @return If the items are the same.
* @throws LuaException If the slot is out of range.
* @cc.treturn boolean If the two items are equal.
*/
@LuaFunction
public final MethodResult compareTo(int slot) throws LuaException {
return this.trackCommand(new TurtleCompareToCommand(checkSlot(slot)));
}
/**
* Move an item from the selected slot to another one.
*
* @param slotArg The slot to move this item to.
* @param countArg The maximum number of items to move.
* @return If the item was moved or not.
* @throws LuaException If the slot is out of range.
* @throws LuaException If the number of items is out of range.
* @cc.treturn boolean If some items were successfully moved.
*/
@LuaFunction
public final MethodResult transferTo(int slotArg, Optional<Integer> countArg) throws LuaException {
int slot = checkSlot(slotArg);
@ -515,16 +602,53 @@ public class TurtleAPI implements ILuaAPI {
return this.turtle.getSelectedSlot() + 1;
}
/**
* Get the maximum amount of fuel this turtle can hold.
*
* By default, normal turtles have a limit of 20,000 and advanced turtles of 100,000.
*
* @return The limit, or "unlimited".
* @cc.treturn[1] number The maximum amount of fuel a turtle can hold.
* @cc.treturn[2] "unlimited" If turtles do not consume fuel when moving.
* @see #getFuelLevel()
* @see #refuel(Optional)
*/
@LuaFunction
public final Object getFuelLimit() {
return this.turtle.isFuelNeeded() ? this.turtle.getFuelLimit() : "unlimited";
}
/**
* Equip (or unequip) an item on the left side of this turtle.
*
* This finds the item in the currently selected slot and attempts to equip it to the left side of the turtle. The
* previous upgrade is removed and placed into the turtle's inventory. If there is no item in the slot, the previous
* upgrade is removed, but no new one is equipped.
*
* @return Whether an item was equiped or not.
* @cc.treturn[1] true If the item was equipped.
* @cc.treturn[2] false If we could not equip the item.
* @cc.treturn[2] string The reason equipping this item failed.
* @see #equipRight()
*/
@LuaFunction
public final MethodResult equipLeft() {
return this.trackCommand(new TurtleEquipCommand(TurtleSide.LEFT));
}
/**
* Equip (or unequip) an item on the right side of this turtle.
*
* This finds the item in the currently selected slot and attempts to equip it to the right side of the turtle. The
* previous upgrade is removed and placed into the turtle's inventory. If there is no item in the slot, the previous
* upgrade is removed, but no new one is equipped.
*
* @return Whether an item was equiped or not.
* @cc.treturn[1] true If the item was equipped.
* @cc.treturn[2] false If we could not equip the item.
* @cc.treturn[2] string The reason equipping this item failed.
* @see #equipRight()
*/
@LuaFunction
public final MethodResult equipRight() {
return this.trackCommand(new TurtleEquipCommand(TurtleSide.RIGHT));

View File

@ -119,10 +119,10 @@ public class TurtleMoveCommand implements ITurtleCommand {
}
private static TurtleCommandResult canEnter(TurtlePlayer turtlePlayer, World world, BlockPos position) {
if (World.isHeightInvalid(position)) {
if (World.isOutOfBuildLimitVertically(position)) {
return TurtleCommandResult.failure(position.getY() < 0 ? "Too low to move" : "Too high to move");
}
if (!World.method_24794(position)) {
if (!World.isValid(position)) {
return TurtleCommandResult.failure("Cannot leave the world");
}

View File

@ -364,7 +364,7 @@ public class TurtlePlaceCommand implements ITurtleCommand {
private static boolean canDeployOnBlock(@Nonnull ItemPlacementContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position,
Direction side, boolean allowReplaceable, String[] outErrorMessage) {
World world = turtle.getWorld();
if (!World.method_24794(position) || world.isAir(position) || (context.getStack()
if (!World.isValid(position) || world.isAir(position) || (context.getStack()
.getItem() instanceof BlockItem && WorldUtil.isLiquidBlock(world,
position))) {
return false;

View File

@ -77,7 +77,7 @@ public final class TurtlePlayer extends FakePlayer {
private void setState(ITurtleAccess turtle) {
if (this.currentScreenHandler != playerScreenHandler) {
ComputerCraft.log.warn("Turtle has open container ({})", this.currentScreenHandler);
closeCurrentScreen();
closeHandledScreen();
}
BlockPos position = turtle.getPosition();
@ -91,13 +91,8 @@ public final class TurtlePlayer extends FakePlayer {
}
public static TurtlePlayer get(ITurtleAccess access) {
ServerWorld world = (ServerWorld) access.getWorld();
if( !(access instanceof TurtleBrain) ) return create( access );
/*if (!(access instanceof TurtleBrain)) {
return new TurtlePlayer(world, access.getOwningPlayer());
}*/
TurtleBrain brain = (TurtleBrain) access;
TurtlePlayer player = brain.m_cachedPlayer;
if (player == null || player.getGameProfile() != getProfile(access.getOwningPlayer()) || player.getEntityWorld() != access.getWorld()) {

View File

@ -6,6 +6,8 @@
package dan200.computercraft.shared.util;
import net.minecraft.util.DyeColor;
/**
* A reimplementation of the colour system in {@link ArmorDyeRecipe}, but bundled together as an object.
*/
@ -28,8 +30,15 @@ public class ColourTracker {
this.count++;
}
public boolean hasColour() {
return this.count > 0;
public void addColour( DyeColor dye )
{
Colour colour = Colour.VALUES[15 - dye.getId()];
addColour( colour.getR(), colour.getG(), colour.getB() );
}
public boolean hasColour()
{
return count > 0;
}
public int getColour() {

View File

@ -14,7 +14,6 @@ import dan200.computercraft.api.turtle.FakePlayer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.NetworkState;
@ -25,15 +24,12 @@ import net.minecraft.network.packet.c2s.play.BoatPaddleStateC2SPacket;
import net.minecraft.network.packet.c2s.play.BookUpdateC2SPacket;
import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket;
import net.minecraft.network.packet.c2s.play.ChatMessageC2SPacket;
import net.minecraft.network.packet.c2s.play.ClickWindowC2SPacket;
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket;
import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket;
import net.minecraft.network.packet.c2s.play.ClientStatusC2SPacket;
import net.minecraft.network.packet.c2s.play.ConfirmGuiActionC2SPacket;
import net.minecraft.network.packet.c2s.play.CraftRequestC2SPacket;
import net.minecraft.network.packet.c2s.play.CreativeInventoryActionC2SPacket;
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.network.packet.c2s.play.GuiCloseC2SPacket;
import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket;
import net.minecraft.network.packet.c2s.play.KeepAliveC2SPacket;
import net.minecraft.network.packet.c2s.play.PickFromInventoryC2SPacket;
@ -48,7 +44,6 @@ import net.minecraft.network.packet.c2s.play.QueryEntityNbtC2SPacket;
import net.minecraft.network.packet.c2s.play.RenameItemC2SPacket;
import net.minecraft.network.packet.c2s.play.RequestCommandCompletionsC2SPacket;
import net.minecraft.network.packet.c2s.play.ResourcePackStatusC2SPacket;
import net.minecraft.network.packet.c2s.play.SelectVillagerTradeC2SPacket;
import net.minecraft.network.packet.c2s.play.SpectatorTeleportC2SPacket;
import net.minecraft.network.packet.c2s.play.TeleportConfirmC2SPacket;
import net.minecraft.network.packet.c2s.play.UpdateBeaconC2SPacket;
@ -127,10 +122,6 @@ public class FakeNetHandler extends ServerPlayNetworkHandler {
public void onJigsawUpdate(@Nonnull UpdateJigsawC2SPacket packet) {
}
@Override
public void onVillagerTradeSelect(@Nonnull SelectVillagerTradeC2SPacket packet) {
}
@Override
public void onBookUpdate(@Nonnull BookUpdateC2SPacket packet) {
}
@ -207,14 +198,6 @@ public class FakeNetHandler extends ServerPlayNetworkHandler {
public void onClientStatus(@Nonnull ClientStatusC2SPacket packet) {
}
@Override
public void onGuiClose(@Nonnull GuiCloseC2SPacket packet) {
}
@Override
public void onClickWindow(@Nonnull ClickWindowC2SPacket packet) {
}
@Override
public void onCraftRequest(@Nonnull CraftRequestC2SPacket packet) {
}
@ -227,10 +210,6 @@ public class FakeNetHandler extends ServerPlayNetworkHandler {
public void onCreativeInventoryAction(@Nonnull CreativeInventoryActionC2SPacket packet) {
}
@Override
public void onConfirmTransaction(@Nonnull ConfirmGuiActionC2SPacket packet) {
}
@Override
public void onSignUpdate(@Nonnull UpdateSignC2SPacket packet) {
}
@ -309,10 +288,6 @@ public class FakeNetHandler extends ServerPlayNetworkHandler {
this.closeReason = message;
}
@Override
public void setupEncryption(@Nonnull SecretKey key) {
}
@Nonnull
@Override
public PacketListener getPacketListener() {

View File

@ -40,7 +40,7 @@ public final class WorldUtil {
.makeMap();
public static boolean isLiquidBlock(World world, BlockPos pos) {
if (!World.method_24794(pos)) {
if (!World.isValid(pos)) {
return false;
}
return world.getBlockState(pos)

View File

@ -38,5 +38,21 @@
"upgrade.computercraft.speaker.adjective": "(Alto-Falante)",
"chat.computercraft.wired_modem.peripheral_connected": "Periférico \"%s\" conectado à rede",
"chat.computercraft.wired_modem.peripheral_disconnected": "Periférico \"%s\" desconectado da rede",
"gui.computercraft.tooltip.copy": "Copiar para a área de transferência"
"gui.computercraft.tooltip.copy": "Copiar para a área de transferência",
"commands.computercraft.tp.synopsis": "Teleprota para um computador específico.",
"commands.computercraft.turn_on.done": "Ligou %s/%s computadores",
"commands.computercraft.turn_on.desc": "Liga os computadores em escuta. Você pode especificar o id de instância do computador (ex.: 123), id do computador (ex.: #123) ou o rótulo (ex.: \"@MeuComputador\").",
"commands.computercraft.turn_on.synopsis": "Liga computadores remotamente.",
"commands.computercraft.shutdown.done": "Desliga %s/%s computadores",
"commands.computercraft.shutdown.desc": "Desliga os computadores em escuta ou todos caso não tenha sido especificado. Você pode especificar o id de instância do computador (ex.: 123), id do computador (ex.: #123) ou o rótulo (ex.: \"@MeuComputador\").",
"commands.computercraft.shutdown.synopsis": "Desliga computadores remotamente.",
"commands.computercraft.dump.action": "Ver mais informação sobre este computador",
"commands.computercraft.dump.desc": "Mostra o status de todos os computadores ou uma informação específica sobre um computador. Você pode especificar o id de instância do computador (ex.: 123), id do computador (ex.: #123) ou o rótulo (ex.: \"@MeuComputador\").",
"commands.computercraft.dump.synopsis": "Mostra status de computadores.",
"commands.computercraft.help.no_command": "Comando '%s' não existe",
"commands.computercraft.help.no_children": "%s não tem sub-comandos",
"commands.computercraft.help.desc": "Mostra essa mensagem de ajuda",
"commands.computercraft.help.synopsis": "Providencia ajuda para um comando específico",
"commands.computercraft.desc": "O comando /computercraft providencia várias ferramentas de depuração e administração para controle e interação com computadores.",
"commands.computercraft.synopsis": "Vários comandos para controlar computadores."
}

View File

@ -1,11 +1,34 @@
{
"type": "minecraft:block",
"pools": [
"type": "minecraft:block",
"pools": [
{
"name": "main",
"rolls": 1,
"entries": [
{
"rolls": 1,
"entries": [
{ "type": "minecraft:dynamic", "name": "computercraft:computer" }
]
"type": "minecraft:dynamic",
"name": "computercraft:computer"
}
]
}
],
"conditions": [
{
"condition": "minecraft:alternative",
"terms": [
{
"condition": "computercraft:block_named"
},
{
"condition": "computercraft:has_id"
},
{
"condition": "minecraft:inverted",
"term": {
"condition": "computercraft:player_creative"
}
}
]
}
]
}
]
}

View File

@ -83,9 +83,9 @@ end
--- Tries to retrieve the computer or turtles own location.
--
-- @tparam[opt] number timeout The maximum time taken to establish our
-- position. Defaults to 2 seconds if not specified.
-- @tparam[opt] boolean debug Print debugging messages
-- @tparam[opt=2] number timeout The maximum time in seconds taken to establish our
-- position.
-- @tparam[opt=false] boolean debug Print debugging messages
-- @treturn[1] number This computer's `x` position.
-- @treturn[1] number This computer's `y` position.
-- @treturn[1] number This computer's `z` position.

View File

@ -137,6 +137,15 @@ handleMetatable = {
return handle.seek(whence, offset)
end,
--[[- Sets the buffering mode for an output file.
This has no effect under ComputerCraft, and exists with compatility
with base Lua.
@tparam string mode The buffering mode.
@tparam[opt] number size The size of the buffer.
@see file:setvbuf Lua's documentation for `setvbuf`.
@deprecated This has no effect in CC.
]]
setvbuf = function(self, mode, size) end,
--- Write one or more values to the file

View File

@ -132,7 +132,17 @@ function drawLine(startX, startY, endX, endY, colour)
return
end
local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
local minX = math.min(startX, endX)
local maxX, minY, maxY
if minX == startX then
minY = startY
maxX = endX
maxY = endY
else
minY = endY
maxX = startX
maxY = startY
end
-- TODO: clip to screen rectangle?

View File

@ -161,7 +161,9 @@ end
-- @tparam string name The name of the peripheral to wrap.
-- @treturn table|nil The table containing the peripheral's methods, or `nil` if
-- there is no peripheral present with the given name.
-- @usage peripheral.wrap("top").open(1)
-- @usage Open the modem on the top of this computer.
--
-- peripheral.wrap("top").open(1)
function wrap(name)
expect(1, name, "string")
@ -183,16 +185,25 @@ function wrap(name)
return result
end
--- Find all peripherals of a specific type, and return the
-- @{peripheral.wrap|wrapped} peripherals.
--
-- @tparam string ty The type of peripheral to look for.
-- @tparam[opt] function(name:string, wrapped:table):boolean filter A
-- filter function, which takes the peripheral's name and wrapped table
-- and returns if it should be included in the result.
-- @treturn table... 0 or more wrapped peripherals matching the given filters.
-- @usage { peripheral.find("monitor") }
-- @usage peripheral.find("modem", rednet.open)
--[[- Find all peripherals of a specific type, and return the
@{peripheral.wrap|wrapped} peripherals.
@tparam string ty The type of peripheral to look for.
@tparam[opt] function(name:string, wrapped:table):boolean filter A
filter function, which takes the peripheral's name and wrapped table
and returns if it should be included in the result.
@treturn table... 0 or more wrapped peripherals matching the given filters.
@usage Find all monitors and store them in a table, writing "Hello" on each one.
local monitors = { peripheral.find("monitor") }
for _, monitor in pairs(monitors) do
monitor.write("Hello")
end
@usage This abuses the `filter` argument to call @{rednet.open} on every modem.
peripheral.find("modem", rednet.open)
]]
function find(ty, filter)
expect(1, ty, "string")
expect(2, filter, "function", "nil")

View File

@ -381,6 +381,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
local sArrayResult = "["
local nObjectSize = 0
local nArraySize = 0
local largestArrayIndex = 0
for k, v in pairs(t) do
if type(k) == "string" then
local sEntry
@ -395,10 +396,17 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
sObjectResult = sObjectResult .. "," .. sEntry
end
nObjectSize = nObjectSize + 1
elseif type(k) == "number" and k > largestArrayIndex then --the largest index is kept to avoid losing half the array if there is any single nil in that array
largestArrayIndex = k
end
end
for _, v in ipairs(t) do
local sEntry = serializeJSONImpl(v, tTracking, bNBTStyle)
for k = 1, largestArrayIndex, 1 do --the array is read up to the very last valid array index, ipairs() would stop at the first nil value and we would lose any data after.
local sEntry
if t[k] == nil then --if the array is nil at index k the value is "null" as to keep the unused indexes in between used ones.
sEntry = "null"
else -- if the array index does not point to a nil we serialise it's content.
sEntry = serializeJSONImpl(t[k], tTracking, bNBTStyle)
end
if nArraySize == 0 then
sArrayResult = sArrayResult .. sEntry
else

View File

@ -10,6 +10,7 @@ end
--
-- Generally you should not need to use this table - it only exists for
-- backwards compatibility reasons.
-- @deprecated
native = turtle.native or turtle
local function addCraftMethod(object)

View File

@ -1,3 +1,28 @@
New features in CC: Restitched 1.95.3
Several bug fixes:
* Correctly serialise sparse arrays into JSON (livegamer999)
* Fix rs.getBundledInput returning the output instead (SkyTheCodeMaster)
* Programs run via edit are now a little better behaved (Wojbie)
* Add User-Agent to a websocket's headers.
# New features in CC: Restitched 1.95.2
* Add `isReadOnly` to `fs.attributes` (Lupus590)
* Many more programs now support numpad enter (Wojbie)
Several bug fixes:
* Fix some commands failing to parse on dedicated servers.
* Hopefully improve edit's behaviour with AltGr on some European keyboards.
* Prevent files being usable after their mount was removed.
* Fix the `id` program crashing on non-disk items (Wojbie).
# New features in CC: Restitched 1.95.1
Several bug fixes:
* Command computers now drop items again.
* Restore crafting of disks with dyes.
# New features in CC: Restitched 1.95.0
* Optimise the paint program's initial render.

View File

@ -1,23 +1,9 @@
New features in CC: Restitched 1.95.0
New features in CC: Restitched 1.95.3
* Optimise the paint program's initial render.
* Several documentation improvments (Gibbo3771, MCJack123).
* `fs.combine` now accepts multiple arguments.
* Add a setting (`bios.strict_globals`) to error when accidentally declaring a global. (Lupus590).
* Add an improved help viewer which allows scrolling up and down (MCJack123).
* Add `cc.strings` module, with utilities for wrapping text (Lupus590).
* The `clear` program now allows resetting the palette too (Luca0208).
And several bug fixes:
* Fix memory leak in generic peripherals.
* Fix crash when a turtle is broken while being ticked.
* `textutils.*tabulate` now accepts strings _or_ numbers.
* We now deny _all_ local IPs, using the magic `$private` host. Previously the IPv6 loopback interface was not blocked.
* Fix crash when rendering monitors if the block has not yet been synced. You will need to regenerate the config file to apply this change.
* `read` now supports numpad enter (TheWireLord)
* Correctly handle HTTP redirects to URLs containing escape characters.
* Fix integer overflow in `os.epoch`.
* Allow using pickaxes (and other items) for turtle upgrades which have mod-specific NBT.
* Fix duplicate turtle/pocket upgrade recipes appearing in JEI.
Several bug fixes:
* Correctly serialise sparse arrays into JSON (livegamer999)
* Fix rs.getBundledInput returning the output instead (SkyTheCodeMaster)
* Programs run via edit are now a little better behaved (Wojbie)
* Add User-Agent to a websocket's headers.
Type "help changelog" to see the full version history.

View File

@ -1,23 +1,28 @@
--- Provides a "pretty printer", for rendering data structures in an
-- aesthetically pleasing manner.
--
-- In order to display something using @{cc.pretty}, you build up a series of
-- @{Doc|documents}. These behave a little bit like strings; you can concatenate
-- them together and then print them to the screen.
--
-- However, documents also allow you to control how they should be printed. There
-- are several functions (such as @{nest} and @{group}) which allow you to control
-- the "layout" of the document. When you come to display the document, the 'best'
-- (most compact) layout is used.
--
-- @module cc.pretty
-- @usage Print a table to the terminal
-- local pretty = require "cc.pretty"
-- pretty.print(pretty.pretty({ 1, 2, 3 }))
--
-- @usage Build a custom document and display it
-- local pretty = require "cc.pretty"
-- pretty.print(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
--[[- Provides a "pretty printer", for rendering data structures in an
aesthetically pleasing manner.
In order to display something using @{cc.pretty}, you build up a series of
@{Doc|documents}. These behave a little bit like strings; you can concatenate
them together and then print them to the screen.
However, documents also allow you to control how they should be printed. There
are several functions (such as @{nest} and @{group}) which allow you to control
the "layout" of the document. When you come to display the document, the 'best'
(most compact) layout is used.
The structure of this module is based on [A Prettier Printer][prettier].
[prettier]: https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf "A Prettier Printer"
@module cc.pretty
@usage Print a table to the terminal
local pretty = require "cc.pretty"
pretty.print(pretty.pretty({ 1, 2, 3 }))
@usage Build a custom document and display it
local pretty = require "cc.pretty"
pretty.print(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
]]
local expect = require "cc.expect"
local expect, field = expect.expect, expect.field

View File

@ -5,17 +5,24 @@
local expect = require "cc.expect".expect
--- Wraps a block of text, so that each line fits within the given width.
--
-- This may be useful if you want to wrap text before displaying it to a
-- @{monitor} or @{printer} without using @{_G.print|print}.
--
-- @tparam string text The string to wrap.
-- @tparam[opt] number width The width to constrain to, defaults to the width of
-- the terminal.
--
-- @treturn { string... } The wrapped input string.
-- @usage require "cc.strings".wrap("This is a long piece of text", 10)
--[[- Wraps a block of text, so that each line fits within the given width.
This may be useful if you want to wrap text before displaying it to a
@{monitor} or @{printer} without using @{_G.print|print}.
@tparam string text The string to wrap.
@tparam[opt] number width The width to constrain to, defaults to the width of
the terminal.
@treturn { string... } The wrapped input string as a list of lines.
@usage Wrap a string and write it to the terminal.
term.clear()
local lines = require "cc.strings".wrap("This is a long piece of text", 10)
for i = 1, #lines do
term.setCursorPos(1, i)
term.write(lines[i])
end
]]
local function wrap(text, width)
expect(1, text, "string")
expect(2, width, "number", "nil")

View File

@ -1,4 +1,4 @@
Please report bugs at https://github.com/Merith-TK/cc-restiched. Thanks!
Please report bugs at https://github.com/Merith-TK/cc-restitched/issues. Thanks!
View the documentation at https://tweaked.cc
Show off your programs or ask for help at our forum: https://forums.computercraft.cc
You can disable these messages by running "set motd.enable false".

View File

@ -47,6 +47,30 @@ else
stringColour = colours.white
end
local runHandler = [[multishell.setTitle(multishell.getCurrent(), %q)
local current = term.current()
local ok, err = load(%q, %q, nil, _ENV)
if ok then ok, err = pcall(ok, ...) end
term.redirect(current)
term.setTextColor(term.isColour() and colours.yellow or colours.white)
term.setBackgroundColor(colours.black)
term.setCursorBlink(false)
local _, y = term.getCursorPos()
local _, h = term.getSize()
if not ok then
printError(err)
end
if ok and y >= h then
term.scroll(1)
end
term.setCursorPos(1, h)
if ok then
write("Program finished. ")
end
write("Press any key to continue")
os.pullEvent('key')
]]
-- Menus
local bMenu = false
local nMenuItem = 1
@ -89,7 +113,7 @@ local function load(_sPath)
end
end
local function save(_sPath)
local function save(_sPath, fWrite)
-- Create intervening folder
local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len())
if not fs.exists(sDir) then
@ -101,8 +125,8 @@ local function save(_sPath)
local function innerSave()
file, fileerr = fs.open(_sPath, "w")
if file then
for _, sLine in ipairs(tLines) do
file.write(sLine .. "\n")
if file then
fWrite(file)
end
else
error("Failed to open " .. _sPath)
@ -293,7 +317,11 @@ local tMenuFuncs = {
if bReadOnly then
sStatus = "Access denied"
else
local ok, _, fileerr = save(sPath)
local ok, _, fileerr = save(sPath, function(file)
for _, sLine in ipairs(tLines) do
file.write(sLine .. "\n")
end
end)
if ok then
sStatus = "Saved to " .. sPath
else
@ -390,8 +418,18 @@ local tMenuFuncs = {
bRunning = false
end,
Run = function()
local sTempPath = "/.temp"
local ok = save(sTempPath)
local sTitle = fs.getName(sPath)
if sTitle:sub(-4) == ".lua" then
sTitle = sTitle:sub(1, -5)
end
local sTempPath = bReadOnly and ".temp." .. sTitle or fs.combine(fs.getDir(sPath), ".temp." .. sTitle)
if fs.exists(sTempPath) then
sStatus = "Error saving to " .. sTempPath
return
end
local ok = save(sTempPath, function(file)
file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@" .. fs.getName(sPath)))
end)
if ok then
local nTask = shell.openTab(sTempPath)
if nTask then
@ -667,8 +705,8 @@ while bRunning do
end
end
elseif param == keys.enter then
-- Enter
elseif param == keys.enter or param == keys.numPadEnter then
-- Enter/Numpad Enter
if not bMenu and not bReadOnly then
-- Newline
local sLine = tLines[y]
@ -687,7 +725,7 @@ while bRunning do
end
elseif param == keys.leftCtrl or param == keys.rightCtrl or param == keys.rightAlt then
elseif param == keys.leftCtrl or param == keys.rightCtrl then
-- Menu toggle
bMenu = not bMenu
if bMenu then
@ -696,7 +734,12 @@ while bRunning do
term.setCursorBlink(true)
end
redrawMenu()
elseif param == keys.rightAlt then
if bMenu then
bMenu = false
term.setCursorBlink(true)
redrawMenu()
end
end
elseif sEvent == "char" then
@ -758,6 +801,7 @@ while bRunning do
end
else
bMenu = false
term.setCursorBlink(true)
redrawMenu()
end
end

View File

@ -349,7 +349,7 @@ local function accessMenu()
selection = #mChoices
end
elseif key == keys.enter then
elseif key == keys.enter or key == keys.numPadEnter then
-- Select an option
return menu_choices[mChoices[selection]]()
elseif key == keys.leftCtrl or keys == keys.rightCtrl then

View File

@ -199,8 +199,8 @@ while true do
drawMenu()
drawFrontend()
end
elseif key == keys.enter then
-- Enter
elseif key == keys.enter or key == keys.numPadEnter then
-- Enter/Numpad Enter
break
end
end

View File

@ -13,16 +13,30 @@ if sDrive == nil then
end
else
local bData = disk.hasData(sDrive)
if not bData then
if disk.hasAudio(sDrive) then
local title = disk.getAudioTitle(sDrive)
if title then
print("Has audio track \"" .. title .. "\"")
else
print("Has untitled audio")
end
return
end
if not disk.hasData(sDrive) then
print("No disk in drive " .. sDrive)
return
end
print("The disk is #" .. disk.getID(sDrive))
local id = disk.getID(sDrive)
if id then
print("The disk is #" .. id)
else
print("Non-disk data source")
end
local label = disk.getLabel(sDrive)
if label then
print("The disk is labelled \"" .. label .. "\"")
print("Labelled \"" .. label .. "\"")
end
end

View File

@ -546,7 +546,7 @@ local function playGame()
msgBox("Game Over!")
while true do
local _, k = os.pullEvent("key")
if k == keys.space or k == keys.enter then
if k == keys.space or k == keys.enter or k == keys.numPadEnter then
break
end
end
@ -627,7 +627,7 @@ local function runMenu()
elseif key == keys.down or key == keys.s then
selected = selected % 2 + 1
drawMenu()
elseif key == keys.enter or key == keys.space then
elseif key == keys.enter or key == keys.numPadEnter or key == keys.space then
break --begin play!
end
end

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:1118481}"
"nbt": "{Color:1118481}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:15905484}"
"nbt": "{Color:15905484}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:8375321}"
"nbt": "{Color:8375321}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:14605932}"
"nbt": "{Color:14605932}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:10072818}"
"nbt": "{Color:10072818}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:15040472}"
"nbt": "{Color:15040472}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:15905331}"
"nbt": "{Color:15905331}"
}
}

View File

@ -14,6 +14,6 @@
],
"result": {
"item": "computercraft:disk",
"nbt": "{color:15790320}"
"nbt": "{Color:15790320}"
}
}

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