diff --git a/.github/workflows/main-ci.yml b/.github/workflows/main-ci.yml
index 38142dbea..0dbdaf69c 100644
--- a/.github/workflows/main-ci.yml
+++ b/.github/workflows/main-ci.yml
@@ -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') }}
diff --git a/build.gradle b/build.gradle
index 0be21ae8c..c6287584c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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 {
diff --git a/doc/events/alarm.md b/doc/events/alarm.md
new file mode 100644
index 000000000..db7f04845
--- /dev/null
+++ b/doc/events/alarm.md
@@ -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")
+```
diff --git a/doc/events/char.md b/doc/events/char.md
new file mode 100644
index 000000000..473313702
--- /dev/null
+++ b/doc/events/char.md
@@ -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. Ctrl) 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
+```
diff --git a/doc/events/computer_command.md b/doc/events/computer_command.md
new file mode 100644
index 000000000..245252399
--- /dev/null
+++ b/doc/events/computer_command.md
@@ -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
+```
diff --git a/doc/events/disk.md b/doc/events/disk.md
new file mode 100644
index 000000000..2946d70c4
--- /dev/null
+++ b/doc/events/disk.md
@@ -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
+```
diff --git a/doc/events/disk_eject.md b/doc/events/disk_eject.md
new file mode 100644
index 000000000..71c3ede0a
--- /dev/null
+++ b/doc/events/disk_eject.md
@@ -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
+```
diff --git a/doc/events/http_check.md b/doc/events/http_check.md
new file mode 100644
index 000000000..9af5ea7ca
--- /dev/null
+++ b/doc/events/http_check.md
@@ -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.
diff --git a/doc/events/http_failure.md b/doc/events/http_failure.md
new file mode 100644
index 000000000..dc10b40d7
--- /dev/null
+++ b/doc/events/http_failure.md
@@ -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()
+```
diff --git a/doc/events/http_success.md b/doc/events/http_success.md
new file mode 100644
index 000000000..3700b9211
--- /dev/null
+++ b/doc/events/http_success.md
@@ -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()
+```
diff --git a/doc/events/key.md b/doc/events/key.md
new file mode 100644
index 000000000..59598d8dd
--- /dev/null
+++ b/doc/events/key.md
@@ -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, F1 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
+```
diff --git a/doc/events/key_up.md b/doc/events/key_up.md
new file mode 100644
index 000000000..e957cae6b
--- /dev/null
+++ b/doc/events/key_up.md
@@ -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, F1 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
+```
diff --git a/doc/events/modem_message.md b/doc/events/modem_message.md
new file mode 100644
index 000000000..ec619f3d4
--- /dev/null
+++ b/doc/events/modem_message.md
@@ -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
+```
diff --git a/doc/events/monitor_resize.md b/doc/events/monitor_resize.md
new file mode 100644
index 000000000..03de804e7
--- /dev/null
+++ b/doc/events/monitor_resize.md
@@ -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
+```
diff --git a/doc/events/monitor_touch.md b/doc/events/monitor_touch.md
new file mode 100644
index 000000000..0f27a9cc1
--- /dev/null
+++ b/doc/events/monitor_touch.md
@@ -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
+```
diff --git a/doc/events/mouse_click.md b/doc/events/mouse_click.md
new file mode 100644
index 000000000..83d371260
--- /dev/null
+++ b/doc/events/mouse_click.md
@@ -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.
+
+
+
+ Button code | Mouse button |
+ 1 | Left button |
+ 2 | Middle button |
+ 3 | Right button |
+
+
+## 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
+```
diff --git a/doc/events/mouse_drag.md b/doc/events/mouse_drag.md
new file mode 100644
index 000000000..15451c9f8
--- /dev/null
+++ b/doc/events/mouse_drag.md
@@ -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
+```
diff --git a/doc/events/mouse_scroll.md b/doc/events/mouse_scroll.md
new file mode 100644
index 000000000..6248220a5
--- /dev/null
+++ b/doc/events/mouse_scroll.md
@@ -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
+```
diff --git a/doc/events/mouse_up.md b/doc/events/mouse_up.md
new file mode 100644
index 000000000..886330a6d
--- /dev/null
+++ b/doc/events/mouse_up.md
@@ -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
+```
diff --git a/doc/events/paste.md b/doc/events/paste.md
new file mode 100644
index 000000000..b4f8713c5
--- /dev/null
+++ b/doc/events/paste.md
@@ -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
+```
diff --git a/doc/events/peripheral.md b/doc/events/peripheral.md
new file mode 100644
index 000000000..5769f3942
--- /dev/null
+++ b/doc/events/peripheral.md
@@ -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
+```
diff --git a/doc/events/peripheral_detach.md b/doc/events/peripheral_detach.md
new file mode 100644
index 000000000..c8a462cf0
--- /dev/null
+++ b/doc/events/peripheral_detach.md
@@ -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
+```
diff --git a/doc/events/rednet_message.md b/doc/events/rednet_message.md
new file mode 100644
index 000000000..8d0bdf697
--- /dev/null
+++ b/doc/events/rednet_message.md
@@ -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
+```
diff --git a/doc/events/redstone.md b/doc/events/redstone.md
new file mode 100644
index 000000000..44eda304a
--- /dev/null
+++ b/doc/events/redstone.md
@@ -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
+```
diff --git a/doc/events/task_complete.md b/doc/events/task_complete.md
new file mode 100644
index 000000000..eddec51d2
--- /dev/null
+++ b/doc/events/task_complete.md
@@ -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
+```
diff --git a/doc/events/term_resize.md b/doc/events/term_resize.md
new file mode 100644
index 000000000..0eb503bad
--- /dev/null
+++ b/doc/events/term_resize.md
@@ -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
+```
diff --git a/doc/events/terminate.md b/doc/events/terminate.md
new file mode 100644
index 000000000..0760b8c3b
--- /dev/null
+++ b/doc/events/terminate.md
@@ -0,0 +1,25 @@
+---
+module: [kind=event] terminate
+---
+
+The @{terminate} event is fired when Ctrl-T 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
+```
diff --git a/doc/events/timer.md b/doc/events/timer.md
new file mode 100644
index 000000000..c359c37b4
--- /dev/null
+++ b/doc/events/timer.md
@@ -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")
+```
diff --git a/doc/events/turtle_inventory.md b/doc/events/turtle_inventory.md
new file mode 100644
index 000000000..bc9392b6b
--- /dev/null
+++ b/doc/events/turtle_inventory.md
@@ -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
+```
diff --git a/doc/events/websocket_closed.md b/doc/events/websocket_closed.md
new file mode 100644
index 000000000..9e3783d19
--- /dev/null
+++ b/doc/events/websocket_closed.md
@@ -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.")
+```
diff --git a/doc/events/websocket_failure.md b/doc/events/websocket_failure.md
new file mode 100644
index 000000000..eef34e777
--- /dev/null
+++ b/doc/events/websocket_failure.md
@@ -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)
+```
diff --git a/doc/events/websocket_message.md b/doc/events/websocket_message.md
new file mode 100644
index 000000000..53b9d4bd2
--- /dev/null
+++ b/doc/events/websocket_message.md
@@ -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()
+```
diff --git a/doc/events/websocket_success.md b/doc/events/websocket_success.md
new file mode 100644
index 000000000..dcde934b3
--- /dev/null
+++ b/doc/events/websocket_success.md
@@ -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()
+```
diff --git a/gradle.properties b/gradle.properties
index 96b57d2a6..a645c8f2d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -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
diff --git a/patchwork.md b/patchwork.md
index 97d1ecd5d..40372bb64 100644
--- a/patchwork.md
+++ b/patchwork.md
@@ -536,4 +536,92 @@ e4b0a5b3ce035eb23feb4191432fc49af5772c5b
2020 -> 2021
```
-A huge amount of changes.
\ No newline at end of file
+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
+```
diff --git a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java
index 7a7de9cb7..4868de353 100644
--- a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java
+++ b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java
@@ -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".
diff --git a/src/main/java/dan200/computercraft/api/IUpgradeBase.java b/src/main/java/dan200/computercraft/api/IUpgradeBase.java
index c659ea5f5..06675b7ed 100644
--- a/src/main/java/dan200/computercraft/api/IUpgradeBase.java
+++ b/src/main/java/dan200/computercraft/api/IUpgradeBase.java
@@ -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;
diff --git a/src/main/java/dan200/computercraft/api/turtle/FakePlayer.java b/src/main/java/dan200/computercraft/api/turtle/FakePlayer.java
index 76656fa78..4eac506e4 100644
--- a/src/main/java/dan200/computercraft/api/turtle/FakePlayer.java
+++ b/src/main/java/dan200/computercraft/api/turtle/FakePlayer.java
@@ -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() {
}
diff --git a/src/main/java/dan200/computercraft/core/apis/FSAPI.java b/src/main/java/dan200/computercraft/core/apis/FSAPI.java
index 52a2bcbbe..c2156a069 100644
--- a/src/main/java/dan200/computercraft/core/apis/FSAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/FSAPI.java
@@ -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 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());
diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
index 9f1c5f951..0996e5300 100644
--- a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
+++ b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java
@@ -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 checkUrls = new ResourceGroup<>();
- private final ResourceGroup requests = new ResourceQueue<>(() -> ComputerCraft.httpMaxRequests);
- private final ResourceGroup websockets = new ResourceGroup<>(() -> ComputerCraft.httpMaxWebsockets);
+ private final ResourceGroup requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
+ private final ResourceGroup 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