diff --git a/.gitattributes b/.gitattributes index 9a0642d09..6501a79bc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Ignore changes in generated files -src/generated/resources/data/** linguist-generated +src/generated/** linguist-generated src/testMod/server-files/structures linguist-generated * text=auto diff --git a/.github/workflows/make-doc.sh b/.github/workflows/make-doc.sh index f22851bee..e7aca0e61 100755 --- a/.github/workflows/make-doc.sh +++ b/.github/workflows/make-doc.sh @@ -12,7 +12,7 @@ chmod 600 "$HOME/.ssh/key" # And upload rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \ - "$GITHUB_WORKSPACE/build/docs/lua/" \ + "$GITHUB_WORKSPACE/build/docs/site/" \ "$SSH_USER@$SSH_HOST:/$DEST" rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \ "$GITHUB_WORKSPACE/build/docs/javadoc/" \ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9afbdb4c..bae4f7b4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ #### Building documentation - `./gradlew luaJavadoc` - Generate documentation stubs for Java methods. - `./gradlew docWebsite` - Generate the whole website (including Javascript pages). The resulting HTML is stored at - `./build/docs/lua/`. + `./build/docs/site/`. #### Writing documentation illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as diff --git a/build.gradle b/build.gradle index ace5b9938..59da21991 100644 --- a/build.gradle +++ b/build.gradle @@ -146,7 +146,7 @@ accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg') runtimeOnly fg.deobf("mezz.jei:jei-1.16.5:7.7.0.104") - shade 'org.squiddev:Cobalt:0.5.2-SNAPSHOT' + shade 'org.squiddev:Cobalt:0.5.5' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0' @@ -158,7 +158,7 @@ accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg') testModImplementation sourceSets.main.output - cctJavadoc 'cc.tweaked:cct-javadoc:1.4.5' + cctJavadoc 'cc.tweaked:cct-javadoc:1.4.6' } // Compile tasks @@ -220,9 +220,32 @@ task luaJavadoc(type: Javadoc) { try { hash = ["git", "-C", projectDir, "rev-parse", "HEAD"].execute().text.trim() - def blacklist = ['GitHub', 'dan200', 'Daniel Ratcliffe'] - ["git", "-C", projectDir, "log", "--format=tformat:%an%n%cn"].execute().text.split('\n').each { - if (!blacklist.contains(it)) contributors.add(it) + def blacklist = ['GitHub', 'Daniel Ratcliffe', 'Weblate'] + + // Extract all authors, commiters and co-authors from the git log. + def authors = ["git", "-C", projectDir, "log", "--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)"] + .execute().text.readLines().unique() + + // We now pass this through git's mailmap to de-duplicate some authors. + def remapAuthors = ["git", "check-mailmap", "--stdin"].execute() + remapAuthors.withWriter { stdin -> + if (stdin !instanceof BufferedWriter) stdin = new BufferedWriter(stdin) + + authors.forEach { + if (it == "") return + if (!it.endsWith(">")) it += ">" // Some commits have broken Co-Authored-By lines! + stdin.writeLine(it) + } + stdin.close() + } + + // And finally extract out the actual name. + def emailRegex = ~/^([^<]+) <.+>$/ + remapAuthors.text.readLines().forEach { + def matcher = it =~ emailRegex + matcher.find() + def name = matcher.group(1) + if (!blacklist.contains(name)) contributors.add(name) } } catch (Exception e) { e.printStackTrace() @@ -275,7 +298,7 @@ commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js') task illuaminateDocs(type: Exec, dependsOn: [rollup, luaJavadoc]) { group = "build" - description = "Bundles JS into rollup" + description = "Generates docs using Illuaminate" inputs.files(fileTree("doc")).withPropertyName("docs") inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom") @@ -288,7 +311,20 @@ task illuaminateDocs(type: Exec, dependsOn: [rollup, luaJavadoc]) { commandLine mkCommand('"bin/illuaminate" doc-gen') } -task docWebsite(type: Copy, dependsOn: [illuaminateDocs]) { +task jsxDocs(type: Exec, dependsOn: [illuaminateDocs]) { + group = "build" + description = "Post-processes documentation to statically render some dynamic content." + + inputs.files(fileTree("src/web")).withPropertyName("sources") + inputs.file("package-lock.json").withPropertyName("package-lock.json") + inputs.file("tsconfig.json").withPropertyName("Typescript config") + inputs.files(fileTree("$buildDir/docs/lua")) + outputs.dir("$buildDir/docs/site") + + commandLine mkCommand("'node_modules/.bin/ts-node' --esm src/web/transform.tsx") +} + +task docWebsite(type: Copy, dependsOn: [jsxDocs]) { from('doc') { include 'logo.png' include 'images/**' @@ -296,7 +332,14 @@ task docWebsite(type: Copy, dependsOn: [illuaminateDocs]) { from("$buildDir/rollup") { exclude 'index.js' } - into "${project.docsDir}/lua" + from("$buildDir/docs/lua") { + exclude '**/*.html' + } + from("src/generated/export/items") { + into("images/items") + } + + into "${project.docsDir}/site" } // Check tasks diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index b96d5c3db..f67654f08 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -76,7 +76,9 @@ - + + + diff --git a/doc/events/mouse_click.md b/doc/events/mouse_click.md index ed4f2e3eb..1dc1196e2 100644 --- a/doc/events/mouse_click.md +++ b/doc/events/mouse_click.md @@ -15,13 +15,11 @@ ## 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 codeMouse button
1Left button
2Right button
3Middle button
+| Button Code | Mouse Button | +|------------:|---------------| +| 1 | Left button | +| 2 | Right button | +| 3 | Middle button | ## Example Print the button and the coordinates whenever the mouse is clicked. diff --git a/doc/guides/local_ips.md b/doc/guides/local_ips.md new file mode 100644 index 000000000..affad7355 --- /dev/null +++ b/doc/guides/local_ips.md @@ -0,0 +1,99 @@ +--- +module: [kind=guide] local_ips +--- + +# Allowing access to local IPs +By default, ComputerCraft blocks access to local IP addresses for security. This means you can't normally access any +HTTP server running on your computer. However, this may be useful for testing programs without having a remote +server. You can unblock these IPs in the ComputerCraft config. + + - [Minecraft 1.13 and later, CC:T 1.87.0 and later](#cc-1.87.0) + - [Minecraft 1.13 and later, CC:T 1.86.2 and earlier](#cc-1.86.2) + - [Minecraft 1.12.2 and earlier](#mc-1.12) + +## Minecraft 1.13 and later, CC:T 1.87.0 and later {#cc-1.87.0} +The configuration file can be located at `serverconfig/computercraft-server.toml` inside the world folder on either +single-player or multiplayer. Look for lines that look like this: + +```toml +#A list of rules which control behaviour of the "http" API for specific domains or IPs. +#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"), +#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked. +[[http.rules]] + host = "$private" + action = "deny" +``` + +On 1.95.0 and later, this will be a single entry with `host = "$private"`. On earlier versions, this will be a number of +`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entires that have +`action = "deny"`. Then save the file and relaunch Minecraft (Server). + +Here's what it should look like after removing: + +```toml +#A list of rules which control behaviour of the "http" API for specific domains or IPs. +#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"), +#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked. +[[http.rules]] + #The maximum size (in bytes) that a computer can send or receive in one websocket packet. + max_websocket_message = 131072 + host = "*" + #The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text. + max_upload = 4194304 + action = "allow" + #The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client. + max_download = 16777216 + #The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited. + timeout = 30000 +``` + +## Minecraft 1.13 and later, CC:T 1.86.2 and earlier {#cc-1.86.2} +The configuration file for singleplayer is at `.minecraft/config/computercraft-common.toml`. Look for lines that look +like this: + +```toml +#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"] +``` + +Remove everything inside the array, leaving the last line as `blacklist = []`. Then save the file and relaunch Minecraft. + +Here's what it should look like after removing: + +```toml +#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +blacklist = [] +``` + +## Minecraft 1.12.2 and earlier {#mc-1.12} +On singleplayer, the configuration file is located at `.minecraft\config\ComputerCraft.cfg`. On multiplayer, the +configuration file is located at `\config\ComputerCraft.cfg`. Look for lines that look like this: + +```ini +# A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +# If this is empty then all explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +S:blocked_domains < + 127.0.0.0/8 + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + fd00::/8 + > +``` + +Delete everything between the `<>`, leaving the last line as `S:blocked_domains = <>`. Then save the file and relaunch +Minecraft (Server). + +Here's what it should look like after removing: + +```ini +# A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers. +# If this is empty then all explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com. +# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). +S:blocked_domains <> +``` diff --git a/doc/guides/speaker_audio.md b/doc/guides/speaker_audio.md index 0f9652ae4..4cbe9e437 100644 --- a/doc/guides/speaker_audio.md +++ b/doc/guides/speaker_audio.md @@ -125,7 +125,7 @@ ## Storing audio First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker. -As mentioned to above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each +As mentioned above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use @{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and @{fs.BinaryReadHandle.read} if you prefer. @@ -136,22 +136,22 @@ ## Processing audio samples, etc... Let's put together a small demonstration here. We're going to add a small delay effect to the song above, so that you -hear a faint echo about a second later. +hear a faint echo a second and a half later. In order to do this, we'll follow a format similar to the previous example, decoding the audio and then playing it. However, we'll also add some new logic between those two steps, which loops over every sample in our chunk of audio, and -adds the sample from one second ago to it. +adds the sample from 1.5 seconds ago to it. -For this, we'll need to keep track of the last 48k samples - exactly one seconds worth of audio. We can do this using a +For this, we'll need to keep track of the last 72k samples - exactly 1.5 seconds worth of audio. We can do this using a [Ring Buffer], which helps makes things a little more efficient. ```lua {data-peripheral=speaker} local dfpwm = require("cc.audio.dfpwm") local speaker = peripheral.find("speaker") --- Speakers play at 48kHz, so one second is 48k samples. We first fill our buffer +-- Speakers play at 48kHz, so 1.5 seconds is 72k samples. We first fill our buffer -- with 0s, as there's nothing to echo at the start of the track! -local samples_i, samples_n = 1, 48000 +local samples_i, samples_n = 1, 48000 * 1.5 local samples = {} for i = 1, samples_n do samples[i] = 0 end @@ -162,7 +162,7 @@ ## Processing audio for i = 1, #buffer do local original_value = buffer[i] - -- Replace this sample with its current amplitude plus the amplitude from one second ago. + -- Replace this sample with its current amplitude plus the amplitude from 1.5 seconds ago. -- We scale both to ensure the resulting value is still between -128 and 127. buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4 @@ -175,6 +175,11 @@ ## Processing audio while not speaker.playAudio(buffer) do os.pullEvent("speaker_audio_empty") end + + -- The audio processing above can be quite slow and preparing the first batch of audio + -- may timeout the computer. We sleep to avoid this. + -- There's definitely better ways of handling this - this is just an example! + sleep(0.05) end ``` diff --git a/doc/reference/feature_compat.md b/doc/reference/feature_compat.md new file mode 100644 index 000000000..d0b03cf45 --- /dev/null +++ b/doc/reference/feature_compat.md @@ -0,0 +1,95 @@ +--- +module: [kind=reference] feature_compat +--- + +# Lua 5.2/5.3 features in CC: Tweaked +CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions. + +## Lua 5.2 +| Feature | Supported? | Notes | +|---------------------------------------------------------------|------------|-------------------------------------------------------------------| +| `goto`/labels | ❌ | | +| `_ENV` | 🔶 | The `_ENV` global points to `getfenv()`, but it cannot be set. | +| `\z` escape | ✔ | | +| `\xNN` escape | ✔ | | +| Hex literal fractional/exponent parts | ✔ | | +| Empty statements | ❌ | | +| `__len` metamethod | ✔ | | +| `__ipairs` metamethod | ❌ | | +| `__pairs` metamethod | ✔ | | +| `bit32` library | ✔ | | +| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. | +| New `load` syntax | ✔ | | +| `loadfile` mode parameter | ✔ | Supports both 5.1 and 5.2+ syntax. | +| Removed `loadstring` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. | +| Removed `getfenv`, `setfenv` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. | +| `rawlen` function | ✔ | | +| Negative index to `select` | ✔ | | +| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. | +| Arguments to `xpcall` | ❌ | | +| Second return value from `coroutine.running` | ❌ | | +| Removed `module` | ✔ | | +| `package.loaders` -> `package.searchers` | ❌ | | +| Second argument to loader functions | ✔ | | +| `package.config` | ✔ | | +| `package.searchpath` | ✔ | | +| Removed `package.seeall` | ✔ | | +| `string.dump` on functions with upvalues (blanks them out) | ✔ | | +| `string.rep` separator | ❌ | | +| `%g` match group | ❌ | | +| Removal of `%z` match group | ❌ | | +| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. | +| `table.pack`/`table.unpack` | ✔ | | +| `math.log` base argument | ✔ | | +| Removed `math.log10` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. | +| `*L` mode to `file:read` | ✔ | | +| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. | +| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. | +| `istailcall` field in `debug.getinfo` | ❌ | | +| `nparams` field in `debug.getinfo` | ✔ | | +| `isvararg` field in `debug.getinfo` | ✔ | | +| `debug.getlocal` negative indices for varargs | ❌ | | +| `debug.getuservalue`/`debug.setuservalue` | ❌ | Userdata are rarely used in CC:T, so this is not necessary. | +| `debug.upvalueid` | ✔ | | +| `debug.upvaluejoin` | ✔ | | +| Tail call hooks | ❌ | | +| `=` prefix for chunks | ✔ | | +| Yield across C boundary | ✔ | | +| Removal of ambiguity error | ❌ | | +| Identifiers may no longer use locale-dependent letters | ✔ | | +| Ephemeron tables | ❌ | | +| Identical functions may be reused | ❌ | | +| Generational garbage collector | ❌ | Cobalt uses the built-in Java garbage collector. | + +## Lua 5.3 +| Feature | Supported? | Notes | +|---------------------------------------------------------------------------------------|------------|---------------------------| +| Integer subtype | ❌ | | +| Bitwise operators/floor division | ❌ | | +| `\u{XXX}` escape sequence | ✔ | | +| `utf8` library | ✔ | | +| removed `__ipairs` metamethod | ✔ | | +| `coroutine.isyieldable` | ❌ | | +| `string.dump` strip argument | ✔ | | +| `string.pack`/`string.unpack`/`string.packsize` | ✔ | | +| `table.move` | ❌ | | +| `math.atan2` -> `math.atan` | ❌ | | +| Removed `math.frexp`, `math.ldexp`, `math.pow`, `math.cosh`, `math.sinh`, `math.tanh` | ❌ | | +| `math.maxinteger`/`math.mininteger` | ❌ | | +| `math.tointeger` | ❌ | | +| `math.type` | ❌ | | +| `math.ult` | ❌ | | +| Removed `bit32` library | ❌ | | +| Remove `*` from `file:read` modes | ✔ | | +| Metamethods respected in `table.*`, `ipairs` | 🔶 | Only `__lt` is respected. | + +## Lua 5.0 +| Feature | Supported? | Notes | +|----------------------------------|------------|--------------------------------------------------| +| `arg` table | 🔶 | Only set in the shell - not used in functions. | +| `string.gfind` | ✔ | Equal to `string.gmatch`. | +| `table.getn` | ✔ | Equal to `#tbl`. | +| `table.setn` | ❌ | | +| `math.mod` | ✔ | Equal to `math.fmod`. | +| `table.foreach`/`table.foreachi` | ✔ | | +| `gcinfo` | ❌ | Cobalt uses the built-in Java garbage collector. | diff --git a/doc/stub/fs.lua b/doc/stub/fs.lua index aa40a30f3..e2d8f0741 100644 --- a/doc/stub/fs.lua +++ b/doc/stub/fs.lua @@ -1,6 +1,4 @@ ---- The FS API allows you to manipulate files and the filesystem. --- --- @module fs +--- @module fs --- Returns true if a path is mounted to the parent filesystem. -- diff --git a/doc/stub/global.lua b/doc/stub/global.lua index 6541da7e5..7bd8a7311 100644 --- a/doc/stub/global.lua +++ b/doc/stub/global.lua @@ -62,7 +62,7 @@ function print(...) end -- @usage printError("Something went wrong!") function printError(...) end ---[[- Reads user input from the terminal, automatically handling arrow keys, +--[[- Reads user input from the terminal. This automatically handles arrow keys, pasting, character replacement, history scrollback, auto-completion, and default values. @@ -110,10 +110,15 @@ the prompt. ]] function read(replaceChar, history, completeFn, default) end ---- The ComputerCraft and Minecraft version of the current computer environment. +--- Stores the current ComputerCraft and Minecraft versions. +-- +-- Outside of Minecraft (for instance, in an emulator) @{_HOST} will contain the +-- emulator's version instead. -- -- For example, `ComputerCraft 1.93.0 (Minecraft 1.15.2)`. --- @usage _HOST +-- @usage Print the current computer's environment. +-- +-- print(_HOST) -- @since 1.76 _HOST = _HOST diff --git a/doc/stub/http.lua b/doc/stub/http.lua index 81ba63753..6ec4db8d3 100644 --- a/doc/stub/http.lua +++ b/doc/stub/http.lua @@ -1,14 +1,13 @@ ---- The http library allows communicating with web servers, sending and --- receiving data from them. +--- Make HTTP requests, sending and receiving data to a remote web server. -- -- @module http -- @since 1.1 +-- @see local_ips To allow accessing servers running on your local network. --- Asynchronously make a HTTP request to the given url. -- --- This returns immediately, a [`http_success`](#http-success-event) or --- [`http_failure`](#http-failure-event) will be queued once the request has --- completed. +-- This returns immediately, a @{http_success} or @{http_failure} will be queued +-- once the request has completed. -- -- @tparam string url The url to request -- @tparam[opt] string body An optional string containing the body of the @@ -112,9 +111,8 @@ function post(...) end --- Asynchronously determine whether a URL can be requested. -- --- If this returns `true`, one should also listen for [`http_check` --- events](#http-check-event) which will container further information about --- whether the URL is allowed or not. +-- If this returns `true`, one should also listen for @{http_check} which will +-- container further information about whether the URL is allowed or not. -- -- @tparam string url The URL to check. -- @treturn true When this url is not invalid. This does not imply that it is @@ -128,9 +126,8 @@ function checkURLAsync(url) end --- Determine whether a URL can be requested. -- --- If this returns `true`, one should also listen for [`http_check` --- events](#http-check-event) which will container further information about --- whether the URL is allowed or not. +-- If this returns `true`, one should also listen for @{http_check} which will +-- container further information about whether the URL is allowed or not. -- -- @tparam string url The URL to check. -- @treturn true When this url is valid and can be requested via @{http.request}. @@ -168,9 +165,8 @@ function websocket(url, headers) end --- Asynchronously open a websocket. -- --- This returns immediately, a [`websocket_success`](#websocket-success-event) --- or [`websocket_failure`](#websocket-failure-event) will be queued once the --- request has completed. +-- This returns immediately, a @{websocket_success} or @{websocket_failure} +-- will be queued once the request has completed. -- -- @tparam string url The websocket url to connect to. This should have the -- `ws://` or `wss://` protocol. diff --git a/gradle.properties b/gradle.properties index 3e2464221..11f25ee2a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ # Mod properties -mod_version=1.100.4 +mod_version=1.100.5 # Minecraft properties (update mods.toml when changing) mc_version=1.16.5 mapping_version=2021.08.08 -forge_version=36.2.20 +forge_version=36.2.34 # NO SERIOUSLY, UPDATE mods.toml WHEN CHANGING diff --git a/illuaminate.sexp b/illuaminate.sexp index 91d1e99ef..7ddafe9e5 100644 --- a/illuaminate.sexp +++ b/illuaminate.sexp @@ -1,9 +1,7 @@ ; -*- mode: Lisp;-*- (sources - /doc/events/ - /doc/guides/ - /doc/stub/ + /doc/ /build/docs/luaJavadoc/ /src/main/resources/*/computercraft/lua/bios.lua /src/main/resources/*/computercraft/lua/rom/ @@ -29,7 +27,8 @@ (peripheral Peripherals) (generic_peripheral "Generic Peripherals") (event Events) - (guide Guides)) + (guide Guides) + (reference Reference)) (library-path /doc/stub/ diff --git a/package-lock.json b/package-lock.json index ec3137ce0..2cfb30386 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,18 @@ "devDependencies": { "@rollup/plugin-typescript": "^8.2.5", "@rollup/plugin-url": "^6.1.0", + "@types/glob": "^7.2.0", + "@types/react-dom": "^18.0.5", + "glob": "^8.0.3", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "rehype": "^12.0.1", + "rehype-highlight": "^5.0.2", + "rehype-react": "^7.1.1", "requirejs": "^2.3.6", "rollup": "^2.33.1", "rollup-plugin-terser": "^7.0.2", + "ts-node": "^10.8.0", "typescript": "^4.0.5" } }, @@ -56,6 +65,79 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mapbox/hast-util-table-cell-style": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.2.0.tgz", + "integrity": "sha512-gqaTIGC8My3LVSnU38IwjHVKJC94HSonjvFHDk8/aSrApL8v4uWgm8zJkK7MJIIbHuNOr/+Mv2KkQKcxs6LEZA==", + "dev": true, + "dependencies": { + "unist-util-visit": "^1.4.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@mapbox/hast-util-table-cell-style/node_modules/unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "node_modules/@mapbox/hast-util-table-cell-style/node_modules/unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "dependencies": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "node_modules/@mapbox/hast-util-table-cell-style/node_modules/unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "dependencies": { + "unist-util-is": "^3.0.0" + } + }, "node_modules/@rollup/plugin-typescript": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz", @@ -108,18 +190,132 @@ "rollup": "^1.20.0||^2.0.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, "node_modules/@types/node": { "version": "16.11.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz", "integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==", "dev": true }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.5.tgz", + "integrity": "sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -132,12 +328,53 @@ "node": ">=4" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -152,6 +389,26 @@ "node": ">=4" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -167,12 +424,43 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "node_modules/comma-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", + "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -188,6 +476,40 @@ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", "dev": true }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dev": true, + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -208,6 +530,25 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -229,6 +570,200 @@ "node": ">=4" } }, + "node_modules/hast-to-hyperscript": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz", + "integrity": "sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.3.0", + "unist-util-is": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz", + "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", + "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz", + "integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", + "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.2", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.1.tgz", + "integrity": "sha512-7S3mOBxACy8syL45hCn3J7rHqYaXkxRfsX6LXEU5Shz4nt4GxdjtMUtG+T6G/ZLUHd7kslFAf14kAN71bz30xA==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unist-util-find-after": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", + "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.0.2.tgz", + "integrity": "sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/highlight.js": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.5.1.tgz", + "integrity": "sha512-LKzHqnxr4CrD2YsNoIf/o5nJ09j4yi/GcH5BnYz9UnVpZdS4ucMgvP61TDty5xJcFGRjnH4DpujkS9bHT3hq0Q==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "dev": true + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, "node_modules/is-core-module": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", @@ -241,6 +776,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz", + "integrity": "sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-worker": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", @@ -282,6 +829,33 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lowlight": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-2.6.1.tgz", + "integrity": "sha512-t0ueDL6SIn9FKHipm78CNjWcJQv0xi6WCjYAICyO6GyPzoT7E58yom1mNwvI7AMwVe3pLwwFT0Bt2gml7uaUeQ==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "fault": "^2.0.0", + "highlight.js": "~11.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -297,6 +871,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -315,6 +895,33 @@ "node": ">=4.0.0" } }, + "node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -342,6 +949,16 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/property-information": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", + "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -351,6 +968,115 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", + "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", + "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.22.0" + }, + "peerDependencies": { + "react": "^18.1.0" + } + }, + "node_modules/rehype": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.1.tgz", + "integrity": "sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "rehype-parse": "^8.0.0", + "rehype-stringify": "^9.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-highlight": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-5.0.2.tgz", + "integrity": "sha512-ZNm8V8BQUDn05cJPzAu/PjiloaFFrh+Pt3bY+NCcdCggI7Uyl5mW0FGR7RATeIz5/ECUd1D8Kvjt4HaLPmnOMw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-to-text": "^3.0.0", + "lowlight": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", + "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^6.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-react": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/rehype-react/-/rehype-react-7.1.1.tgz", + "integrity": "sha512-6yaitxM95JFsuTA74OH54OyvTAeRqR4/A6f45S2sEk2FEG04iGgsObcmEIFQRsjK9pfAhc74lR4iGf/W2G/sBw==", + "dev": true, + "dependencies": { + "@mapbox/hast-util-table-cell-style": "^0.2.0", + "@types/hast": "^2.0.0", + "hast-to-hyperscript": "^10.0.0", + "hast-util-whitespace": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=17" + } + }, + "node_modules/rehype-stringify": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", + "integrity": "sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-to-html": "^8.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/requirejs": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", @@ -427,6 +1153,15 @@ } ] }, + "node_modules/scheduler": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", + "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -473,6 +1208,39 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", + "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.2.tgz", + "integrity": "sha512-MTxTVcEkorNtBbNpoFJPEh0kKdM6+QbMjLbaxmvaPMmayOXdr/AIVIIJX7FReUVweRBFJfZepK4A4AKgwuFpMQ==", + "dev": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", + "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "dev": true, + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -510,6 +1278,59 @@ } } }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-node": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", + "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -527,6 +1348,166 @@ "engines": { "node": ">=4.2.0" } + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.0.tgz", + "integrity": "sha512-gfpsxKQde7atVF30n5Gff2fQhAc4/HTOV4CvkXpTg9wRfQhZWdXitpyXHWB6YcYgnsxLx+4gGHeVjCTAAp9sjw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", + "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz", + "integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz", + "integrity": "sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vfile": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.2.tgz", + "integrity": "sha512-w0PLIugRY3Crkgw89TeMvHCzqCs/zpreR31hl4D92y6SOE07+bfJe+dK5Q2akwS+i/c801kzjoOr9gMcTe6IAA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.0.1.tgz", + "integrity": "sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", + "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } }, "dependencies": { @@ -556,6 +1537,72 @@ "js-tokens": "^4.0.0" } }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", + "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@mapbox/hast-util-table-cell-style": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/hast-util-table-cell-style/-/hast-util-table-cell-style-0.2.0.tgz", + "integrity": "sha512-gqaTIGC8My3LVSnU38IwjHVKJC94HSonjvFHDk8/aSrApL8v4uWgm8zJkK7MJIIbHuNOr/+Mv2KkQKcxs6LEZA==", + "dev": true, + "requires": { + "unist-util-visit": "^1.4.1" + }, + "dependencies": { + "unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "requires": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "requires": { + "unist-util-is": "^3.0.0" + } + } + } + }, "@rollup/plugin-typescript": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz", @@ -588,18 +1635,123 @@ "picomatch": "^2.2.2" } }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, "@types/node": { "version": "16.11.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz", "integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==", "dev": true }, + "@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "dev": true + }, + "@types/react": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz", + "integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.5.tgz", + "integrity": "sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", + "dev": true + }, + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -609,12 +1761,45 @@ "color-convert": "^1.9.0" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -626,6 +1811,18 @@ "supports-color": "^5.3.0" } }, + "character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true + }, + "character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -641,12 +1838,36 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "comma-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", + "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", + "dev": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -659,6 +1880,33 @@ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", "dev": true }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dev": true, + "requires": { + "format": "^0.2.0" + } + }, + "format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -672,6 +1920,19 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -687,6 +1948,144 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "hast-to-hyperscript": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz", + "integrity": "sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.3.0", + "unist-util-is": "^5.0.0", + "web-namespaces": "^2.0.0" + } + }, + "hast-util-from-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz", + "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + } + }, + "hast-util-is-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", + "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + } + }, + "hast-util-parse-selector": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz", + "integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0" + } + }, + "hast-util-to-html": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", + "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.2", + "unist-util-is": "^5.0.0" + } + }, + "hast-util-to-text": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-3.1.1.tgz", + "integrity": "sha512-7S3mOBxACy8syL45hCn3J7rHqYaXkxRfsX6LXEU5Shz4nt4GxdjtMUtG+T6G/ZLUHd7kslFAf14kAN71bz30xA==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unist-util-find-after": "^4.0.0" + } + }, + "hast-util-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", + "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", + "dev": true + }, + "hastscript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.0.2.tgz", + "integrity": "sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + } + }, + "highlight.js": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.5.1.tgz", + "integrity": "sha512-LKzHqnxr4CrD2YsNoIf/o5nJ09j4yi/GcH5BnYz9UnVpZdS4ucMgvP61TDty5xJcFGRjnH4DpujkS9bHT3hq0Q==", + "dev": true + }, + "html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "dev": true + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, "is-core-module": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", @@ -696,6 +2095,12 @@ "has": "^1.0.3" } }, + "is-plain-obj": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz", + "integrity": "sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw==", + "dev": true + }, "jest-worker": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", @@ -730,6 +2135,26 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lowlight": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-2.6.1.tgz", + "integrity": "sha512-t0ueDL6SIn9FKHipm78CNjWcJQv0xi6WCjYAICyO6GyPzoT7E58yom1mNwvI7AMwVe3pLwwFT0Bt2gml7uaUeQ==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "fault": "^2.0.0", + "highlight.js": "~11.5.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -739,6 +2164,12 @@ "semver": "^6.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -751,6 +2182,30 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -768,6 +2223,12 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.1.tgz", "integrity": "sha512-ydCg+ISIq70vqiThvNWStZWLRjR9U2awP/JAmGdWUKm9+Tyuy+MqVdAIyEByeIspAVtD4GWC/SJtxO8XD4knVA==" }, + "property-information": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", + "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -777,6 +2238,86 @@ "safe-buffer": "^5.1.0" } }, + "react": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", + "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", + "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.22.0" + } + }, + "rehype": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-12.0.1.tgz", + "integrity": "sha512-ey6kAqwLM3X6QnMDILJthGvG1m1ULROS9NT4uG9IDCuv08SFyLlreSuvOa//DgEvbXx62DS6elGVqusWhRUbgw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "rehype-parse": "^8.0.0", + "rehype-stringify": "^9.0.0", + "unified": "^10.0.0" + } + }, + "rehype-highlight": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-5.0.2.tgz", + "integrity": "sha512-ZNm8V8BQUDn05cJPzAu/PjiloaFFrh+Pt3bY+NCcdCggI7Uyl5mW0FGR7RATeIz5/ECUd1D8Kvjt4HaLPmnOMw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-to-text": "^3.0.0", + "lowlight": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "rehype-parse": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-8.0.4.tgz", + "integrity": "sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^7.0.0", + "parse5": "^6.0.0", + "unified": "^10.0.0" + } + }, + "rehype-react": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/rehype-react/-/rehype-react-7.1.1.tgz", + "integrity": "sha512-6yaitxM95JFsuTA74OH54OyvTAeRqR4/A6f45S2sEk2FEG04iGgsObcmEIFQRsjK9pfAhc74lR4iGf/W2G/sBw==", + "dev": true, + "requires": { + "@mapbox/hast-util-table-cell-style": "^0.2.0", + "@types/hast": "^2.0.0", + "hast-to-hyperscript": "^10.0.0", + "hast-util-whitespace": "^2.0.0", + "unified": "^10.0.0" + } + }, + "rehype-stringify": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", + "integrity": "sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "hast-util-to-html": "^8.0.0", + "unified": "^10.0.0" + } + }, "requirejs": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", @@ -820,6 +2361,15 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, + "scheduler": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", + "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -859,6 +2409,31 @@ } } }, + "space-separated-tokens": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", + "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", + "dev": true + }, + "stringify-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.2.tgz", + "integrity": "sha512-MTxTVcEkorNtBbNpoFJPEh0kKdM6+QbMjLbaxmvaPMmayOXdr/AIVIIJX7FReUVweRBFJfZepK4A4AKgwuFpMQ==", + "dev": true, + "requires": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + } + }, + "style-to-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", + "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "dev": true, + "requires": { + "inline-style-parser": "0.1.1" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -879,6 +2454,33 @@ "source-map-support": "~0.5.20" } }, + "trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "dev": true + }, + "ts-node": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz", + "integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -889,6 +2491,123 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", "dev": true + }, + "unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + } + }, + "unist-util-find-after": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-4.0.0.tgz", + "integrity": "sha512-gfpsxKQde7atVF30n5Gff2fQhAc4/HTOV4CvkXpTg9wRfQhZWdXitpyXHWB6YcYgnsxLx+4gGHeVjCTAAp9sjw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, + "unist-util-is": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", + "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", + "dev": true + }, + "unist-util-stringify-position": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz", + "integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + } + }, + "unist-util-visit-parents": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz", + "integrity": "sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "vfile": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.2.tgz", + "integrity": "sha512-w0PLIugRY3Crkgw89TeMvHCzqCs/zpreR31hl4D92y6SOE07+bfJe+dK5Q2akwS+i/c801kzjoOr9gMcTe6IAA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "vfile-location": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.0.1.tgz", + "integrity": "sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + } + }, + "vfile-message": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", + "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, + "web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index 123f4dbd2..d9aab4b5f 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Website additions for tweaked.cc", "author": "SquidDev", "license": "BSD-3-Clause", + "type": "module", "dependencies": { "preact": "^10.5.5", "tslib": "^2.0.3" @@ -11,9 +12,18 @@ "devDependencies": { "@rollup/plugin-typescript": "^8.2.5", "@rollup/plugin-url": "^6.1.0", + "@types/glob": "^7.2.0", + "@types/react-dom": "^18.0.5", + "glob": "^8.0.3", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "rehype": "^12.0.1", + "rehype-highlight": "^5.0.2", + "rehype-react": "^7.1.1", "requirejs": "^2.3.6", "rollup": "^2.33.1", "rollup-plugin-terser": "^7.0.2", + "ts-node": "^10.8.0", "typescript": "^4.0.5" } } diff --git a/src/generated/export/index.json b/src/generated/export/index.json new file mode 100644 index 000000000..f4bfe9cac --- /dev/null +++ b/src/generated/export/index.json @@ -0,0 +1,760 @@ +{ + "itemNames": { + "computercraft:cable": "Networking Cable", + "computercraft:computer_advanced": "Advanced Computer", + "computercraft:computer_command": "Command Computer", + "computercraft:computer_normal": "Computer", + "computercraft:disk": "Floppy Disk", + "computercraft:disk_drive": "Disk Drive", + "computercraft:monitor_advanced": "Advanced Monitor", + "computercraft:monitor_normal": "Monitor", + "computercraft:pocket_computer_advanced": "Advanced Pocket Computer", + "computercraft:pocket_computer_normal": "Pocket Computer", + "computercraft:printed_book": "Printed Book", + "computercraft:printed_page": "Printed Page", + "computercraft:printed_pages": "Printed Pages", + "computercraft:printer": "Printer", + "computercraft:speaker": "Speaker", + "computercraft:treasure_disk": "Floppy Disk", + "computercraft:turtle_advanced": "Advanced Turtle", + "computercraft:turtle_normal": "Turtle", + "computercraft:wired_modem": "Wired Modem", + "computercraft:wired_modem_full": "Wired Modem", + "computercraft:wireless_modem_advanced": "Ender Modem", + "computercraft:wireless_modem_normal": "Wireless Modem", + "minecraft:black_dye": "Black Dye", + "minecraft:blue_dye": "Blue Dye", + "minecraft:brown_dye": "Brown Dye", + "minecraft:chest": "Chest", + "minecraft:command_block": "Command Block", + "minecraft:cyan_dye": "Cyan Dye", + "minecraft:ender_eye": "Eye of Ender", + "minecraft:ender_pearl": "Ender Pearl", + "minecraft:glass_pane": "Glass Pane", + "minecraft:gold_block": "Block of Gold", + "minecraft:gold_ingot": "Gold Ingot", + "minecraft:golden_apple": "Golden Apple", + "minecraft:gray_dye": "Gray Dye", + "minecraft:green_dye": "Green Dye", + "minecraft:iron_ingot": "Iron Ingot", + "minecraft:leather": "Leather", + "minecraft:light_blue_dye": "Light Blue Dye", + "minecraft:light_gray_dye": "Light Gray Dye", + "minecraft:lime_dye": "Lime Dye", + "minecraft:magenta_dye": "Magenta Dye", + "minecraft:note_block": "Note Block", + "minecraft:orange_dye": "Orange Dye", + "minecraft:pink_dye": "Pink Dye", + "minecraft:purple_dye": "Purple Dye", + "minecraft:red_dye": "Red Dye", + "minecraft:redstone": "Redstone Dust", + "minecraft:stone": "Stone", + "minecraft:string": "String", + "minecraft:white_dye": "White Dye", + "minecraft:yellow_dye": "Yellow Dye" + }, + "recipes": { + "computercraft:cable": { + "inputs": [ + null, + [ + "minecraft:stone" + ], + null, + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ], + null, + [ + "minecraft:stone" + ], + null + ], + "output": "computercraft:cable", + "count": 6 + }, + "computercraft:computer_advanced": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:computer_advanced", + "count": 1 + }, + "computercraft:computer_advanced_upgrade": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "computercraft:computer_normal" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + null, + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:computer_advanced", + "count": 1 + }, + "computercraft:computer_command": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:command_block" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:computer_command", + "count": 1 + }, + "computercraft:computer_normal": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:computer_normal", + "count": 1 + }, + "computercraft:disk_drive": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:disk_drive", + "count": 1 + }, + "computercraft:monitor_advanced": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:monitor_advanced", + "count": 4 + }, + "computercraft:monitor_normal": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:monitor_normal", + "count": 1 + }, + "computercraft:pocket_computer_advanced": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:golden_apple" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:pocket_computer_advanced", + "count": 1 + }, + "computercraft:pocket_computer_advanced_upgrade": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "computercraft:pocket_computer_normal" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + null, + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:pocket_computer_advanced", + "count": 1 + }, + "computercraft:pocket_computer_normal": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:golden_apple" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:glass_pane" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:pocket_computer_normal", + "count": 1 + }, + "computercraft:printed_book": { + "inputs": [ + [ + "minecraft:leather" + ], + [ + "computercraft:printed_page" + ], + [ + "minecraft:string" + ], + null, + null, + null, + null, + null, + null + ], + "output": "computercraft:printed_book", + "count": 1 + }, + "computercraft:printed_pages": { + "inputs": [ + [ + "computercraft:printed_page" + ], + [ + "computercraft:printed_page" + ], + [ + "minecraft:string" + ], + null, + null, + null, + null, + null, + null + ], + "output": "computercraft:printed_pages", + "count": 1 + }, + "computercraft:printer": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:black_dye", + "minecraft:blue_dye", + "minecraft:brown_dye", + "minecraft:cyan_dye", + "minecraft:gray_dye", + "minecraft:green_dye", + "minecraft:light_blue_dye", + "minecraft:light_gray_dye", + "minecraft:lime_dye", + "minecraft:magenta_dye", + "minecraft:orange_dye", + "minecraft:pink_dye", + "minecraft:purple_dye", + "minecraft:red_dye", + "minecraft:white_dye", + "minecraft:yellow_dye" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:printer", + "count": 1 + }, + "computercraft:speaker": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:note_block" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:speaker", + "count": 1 + }, + "computercraft:turtle_advanced": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "computercraft:computer_advanced" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:chest" + ], + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:turtle_advanced", + "count": 1 + }, + "computercraft:turtle_advanced_upgrade": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "computercraft:turtle_normal" + ], + [ + "minecraft:gold_ingot" + ], + null, + [ + "minecraft:gold_block" + ], + null + ], + "output": "computercraft:turtle_advanced", + "count": 1 + }, + "computercraft:turtle_normal": { + "inputs": [ + [ + "minecraft:iron_ingot" + ], + [ + "minecraft:iron_ingot" + ], + [ + "minecraft:iron_ingot" + ], + [ + "minecraft:iron_ingot" + ], + [ + "computercraft:computer_normal" + ], + [ + "minecraft:iron_ingot" + ], + [ + "minecraft:iron_ingot" + ], + [ + "minecraft:chest" + ], + [ + "minecraft:iron_ingot" + ] + ], + "output": "computercraft:turtle_normal", + "count": 1 + }, + "computercraft:wired_modem": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:redstone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:wired_modem", + "count": 1 + }, + "computercraft:wired_modem_full_from": { + "inputs": [ + [ + "computercraft:wired_modem" + ], + null, + null, + null, + null, + null, + null, + null, + null + ], + "output": "computercraft:wired_modem_full", + "count": 1 + }, + "computercraft:wired_modem_full_to": { + "inputs": [ + [ + "computercraft:wired_modem_full" + ], + null, + null, + null, + null, + null, + null, + null, + null + ], + "output": "computercraft:wired_modem", + "count": 1 + }, + "computercraft:wireless_modem_advanced": { + "inputs": [ + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:ender_eye" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ], + [ + "minecraft:gold_ingot" + ] + ], + "output": "computercraft:wireless_modem_advanced", + "count": 1 + }, + "computercraft:wireless_modem_normal": { + "inputs": [ + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:ender_pearl" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ], + [ + "minecraft:stone" + ] + ], + "output": "computercraft:wireless_modem_normal", + "count": 1 + } + } +} diff --git a/src/generated/export/items/computercraft/cable.png b/src/generated/export/items/computercraft/cable.png new file mode 100644 index 000000000..593ed2373 Binary files /dev/null and b/src/generated/export/items/computercraft/cable.png differ diff --git a/src/generated/export/items/computercraft/computer_advanced.png b/src/generated/export/items/computercraft/computer_advanced.png new file mode 100644 index 000000000..2b9f5f5a3 Binary files /dev/null and b/src/generated/export/items/computercraft/computer_advanced.png differ diff --git a/src/generated/export/items/computercraft/computer_command.png b/src/generated/export/items/computercraft/computer_command.png new file mode 100644 index 000000000..d8f65c75d Binary files /dev/null and b/src/generated/export/items/computercraft/computer_command.png differ diff --git a/src/generated/export/items/computercraft/computer_normal.png b/src/generated/export/items/computercraft/computer_normal.png new file mode 100644 index 000000000..fa1787e89 Binary files /dev/null and b/src/generated/export/items/computercraft/computer_normal.png differ diff --git a/src/generated/export/items/computercraft/disk.png b/src/generated/export/items/computercraft/disk.png new file mode 100644 index 000000000..0f11b0762 Binary files /dev/null and b/src/generated/export/items/computercraft/disk.png differ diff --git a/src/generated/export/items/computercraft/disk_drive.png b/src/generated/export/items/computercraft/disk_drive.png new file mode 100644 index 000000000..158aafddf Binary files /dev/null and b/src/generated/export/items/computercraft/disk_drive.png differ diff --git a/src/generated/export/items/computercraft/monitor_advanced.png b/src/generated/export/items/computercraft/monitor_advanced.png new file mode 100644 index 000000000..5e99f1a3f Binary files /dev/null and b/src/generated/export/items/computercraft/monitor_advanced.png differ diff --git a/src/generated/export/items/computercraft/monitor_normal.png b/src/generated/export/items/computercraft/monitor_normal.png new file mode 100644 index 000000000..b80d1f84f Binary files /dev/null and b/src/generated/export/items/computercraft/monitor_normal.png differ diff --git a/src/generated/export/items/computercraft/pocket_computer_advanced.png b/src/generated/export/items/computercraft/pocket_computer_advanced.png new file mode 100644 index 000000000..e3865e062 Binary files /dev/null and b/src/generated/export/items/computercraft/pocket_computer_advanced.png differ diff --git a/src/generated/export/items/computercraft/pocket_computer_normal.png b/src/generated/export/items/computercraft/pocket_computer_normal.png new file mode 100644 index 000000000..25f6ee700 Binary files /dev/null and b/src/generated/export/items/computercraft/pocket_computer_normal.png differ diff --git a/src/generated/export/items/computercraft/printed_book.png b/src/generated/export/items/computercraft/printed_book.png new file mode 100644 index 000000000..ee4b235f7 Binary files /dev/null and b/src/generated/export/items/computercraft/printed_book.png differ diff --git a/src/generated/export/items/computercraft/printed_page.png b/src/generated/export/items/computercraft/printed_page.png new file mode 100644 index 000000000..b27f175dd Binary files /dev/null and b/src/generated/export/items/computercraft/printed_page.png differ diff --git a/src/generated/export/items/computercraft/printed_pages.png b/src/generated/export/items/computercraft/printed_pages.png new file mode 100644 index 000000000..182902e96 Binary files /dev/null and b/src/generated/export/items/computercraft/printed_pages.png differ diff --git a/src/generated/export/items/computercraft/printer.png b/src/generated/export/items/computercraft/printer.png new file mode 100644 index 000000000..4fe8a80da Binary files /dev/null and b/src/generated/export/items/computercraft/printer.png differ diff --git a/src/generated/export/items/computercraft/speaker.png b/src/generated/export/items/computercraft/speaker.png new file mode 100644 index 000000000..c30e8355b Binary files /dev/null and b/src/generated/export/items/computercraft/speaker.png differ diff --git a/src/generated/export/items/computercraft/treasure_disk.png b/src/generated/export/items/computercraft/treasure_disk.png new file mode 100644 index 000000000..2aa8a62b7 Binary files /dev/null and b/src/generated/export/items/computercraft/treasure_disk.png differ diff --git a/src/generated/export/items/computercraft/turtle_advanced.png b/src/generated/export/items/computercraft/turtle_advanced.png new file mode 100644 index 000000000..6313e8462 Binary files /dev/null and b/src/generated/export/items/computercraft/turtle_advanced.png differ diff --git a/src/generated/export/items/computercraft/turtle_normal.png b/src/generated/export/items/computercraft/turtle_normal.png new file mode 100644 index 000000000..7ab1e0786 Binary files /dev/null and b/src/generated/export/items/computercraft/turtle_normal.png differ diff --git a/src/generated/export/items/computercraft/wired_modem.png b/src/generated/export/items/computercraft/wired_modem.png new file mode 100644 index 000000000..841d3dc73 Binary files /dev/null and b/src/generated/export/items/computercraft/wired_modem.png differ diff --git a/src/generated/export/items/computercraft/wired_modem_full.png b/src/generated/export/items/computercraft/wired_modem_full.png new file mode 100644 index 000000000..8d5a0ea5f Binary files /dev/null and b/src/generated/export/items/computercraft/wired_modem_full.png differ diff --git a/src/generated/export/items/computercraft/wireless_modem_advanced.png b/src/generated/export/items/computercraft/wireless_modem_advanced.png new file mode 100644 index 000000000..f9853d978 Binary files /dev/null and b/src/generated/export/items/computercraft/wireless_modem_advanced.png differ diff --git a/src/generated/export/items/computercraft/wireless_modem_normal.png b/src/generated/export/items/computercraft/wireless_modem_normal.png new file mode 100644 index 000000000..287a1738f Binary files /dev/null and b/src/generated/export/items/computercraft/wireless_modem_normal.png differ diff --git a/src/generated/export/items/minecraft/black_dye.png b/src/generated/export/items/minecraft/black_dye.png new file mode 100644 index 000000000..4b5e52560 Binary files /dev/null and b/src/generated/export/items/minecraft/black_dye.png differ diff --git a/src/generated/export/items/minecraft/blue_dye.png b/src/generated/export/items/minecraft/blue_dye.png new file mode 100644 index 000000000..0edf68729 Binary files /dev/null and b/src/generated/export/items/minecraft/blue_dye.png differ diff --git a/src/generated/export/items/minecraft/brown_dye.png b/src/generated/export/items/minecraft/brown_dye.png new file mode 100644 index 000000000..7263ee463 Binary files /dev/null and b/src/generated/export/items/minecraft/brown_dye.png differ diff --git a/src/generated/export/items/minecraft/chest.png b/src/generated/export/items/minecraft/chest.png new file mode 100644 index 000000000..19f6cab55 Binary files /dev/null and b/src/generated/export/items/minecraft/chest.png differ diff --git a/src/generated/export/items/minecraft/command_block.png b/src/generated/export/items/minecraft/command_block.png new file mode 100644 index 000000000..cef1cac2e Binary files /dev/null and b/src/generated/export/items/minecraft/command_block.png differ diff --git a/src/generated/export/items/minecraft/cyan_dye.png b/src/generated/export/items/minecraft/cyan_dye.png new file mode 100644 index 000000000..03d946dbb Binary files /dev/null and b/src/generated/export/items/minecraft/cyan_dye.png differ diff --git a/src/generated/export/items/minecraft/ender_eye.png b/src/generated/export/items/minecraft/ender_eye.png new file mode 100644 index 000000000..caae6f4d6 Binary files /dev/null and b/src/generated/export/items/minecraft/ender_eye.png differ diff --git a/src/generated/export/items/minecraft/ender_pearl.png b/src/generated/export/items/minecraft/ender_pearl.png new file mode 100644 index 000000000..539a50c68 Binary files /dev/null and b/src/generated/export/items/minecraft/ender_pearl.png differ diff --git a/src/generated/export/items/minecraft/glass_pane.png b/src/generated/export/items/minecraft/glass_pane.png new file mode 100644 index 000000000..64924e7fa Binary files /dev/null and b/src/generated/export/items/minecraft/glass_pane.png differ diff --git a/src/generated/export/items/minecraft/gold_block.png b/src/generated/export/items/minecraft/gold_block.png new file mode 100644 index 000000000..4331c0b18 Binary files /dev/null and b/src/generated/export/items/minecraft/gold_block.png differ diff --git a/src/generated/export/items/minecraft/gold_ingot.png b/src/generated/export/items/minecraft/gold_ingot.png new file mode 100644 index 000000000..ab4f787d6 Binary files /dev/null and b/src/generated/export/items/minecraft/gold_ingot.png differ diff --git a/src/generated/export/items/minecraft/golden_apple.png b/src/generated/export/items/minecraft/golden_apple.png new file mode 100644 index 000000000..88f462936 Binary files /dev/null and b/src/generated/export/items/minecraft/golden_apple.png differ diff --git a/src/generated/export/items/minecraft/gray_dye.png b/src/generated/export/items/minecraft/gray_dye.png new file mode 100644 index 000000000..6f5c3bda9 Binary files /dev/null and b/src/generated/export/items/minecraft/gray_dye.png differ diff --git a/src/generated/export/items/minecraft/green_dye.png b/src/generated/export/items/minecraft/green_dye.png new file mode 100644 index 000000000..46c6bb436 Binary files /dev/null and b/src/generated/export/items/minecraft/green_dye.png differ diff --git a/src/generated/export/items/minecraft/iron_ingot.png b/src/generated/export/items/minecraft/iron_ingot.png new file mode 100644 index 000000000..a9e1e21ed Binary files /dev/null and b/src/generated/export/items/minecraft/iron_ingot.png differ diff --git a/src/generated/export/items/minecraft/leather.png b/src/generated/export/items/minecraft/leather.png new file mode 100644 index 000000000..a7655ebb2 Binary files /dev/null and b/src/generated/export/items/minecraft/leather.png differ diff --git a/src/generated/export/items/minecraft/light_blue_dye.png b/src/generated/export/items/minecraft/light_blue_dye.png new file mode 100644 index 000000000..f41f3eb3e Binary files /dev/null and b/src/generated/export/items/minecraft/light_blue_dye.png differ diff --git a/src/generated/export/items/minecraft/light_gray_dye.png b/src/generated/export/items/minecraft/light_gray_dye.png new file mode 100644 index 000000000..a62673fea Binary files /dev/null and b/src/generated/export/items/minecraft/light_gray_dye.png differ diff --git a/src/generated/export/items/minecraft/lime_dye.png b/src/generated/export/items/minecraft/lime_dye.png new file mode 100644 index 000000000..c80489830 Binary files /dev/null and b/src/generated/export/items/minecraft/lime_dye.png differ diff --git a/src/generated/export/items/minecraft/magenta_dye.png b/src/generated/export/items/minecraft/magenta_dye.png new file mode 100644 index 000000000..628b15ab8 Binary files /dev/null and b/src/generated/export/items/minecraft/magenta_dye.png differ diff --git a/src/generated/export/items/minecraft/note_block.png b/src/generated/export/items/minecraft/note_block.png new file mode 100644 index 000000000..e41a8fc30 Binary files /dev/null and b/src/generated/export/items/minecraft/note_block.png differ diff --git a/src/generated/export/items/minecraft/orange_dye.png b/src/generated/export/items/minecraft/orange_dye.png new file mode 100644 index 000000000..ef0d8100f Binary files /dev/null and b/src/generated/export/items/minecraft/orange_dye.png differ diff --git a/src/generated/export/items/minecraft/pink_dye.png b/src/generated/export/items/minecraft/pink_dye.png new file mode 100644 index 000000000..992f90517 Binary files /dev/null and b/src/generated/export/items/minecraft/pink_dye.png differ diff --git a/src/generated/export/items/minecraft/purple_dye.png b/src/generated/export/items/minecraft/purple_dye.png new file mode 100644 index 000000000..318d73f5e Binary files /dev/null and b/src/generated/export/items/minecraft/purple_dye.png differ diff --git a/src/generated/export/items/minecraft/red_dye.png b/src/generated/export/items/minecraft/red_dye.png new file mode 100644 index 000000000..2b5f72bec Binary files /dev/null and b/src/generated/export/items/minecraft/red_dye.png differ diff --git a/src/generated/export/items/minecraft/redstone.png b/src/generated/export/items/minecraft/redstone.png new file mode 100644 index 000000000..0beaa512a Binary files /dev/null and b/src/generated/export/items/minecraft/redstone.png differ diff --git a/src/generated/export/items/minecraft/stone.png b/src/generated/export/items/minecraft/stone.png new file mode 100644 index 000000000..f5f74d1d1 Binary files /dev/null and b/src/generated/export/items/minecraft/stone.png differ diff --git a/src/generated/export/items/minecraft/string.png b/src/generated/export/items/minecraft/string.png new file mode 100644 index 000000000..6b82875cd Binary files /dev/null and b/src/generated/export/items/minecraft/string.png differ diff --git a/src/generated/export/items/minecraft/white_dye.png b/src/generated/export/items/minecraft/white_dye.png new file mode 100644 index 000000000..314ceaba9 Binary files /dev/null and b/src/generated/export/items/minecraft/white_dye.png differ diff --git a/src/generated/export/items/minecraft/yellow_dye.png b/src/generated/export/items/minecraft/yellow_dye.png new file mode 100644 index 000000000..6e1b056a3 Binary files /dev/null and b/src/generated/export/items/minecraft/yellow_dye.png differ diff --git a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java index 08432f0ff..dfbb26ed5 100644 --- a/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java +++ b/src/main/java/dan200/computercraft/ComputerCraftAPIImpl.java @@ -6,6 +6,7 @@ package dan200.computercraft; import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI; +import dan200.computercraft.api.detail.IDetailProvider; import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.lua.GenericSource; @@ -24,6 +25,7 @@ import dan200.computercraft.core.filesystem.ResourceMount; import dan200.computercraft.shared.*; import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; +import dan200.computercraft.shared.peripheral.generic.data.DetailProviders; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork; import dan200.computercraft.shared.util.IDAssigner; import dan200.computercraft.shared.wired.WiredNode; @@ -168,6 +170,12 @@ public void registerAPIFactory( @Nonnull ILuaAPIFactory factory ) ApiFactories.register( factory ); } + @Override + public void registerDetailProvider( @Nonnull Class type, @Nonnull IDetailProvider provider ) + { + DetailProviders.registerProvider( type, provider ); + } + @Nonnull @Override public IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ) diff --git a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java index 7daa7e6bf..dc00bb641 100644 --- a/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java +++ b/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java @@ -5,6 +5,8 @@ */ package dan200.computercraft.api; +import dan200.computercraft.api.detail.BlockReference; +import dan200.computercraft.api.detail.IDetailProvider; import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.lua.GenericSource; @@ -20,12 +22,14 @@ import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.redstone.IBundledRedstoneProvider; import dan200.computercraft.api.turtle.ITurtleUpgrade; +import net.minecraft.item.ItemStack; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -221,6 +225,20 @@ public static void registerAPIFactory( @Nonnull ILuaAPIFactory factory ) getInstance().registerAPIFactory( factory ); } + /** + * Registers a detail provider to provide additional details for blocks, fluids and items when inspected by methods + * such as {@code turtle.getItemDetail()} or {@code turtle.inspect()}. + * + * @param type The type of object that this provider can provide details for. Should be {@link BlockReference}, + * {@link FluidStack} or {@link ItemStack}. + * @param provider The detail provider to register. + * @param The type of object that this provider can provide details for. + */ + public static void registerDetailProvider( @Nonnull Class type, @Nonnull IDetailProvider provider ) + { + getInstance().registerDetailProvider( type, provider ); + } + /** * Construct a new wired node for a given wired element. * @@ -301,6 +319,8 @@ public interface IComputerCraftAPI void registerAPIFactory( @Nonnull ILuaAPIFactory factory ); + void registerDetailProvider( @Nonnull Class type, @Nonnull IDetailProvider provider ); + @Nonnull IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element ); diff --git a/src/main/java/dan200/computercraft/api/detail/BasicItemDetailProvider.java b/src/main/java/dan200/computercraft/api/detail/BasicItemDetailProvider.java new file mode 100644 index 000000000..10398d8bd --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/BasicItemDetailProvider.java @@ -0,0 +1,79 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. 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.detail; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type. + * + * @param The type the stack's item must have. + */ +public abstract class BasicItemDetailProvider implements IDetailProvider +{ + private final Class itemType; + private final String namespace; + + /** + * Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}. + * + * @param itemType The type the stack's item must have. + * @param namespace The namespace to use for this provider. + */ + public BasicItemDetailProvider( String namespace, @Nonnull Class itemType ) + { + Objects.requireNonNull( itemType ); + this.itemType = itemType; + this.namespace = namespace; + } + + /** + * Create a new item detail provider. Meta will be inserted directly into the results. + * + * @param itemType The type the stack's item must have. + */ + public BasicItemDetailProvider( @Nonnull Class itemType ) + { + this( null, itemType ); + } + + /** + * Provide additional details for the given {@link Item} and {@link ItemStack}. This method is called by + * {@code turtle.getItemDetail()}. New properties should be added to the given {@link Map}, {@code data}. + * + * This method is always called on the server thread, so it is safe to interact with the world here, but you should + * take care to avoid long blocking operations as this will stall the server and other computers. + * + * @param data The full details to be returned for this item stack. New properties should be added to this map. + * @param stack The item stack to provide details for. + * @param item The item to provide details for. + */ + public abstract void provideDetails( @Nonnull Map data, @Nonnull ItemStack stack, + @Nonnull T item ); + + @Override + public void provideDetails( @Nonnull Map data, @Nonnull ItemStack stack ) + { + Item item = stack.getItem(); + if ( !itemType.isInstance( item ) ) return; + + // If `namespace` is specified, insert into a new data map instead of the existing one. + Map child = namespace == null ? data : new HashMap<>(); + + provideDetails( child, stack, itemType.cast( item ) ); + + if ( namespace != null ) + { + data.put( namespace, child ); + } + } +} diff --git a/src/main/java/dan200/computercraft/api/detail/BlockReference.java b/src/main/java/dan200/computercraft/api/detail/BlockReference.java new file mode 100644 index 000000000..03aa443cb --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/BlockReference.java @@ -0,0 +1,65 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. 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.detail; + +import net.minecraft.block.BlockState; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A reference to a block in the world, used by block detail providers. + */ +public class BlockReference +{ + private final World world; + private final BlockPos pos; + private final BlockState state; + private final TileEntity blockEntity; + + public BlockReference( World world, BlockPos pos ) + { + this.world = world; + this.pos = pos; + this.state = world.getBlockState( pos ); + this.blockEntity = world.getBlockEntity( pos ); + } + + public BlockReference( World world, BlockPos pos, BlockState state, TileEntity blockEntity ) + { + this.world = world; + this.pos = pos; + this.state = state; + this.blockEntity = blockEntity; + } + + @Nonnull + public World getWorld() + { + return world; + } + + @Nonnull + public BlockPos getPos() + { + return pos; + } + + @Nonnull + public BlockState getState() + { + return state; + } + + @Nullable + public TileEntity getBlockEntity() + { + return blockEntity; + } +} diff --git a/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java b/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java new file mode 100644 index 000000000..74841bc8c --- /dev/null +++ b/src/main/java/dan200/computercraft/api/detail/IDetailProvider.java @@ -0,0 +1,33 @@ +/* + * This file is part of the public ComputerCraft API - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. 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.detail; + +import javax.annotation.Nonnull; +import java.util.Map; + +/** + * This interface is used to provide details about a block, fluid, or item. + * + * @param The type of object that this provider can provide details for. + * + * @see dan200.computercraft.api.ComputerCraftAPI#registerDetailProvider(Class, IDetailProvider) + */ +@FunctionalInterface +public interface IDetailProvider +{ + /** + * Provide additional details for the given object. This method is called by functions such as + * {@code turtle.getItemDetail()} and {@code turtle.inspect()}. New properties should be added to the given + * {@link Map}, {@code data}. + * + * This method is always called on the server thread, so it is safe to interact with the world here, but you should + * take care to avoid long blocking operations as this will stall the server and other computers. + * + * @param data The full details to be returned. New properties should be added to this map. + * @param object The object to provide details for. + */ + void provideDetails( @Nonnull Map data, @Nonnull T object ); +} diff --git a/src/main/java/dan200/computercraft/api/lua/IArguments.java b/src/main/java/dan200/computercraft/api/lua/IArguments.java index 0557665d6..731b00c45 100644 --- a/src/main/java/dan200/computercraft/api/lua/IArguments.java +++ b/src/main/java/dan200/computercraft/api/lua/IArguments.java @@ -188,8 +188,8 @@ default > T getEnum( int index, Class klass ) throws LuaExc * * Classes implementing this interface may choose to implement a more optimised version which does not copy the * table, instead returning a wrapper version, making it more efficient. However, the caller must guarantee that - * they do not access off the computer thread (and so should not be used with main-thread functions) or once the - * function call has finished (for instance, in callbacks). + * they do not access the table the computer thread (and so should not be used with main-thread functions) or once + * the initial call has finished (for instance, in a callback to {@link MethodResult#pullEvent}). * * @param index The argument number. * @return The argument's value. @@ -448,7 +448,10 @@ default String optString( int index, String def ) throws LuaException * This is called when the current function finishes, before any main thread tasks have run. * * Called when the current function returns, and so some values are no longer guaranteed to be safe to access. + * + * @deprecated This method was an internal implementation detail and is no longer used. */ + @Deprecated default void releaseImmediate() { } diff --git a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java index 2fa9c8fe8..f6eddbbdb 100644 --- a/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java +++ b/src/main/java/dan200/computercraft/api/lua/ObjectArguments.java @@ -5,12 +5,10 @@ */ package dan200.computercraft.api.lua; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; /** * An implementation of {@link IArguments} which wraps an array of {@link Object}. @@ -19,7 +17,6 @@ public final class ObjectArguments implements IArguments { private static final IArguments EMPTY = new ObjectArguments(); - private boolean released = false; private final List args; @Deprecated @@ -67,34 +64,4 @@ public Object[] getAll() { return args.toArray(); } - - @Nonnull - @Override - public LuaTable getTableUnsafe( int index ) throws LuaException - { - if( released ) - { - throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); - } - - return IArguments.super.getTableUnsafe( index ); - } - - @Nonnull - @Override - public Optional> optTableUnsafe( int index ) throws LuaException - { - if( released ) - { - throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); - } - - return IArguments.super.optTableUnsafe( index ); - } - - @Override - public void releaseImmediate() - { - released = true; - } } diff --git a/src/main/java/dan200/computercraft/client/ClientRegistry.java b/src/main/java/dan200/computercraft/client/ClientRegistry.java index 362de0760..b9e553b9b 100644 --- a/src/main/java/dan200/computercraft/client/ClientRegistry.java +++ b/src/main/java/dan200/computercraft/client/ClientRegistry.java @@ -122,8 +122,6 @@ public static void onItemColours( ColorHandlerEvent.Item event ) @SubscribeEvent public static void setupClient( FMLClientSetupEvent event ) { - registerContainers(); - // While turtles themselves are not transparent, their upgrades may be. RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() ); RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() ); @@ -138,14 +136,18 @@ public static void setupClient( FMLClientSetupEvent event ) net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new ); net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new ); - registerItemProperty( "state", - ( stack, world, player ) -> ItemPocketComputer.getState( stack ).ordinal(), - Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED - ); - registerItemProperty( "coloured", - ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0, - Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED - ); + event.enqueueWork( () -> { + registerContainers(); + + registerItemProperty( "state", + ( stack, world, player ) -> ItemPocketComputer.getState( stack ).ordinal(), + Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED + ); + registerItemProperty( "coloured", + ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0, + Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED + ); + } ); } @SafeVarargs diff --git a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java index 9f1e85f0a..92b942dcf 100644 --- a/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java +++ b/src/main/java/dan200/computercraft/client/gui/ComputerScreenBase.java @@ -58,7 +58,7 @@ public ComputerScreenBase( T container, PlayerInventory player, ITextComponent t protected abstract WidgetTerminal createTerminal(); @Override - protected final void init() + protected void init() { super.init(); minecraft.keyboardHandler.setSendRepeatsToGui( true ); @@ -69,21 +69,21 @@ protected final void init() } @Override - public final void removed() + public void removed() { super.removed(); minecraft.keyboardHandler.setSendRepeatsToGui( false ); } @Override - public final void tick() + public void tick() { super.tick(); terminal.update(); } @Override - public final boolean keyPressed( int key, int scancode, int modifiers ) + public boolean keyPressed( int key, int scancode, int modifiers ) { // Forward the tab key to the terminal, rather than moving between controls. if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal ) @@ -96,7 +96,7 @@ public final boolean keyPressed( int key, int scancode, int modifiers ) @Override - public final void render( @Nonnull MatrixStack stack, int mouseX, int mouseY, float partialTicks ) + public void render( @Nonnull MatrixStack stack, int mouseX, int mouseY, float partialTicks ) { renderBackground( stack ); super.render( stack, mouseX, mouseY, partialTicks ); @@ -114,7 +114,7 @@ public boolean mouseClicked( double x, double y, int button ) } @Override - public final boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY ) + public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY ) { return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY )) || super.mouseDragged( x, y, button, deltaX, deltaY ); diff --git a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java deleted file mode 100644 index 7a63d6557..000000000 --- a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.client.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.IVertexBuilder; -import dan200.computercraft.client.FrameInfo; -import dan200.computercraft.core.terminal.Terminal; -import dan200.computercraft.core.terminal.TextBuffer; -import dan200.computercraft.shared.util.Colour; -import dan200.computercraft.shared.util.Palette; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.RenderState; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.client.renderer.vertex.VertexFormat; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.vector.Matrix4f; -import net.minecraft.util.math.vector.TransformationMatrix; -import org.lwjgl.opengl.GL11; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class FixedWidthFontRenderer -{ - private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix(); - - private static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" ); - - public static final int FONT_HEIGHT = 9; - public static final int FONT_WIDTH = 6; - public static final float WIDTH = 256.0f; - - public static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH; - public static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH; - - public static final RenderType TYPE = Type.MAIN; - - private FixedWidthFontRenderer() - { - } - - public static float toGreyscale( double[] rgb ) - { - return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3); - } - - public static int getColour( char c, Colour def ) - { - return 15 - Terminal.getColour( c, def ); - } - - private static void drawChar( Matrix4f transform, IVertexBuilder buffer, float x, float y, int index, float r, float g, float b ) - { - // Short circuit to avoid the common case - the texture should be blank here after all. - if( index == '\0' || index == ' ' ) return; - - int column = index % 16; - int row = index / 16; - - int xStart = 1 + column * (FONT_WIDTH + 2); - int yStart = 1 + row * (FONT_HEIGHT + 2); - - buffer.vertex( transform, x, y, 0f ).color( r, g, b, 1.0f ).uv( xStart / WIDTH, yStart / WIDTH ).endVertex(); - buffer.vertex( transform, x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).uv( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex(); - buffer.vertex( transform, x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).uv( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex(); - buffer.vertex( transform, x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).uv( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex(); - buffer.vertex( transform, x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).uv( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex(); - buffer.vertex( transform, x + FONT_WIDTH, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).uv( (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex(); - } - - private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, float r, float g, float b ) - { - buffer.vertex( transform, x, y, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_START, BACKGROUND_START ).endVertex(); - buffer.vertex( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_START, BACKGROUND_END ).endVertex(); - buffer.vertex( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_END, BACKGROUND_START ).endVertex(); - buffer.vertex( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_END, BACKGROUND_START ).endVertex(); - buffer.vertex( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_START, BACKGROUND_END ).endVertex(); - buffer.vertex( transform, x + width, y + height, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_END, BACKGROUND_END ).endVertex(); - } - - private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex ) - { - double[] colour = palette.getColour( getColour( colourIndex, Colour.BLACK ) ); - float r, g, b; - if( greyscale ) - { - r = g = b = toGreyscale( colour ); - } - else - { - r = (float) colour[0]; - g = (float) colour[1]; - b = (float) colour[2]; - } - - drawQuad( transform, buffer, x, y, width, height, r, g, b ); - } - - private static void drawBackground( - @Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y, - @Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale, - float leftMarginSize, float rightMarginSize, float height - ) - { - if( leftMarginSize > 0 ) - { - drawQuad( transform, renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) ); - } - - if( rightMarginSize > 0 ) - { - drawQuad( transform, renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) ); - } - - // Batch together runs of identical background cells. - int blockStart = 0; - char blockColour = '\0'; - for( int i = 0; i < backgroundColour.length(); i++ ) - { - char colourIndex = backgroundColour.charAt( i ); - if( colourIndex == blockColour ) continue; - - if( blockColour != '\0' ) - { - drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour ); - } - - blockColour = colourIndex; - blockStart = i; - } - - if( blockColour != '\0' ) - { - drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour ); - } - } - - public static void drawString( - @Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y, - @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour, - @Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize - ) - { - if( backgroundColour != null ) - { - drawBackground( transform, renderer, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT ); - } - - for( int i = 0; i < text.length(); i++ ) - { - double[] colour = palette.getColour( getColour( textColour.charAt( i ), Colour.BLACK ) ); - float r, g, b; - if( greyscale ) - { - r = g = b = toGreyscale( colour ); - } - else - { - r = (float) colour[0]; - g = (float) colour[1]; - b = (float) colour[2]; - } - - // Draw char - int index = text.charAt( i ); - if( index > 255 ) index = '?'; - drawChar( transform, renderer, x + i * FONT_WIDTH, y, index, r, g, b ); - } - - } - - public static void drawString( - float x, float y, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour, - @Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize - ) - { - bindFont(); - - IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().renderBuffers().bufferSource(); - drawString( IDENTITY, ((IRenderTypeBuffer) renderer).getBuffer( TYPE ), x, y, text, textColour, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize ); - renderer.endBatch(); - } - - public static void drawTerminalWithoutCursor( - @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, - @Nonnull Terminal terminal, boolean greyscale, - float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize - ) - { - Palette palette = terminal.getPalette(); - int height = terminal.getHeight(); - - // Top and bottom margins - drawBackground( - transform, buffer, x, y - topMarginSize, - terminal.getBackgroundColourLine( 0 ), palette, greyscale, - leftMarginSize, rightMarginSize, topMarginSize - ); - - drawBackground( - transform, buffer, x, y + height * FONT_HEIGHT, - terminal.getBackgroundColourLine( height - 1 ), palette, greyscale, - leftMarginSize, rightMarginSize, bottomMarginSize - ); - - // The main text - for( int i = 0; i < height; i++ ) - { - drawString( - transform, buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i, - terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ), - palette, greyscale, leftMarginSize, rightMarginSize - ); - } - } - - public static void drawCursor( - @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, - @Nonnull Terminal terminal, boolean greyscale - ) - { - Palette palette = terminal.getPalette(); - int width = terminal.getWidth(); - int height = terminal.getHeight(); - - int cursorX = terminal.getCursorX(); - int cursorY = terminal.getCursorY(); - if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height && FrameInfo.getGlobalCursorBlink() ) - { - double[] colour = palette.getColour( 15 - terminal.getTextColour() ); - float r, g, b; - if( greyscale ) - { - r = g = b = toGreyscale( colour ); - } - else - { - r = (float) colour[0]; - g = (float) colour[1]; - b = (float) colour[2]; - } - - drawChar( transform, buffer, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', r, g, b ); - } - } - - public static void drawTerminal( - @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, - @Nonnull Terminal terminal, boolean greyscale, - float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize - ) - { - drawTerminalWithoutCursor( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize ); - drawCursor( transform, buffer, x, y, terminal, greyscale ); - } - - public static void drawTerminal( - @Nonnull Matrix4f transform, float x, float y, @Nonnull Terminal terminal, boolean greyscale, - float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize - ) - { - bindFont(); - - IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().renderBuffers().bufferSource(); - IVertexBuilder buffer = renderer.getBuffer( TYPE ); - drawTerminal( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize ); - renderer.endBatch( TYPE ); - } - - public static void drawTerminal( - float x, float y, @Nonnull Terminal terminal, boolean greyscale, - float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize - ) - { - drawTerminal( IDENTITY, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize ); - } - - public static void drawEmptyTerminal( @Nonnull Matrix4f transform, @Nonnull IRenderTypeBuffer renderer, float x, float y, float width, float height ) - { - Colour colour = Colour.BLACK; - drawQuad( transform, renderer.getBuffer( TYPE ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() ); - } - - public static void drawEmptyTerminal( @Nonnull Matrix4f transform, float x, float y, float width, float height ) - { - bindFont(); - - IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().renderBuffers().bufferSource(); - drawEmptyTerminal( transform, renderer, x, y, width, height ); - renderer.endBatch(); - } - - public static void drawEmptyTerminal( float x, float y, float width, float height ) - { - drawEmptyTerminal( IDENTITY, x, y, width, height ); - } - - public static void drawBlocker( @Nonnull Matrix4f transform, @Nonnull IRenderTypeBuffer renderer, float x, float y, float width, float height ) - { - Colour colour = Colour.BLACK; - drawQuad( transform, renderer.getBuffer( Type.BLOCKER ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() ); - } - - private static void bindFont() - { - Minecraft.getInstance().getTextureManager().bind( FONT ); - RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP ); - } - - private static final class Type extends RenderState - { - private static final int GL_MODE = GL11.GL_TRIANGLES; - - private static final VertexFormat FORMAT = DefaultVertexFormats.POSITION_COLOR_TEX; - - static final RenderType MAIN = RenderType.create( - "terminal_font", FORMAT, GL_MODE, 1024, - false, false, // useDelegate, needsSorting - RenderType.State.builder() - .setTextureState( new RenderState.TextureState( FONT, false, false ) ) // blur, minimap - .setAlphaState( DEFAULT_ALPHA ) - .setLightmapState( NO_LIGHTMAP ) - .setWriteMaskState( COLOR_WRITE ) - .createCompositeState( false ) - ); - - static final RenderType BLOCKER = RenderType.create( - "terminal_blocker", FORMAT, GL_MODE, 256, - false, false, // useDelegate, needsSorting - RenderType.State.builder() - .setTextureState( new RenderState.TextureState( FONT, false, false ) ) // blur, minimap - .setAlphaState( DEFAULT_ALPHA ) - .setWriteMaskState( DEPTH_WRITE ) - .setLightmapState( NO_LIGHTMAP ) - .createCompositeState( false ) - ); - - private Type( String name, Runnable setup, Runnable destroy ) - { - super( name, setup, destroy ); - } - } -} diff --git a/src/main/java/dan200/computercraft/client/gui/GuiComputer.java b/src/main/java/dan200/computercraft/client/gui/GuiComputer.java index 5b0b2fe06..bf2e39de7 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiComputer.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiComputer.java @@ -6,7 +6,6 @@ package dan200.computercraft.client.gui; import com.mojang.blaze3d.matrix.MatrixStack; -import com.mojang.blaze3d.systems.RenderSystem; import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.widgets.ComputerSidebar; import dan200.computercraft.client.gui.widgets.WidgetTerminal; @@ -19,6 +18,7 @@ import javax.annotation.Nonnull; import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER; +import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP; public final class GuiComputer extends ComputerScreenBase { @@ -73,9 +73,10 @@ protected WidgetTerminal createTerminal() public void renderBg( @Nonnull MatrixStack stack, float partialTicks, int mouseX, int mouseY ) { // Draw a border around the terminal - RenderSystem.color4f( 1, 1, 1, 1 ); - minecraft.getTextureManager().bind( ComputerBorderRenderer.getTexture( family ) ); - ComputerBorderRenderer.render( terminal.x, terminal.y, getBlitOffset(), terminal.getWidth(), terminal.getHeight() ); + ComputerBorderRenderer.render( + stack.last().pose(), ComputerBorderRenderer.getTexture( family ), terminal.x, terminal.y, getBlitOffset(), + FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight() + ); ComputerSidebar.renderBackground( stack, leftPos, topPos + sidebarYOffset ); } } diff --git a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java index 81893c682..eb7ab0bc7 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java @@ -10,9 +10,9 @@ import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.shared.common.ContainerHeldItem; import dan200.computercraft.shared.media.items.ItemPrintout; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.inventory.ContainerScreen; import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.util.math.vector.Matrix4f; import net.minecraft.util.text.ITextComponent; @@ -21,6 +21,7 @@ import javax.annotation.Nonnull; import static dan200.computercraft.client.render.PrintoutRenderer.*; +import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP; public class GuiPrintout extends ContainerScreen { @@ -97,10 +98,10 @@ protected void renderBg( @Nonnull MatrixStack transform, float partialTicks, int RenderSystem.color4f( 1.0f, 1.0f, 1.0f, 1.0f ); RenderSystem.enableDepthTest(); - IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().renderBuffers().bufferSource(); + IRenderTypeBuffer.Impl renderer = IRenderTypeBuffer.immediate( Tessellator.getInstance().getBuilder() ); Matrix4f matrix = transform.last().pose(); - drawBorder( matrix, renderer, leftPos, topPos, getBlitOffset(), page, pages, book ); - drawText( matrix, renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * page, text, colours ); + drawBorder( matrix, renderer, leftPos, topPos, getBlitOffset(), page, pages, book, FULL_BRIGHT_LIGHTMAP ); + drawText( matrix, renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours ); renderer.endBatch(); } diff --git a/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java b/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java index dc0f6744d..056af5604 100644 --- a/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java +++ b/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java @@ -6,22 +6,28 @@ package dan200.computercraft.client.gui.widgets; import com.mojang.blaze3d.matrix.MatrixStack; -import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.IVertexBuilder; +import dan200.computercraft.client.render.RenderTypes; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.shared.computer.core.ClientComputer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.widget.Widget; +import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.util.SharedConstants; import net.minecraft.util.math.vector.Matrix4f; import net.minecraft.util.text.StringTextComponent; import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL11; import javax.annotation.Nonnull; import java.util.BitSet; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH; import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH; public class WidgetTerminal extends Widget { @@ -314,14 +320,23 @@ public void render( @Nonnull MatrixStack transform, int mouseX, int mouseY, floa if( !visible ) return; Matrix4f matrix = transform.last().pose(); Terminal terminal = computer.getTerminal(); + + Minecraft.getInstance().getTextureManager().bind( FixedWidthFontRenderer.FONT ); + RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP ); + + IRenderTypeBuffer.Impl renderer = IRenderTypeBuffer.immediate( Tessellator.getInstance().getBuilder() ); + IVertexBuilder buffer = renderer.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ); + if( terminal != null ) { - FixedWidthFontRenderer.drawTerminal( matrix, innerX, innerY, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN ); + FixedWidthFontRenderer.drawTerminal( matrix, buffer, innerX, innerY, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN ); } else { - FixedWidthFontRenderer.drawEmptyTerminal( matrix, x, y, width, height ); + FixedWidthFontRenderer.drawEmptyTerminal( matrix, buffer, x, y, width, height ); } + + renderer.endBatch(); } public static int getWidth( int termWidth ) diff --git a/src/main/java/dan200/computercraft/client/render/ComputerBorderRenderer.java b/src/main/java/dan200/computercraft/client/render/ComputerBorderRenderer.java index 9d410b57c..ebd8a96c7 100644 --- a/src/main/java/dan200/computercraft/client/render/ComputerBorderRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/ComputerBorderRenderer.java @@ -5,16 +5,14 @@ */ package dan200.computercraft.client.render; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.IVertexBuilder; import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.computer.core.ComputerFamily; -import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.vector.Matrix4f; -import org.lwjgl.opengl.GL11; import javax.annotation.Nonnull; @@ -25,13 +23,6 @@ public class ComputerBorderRenderer public static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_command.png" ); public static final ResourceLocation BACKGROUND_COLOUR = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/corners_colour.png" ); - private static final Matrix4f IDENTITY = new Matrix4f(); - - static - { - IDENTITY.setIdentity(); - } - /** * The margin between the terminal and its border. */ @@ -59,18 +50,19 @@ public class ComputerBorderRenderer private final IVertexBuilder builder; private final int z; private final float r, g, b; + private final int light; - public ComputerBorderRenderer( Matrix4f transform, IVertexBuilder builder, int z, float r, float g, float b ) + public ComputerBorderRenderer( Matrix4f transform, IVertexBuilder builder, int z, int light, float r, float g, float b ) { this.transform = transform; this.builder = builder; this.z = z; + this.light = light; this.r = r; this.g = g; this.b = b; } - @Nonnull public static ResourceLocation getTexture( @Nonnull ComputerFamily family ) { @@ -86,31 +78,22 @@ public static ResourceLocation getTexture( @Nonnull ComputerFamily family ) } } - public static void render( int x, int y, int z, int width, int height ) + public static RenderType getRenderType( ResourceLocation location ) { - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuilder(); - buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX ); - - render( IDENTITY, buffer, x, y, z, width, height ); - - RenderSystem.enableAlphaTest(); - tessellator.end(); + // See note in RenderTypes about why we use text rather than anything intuitive. + return RenderType.text( location ); } - public static void render( Matrix4f transform, IVertexBuilder buffer, int x, int y, int z, int width, int height ) + public static void render( Matrix4f transform, ResourceLocation location, int x, int y, int z, int light, int width, int height ) { - render( transform, buffer, x, y, z, width, height, 1, 1, 1 ); + IRenderTypeBuffer.Impl source = IRenderTypeBuffer.immediate( Tessellator.getInstance().getBuilder() ); + render( transform, source.getBuffer( getRenderType( location ) ), x, y, z, light, width, height, false, 1, 1, 1 ); + source.endBatch(); } - public static void render( Matrix4f transform, IVertexBuilder buffer, int x, int y, int z, int width, int height, float r, float g, float b ) + public static void render( Matrix4f transform, IVertexBuilder buffer, int x, int y, int z, int light, int width, int height, boolean withLight, float r, float g, float b ) { - render( transform, buffer, x, y, z, width, height, false, r, g, b ); - } - - public static void render( Matrix4f transform, IVertexBuilder buffer, int x, int y, int z, int width, int height, boolean withLight, float r, float g, float b ) - { - new ComputerBorderRenderer( transform, buffer, z, r, g, b ).doRender( x, y, width, height, withLight ); + new ComputerBorderRenderer( transform, buffer, z, light, r, g, b ).doRender( x, y, width, height, withLight ); } public void doRender( int x, int y, int width, int height, boolean withLight ) @@ -160,9 +143,9 @@ private void renderTexture( int x, int y, int u, int v, int width, int height ) private void renderTexture( int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight ) { - builder.vertex( transform, x, y + height, z ).color( r, g, b, 1.0f ).uv( u * TEX_SCALE, (v + textureHeight) * TEX_SCALE ).endVertex(); - builder.vertex( transform, x + width, y + height, z ).color( r, g, b, 1.0f ).uv( (u + textureWidth) * TEX_SCALE, (v + textureHeight) * TEX_SCALE ).endVertex(); - builder.vertex( transform, x + width, y, z ).color( r, g, b, 1.0f ).uv( (u + textureWidth) * TEX_SCALE, v * TEX_SCALE ).endVertex(); - builder.vertex( transform, x, y, z ).color( r, g, b, 1.0f ).uv( u * TEX_SCALE, v * TEX_SCALE ).endVertex(); + builder.vertex( transform, x, y + height, z ).color( r, g, b, 1.0f ).uv( u * TEX_SCALE, (v + textureHeight) * TEX_SCALE ).uv2( light ).endVertex(); + builder.vertex( transform, x + width, y + height, z ).color( r, g, b, 1.0f ).uv( (u + textureWidth) * TEX_SCALE, (v + textureHeight) * TEX_SCALE ).uv2( light ).endVertex(); + builder.vertex( transform, x + width, y, z ).color( r, g, b, 1.0f ).uv( (u + textureWidth) * TEX_SCALE, v * TEX_SCALE ).uv2( light ).endVertex(); + builder.vertex( transform, x, y, z ).color( r, g, b, 1.0f ).uv( u * TEX_SCALE, v * TEX_SCALE ).uv2( light ).endVertex(); } } diff --git a/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java b/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java index ac146c895..cd7a73f55 100644 --- a/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java @@ -25,9 +25,10 @@ public abstract class ItemMapLikeRenderer * @param transform The matrix transformation stack * @param render The buffer to render to * @param stack The stack to render + * @param light The packed lightmap coordinates. * @see FirstPersonRenderer#renderItemInFirstPerson(AbstractClientPlayerEntity, float, float, Hand, float, ItemStack, float, MatrixStack, IRenderTypeBuffer, int) */ - protected abstract void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack ); + protected abstract void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack, int light ); protected void renderItemFirstPerson( MatrixStack transform, IRenderTypeBuffer render, int lightTexture, Hand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack ) { @@ -89,7 +90,7 @@ private void renderItemFirstPersonSide( MatrixStack transform, IRenderTypeBuffer transform.mulPose( Vector3f.XP.rotationDegrees( f2 * -45f ) ); transform.mulPose( Vector3f.YP.rotationDegrees( offset * f2 * -30f ) ); - renderItem( transform, render, stack ); + renderItem( transform, render, stack, combinedLight ); transform.popPose(); } @@ -134,6 +135,6 @@ private void renderItemFirstPersonCenter( MatrixStack transform, IRenderTypeBuff transform.mulPose( Vector3f.XP.rotationDegrees( rX * 20.0F ) ); transform.scale( 2.0F, 2.0F, 2.0F ); - renderItem( transform, render, stack ); + renderItem( transform, render, stack, combinedLight ); } } diff --git a/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java b/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java index a25a48b49..c5c8b2efa 100644 --- a/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java @@ -6,31 +6,27 @@ package dan200.computercraft.client.render; import com.mojang.blaze3d.matrix.MatrixStack; -import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.IVertexBuilder; import dan200.computercraft.ComputerCraft; -import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.pocket.items.ItemPocketComputer; import dan200.computercraft.shared.util.Colour; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.Tessellator; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.vector.Matrix4f; import net.minecraft.util.math.vector.Vector3f; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderHandEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import org.lwjgl.opengl.GL11; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH; import static dan200.computercraft.client.render.ComputerBorderRenderer.*; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH; /** * Emulates map rendering for pocket computers. @@ -58,7 +54,7 @@ public static void onRenderInHand( RenderHandEvent event ) } @Override - protected void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack ) + protected void renderItem( MatrixStack transform, IRenderTypeBuffer bufferSource, ItemStack stack, int light ) { ClientComputer computer = ItemPocketComputer.createClientComputer( stack ); Terminal terminal = computer == null ? null : computer.getTerminal(); @@ -95,61 +91,58 @@ protected void renderItem( MatrixStack transform, IRenderTypeBuffer render, Item int frameColour = item.getColour( stack ); Matrix4f matrix = transform.last().pose(); - renderFrame( matrix, family, frameColour, width, height ); + renderFrame( matrix, bufferSource, family, frameColour, light, width, height ); // Render the light int lightColour = ItemPocketComputer.getLightState( stack ); if( lightColour == -1 ) lightColour = Colour.BLACK.getHex(); - renderLight( matrix, lightColour, width, height ); + renderLight( matrix, bufferSource, lightColour, width, height ); if( computer != null && terminal != null ) { - FixedWidthFontRenderer.drawTerminal( matrix, MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN ); + FixedWidthFontRenderer.drawTerminal( + matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ), + MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN + ); + FixedWidthFontRenderer.drawBlocker( + matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_BLOCKER ), + 0, 0, width, height + ); } else { - FixedWidthFontRenderer.drawEmptyTerminal( matrix, 0, 0, width, height ); + FixedWidthFontRenderer.drawEmptyTerminal( + matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ), + 0, 0, width, height + ); } transform.popPose(); } - private static void renderFrame( Matrix4f transform, ComputerFamily family, int colour, int width, int height ) + private static void renderFrame( Matrix4f transform, IRenderTypeBuffer bufferSource, ComputerFamily family, int colour, int light, int width, int height ) { - RenderSystem.enableBlend(); - Minecraft.getInstance().getTextureManager() - .bind( colour != -1 ? ComputerBorderRenderer.BACKGROUND_COLOUR : ComputerBorderRenderer.getTexture( family ) ); + ResourceLocation texture = colour != -1 ? ComputerBorderRenderer.BACKGROUND_COLOUR : ComputerBorderRenderer.getTexture( family ); float r = ((colour >>> 16) & 0xFF) / 255.0f; float g = ((colour >>> 8) & 0xFF) / 255.0f; float b = (colour & 0xFF) / 255.0f; - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuilder(); - buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX ); - - ComputerBorderRenderer.render( transform, buffer, 0, 0, 0, width, height, true, r, g, b ); - - tessellator.end(); + ComputerBorderRenderer.render( transform, bufferSource.getBuffer( ComputerBorderRenderer.getRenderType( texture ) ), 0, 0, 0, light, width, height, true, r, g, b ); } - private static void renderLight( Matrix4f transform, int colour, int width, int height ) + private static void renderLight( Matrix4f transform, IRenderTypeBuffer bufferSource, int colour, int width, int height ) { - RenderSystem.disableTexture(); + byte r = (byte) ((colour >>> 16) & 0xFF); + byte g = (byte) ((colour >>> 8) & 0xFF); + byte b = (byte) (colour & 0xFF); + byte[] c = new byte[] { r, g, b, (byte) 255 }; - float r = ((colour >>> 16) & 0xFF) / 255.0f; - float g = ((colour >>> 8) & 0xFF) / 255.0f; - float b = (colour & 0xFF) / 255.0f; - - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuilder(); - buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR ); - buffer.vertex( transform, width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + BORDER / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex(); - buffer.vertex( transform, width, height + LIGHT_HEIGHT + BORDER / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex(); - buffer.vertex( transform, width, height + BORDER / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex(); - buffer.vertex( transform, width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex(); - - tessellator.end(); - RenderSystem.enableTexture(); + IVertexBuilder buffer = bufferSource.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ); + FixedWidthFontRenderer.drawQuad( + transform, buffer, + width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT, + c, RenderTypes.FULL_BRIGHT_LIGHTMAP + ); } } diff --git a/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java b/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java index c6b5bb261..77d44df09 100644 --- a/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/ItemPrintoutRenderer.java @@ -18,9 +18,9 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH; import static dan200.computercraft.client.render.PrintoutRenderer.*; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH; import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE; import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENGTH; @@ -50,13 +50,13 @@ public static void onRenderInHand( RenderHandEvent event ) } @Override - protected void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack ) + protected void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack, int light ) { transform.mulPose( Vector3f.XP.rotationDegrees( 180f ) ); transform.scale( 0.42f, 0.42f, -0.42f ); transform.translate( -0.5f, -0.48f, 0.0f ); - drawPrintout( transform, render, stack ); + drawPrintout( transform, render, stack, light ); } @SubscribeEvent @@ -74,10 +74,10 @@ public static void onRenderInFrame( RenderItemInFrameEvent event ) transform.scale( 0.95f, 0.95f, -0.95f ); transform.translate( -0.5f, -0.5f, 0.0f ); - drawPrintout( transform, event.getBuffers(), stack ); + drawPrintout( transform, event.getBuffers(), stack, event.getLight() ); } - private static void drawPrintout( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack ) + private static void drawPrintout( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack, int light ) { int pages = ItemPrintout.getPageCount( stack ); boolean book = ((ItemPrintout) stack.getItem()).getType() == ItemPrintout.Type.BOOK; @@ -86,7 +86,7 @@ private static void drawPrintout( MatrixStack transform, IRenderTypeBuffer rende double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2; // Non-books will be left aligned - if( !book ) width += offsetAt( pages ); + if( !book ) width += offsetAt( pages - 1 ); double visualWidth = width, visualHeight = height; @@ -105,9 +105,10 @@ private static void drawPrintout( MatrixStack transform, IRenderTypeBuffer rende transform.translate( (max - width) / 2.0, (max - height) / 2.0, 0.0 ); Matrix4f matrix = transform.last().pose(); - drawBorder( matrix, render, 0, 0, -0.01f, 0, pages, book ); - drawText( matrix, render, - X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, ItemPrintout.getText( stack ), ItemPrintout.getColours( stack ) + drawBorder( matrix, render, 0, 0, -0.01f, 0, pages, book, light ); + drawText( + matrix, render, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light, + ItemPrintout.getText( stack ), ItemPrintout.getColours( stack ) ); } } diff --git a/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java b/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java index 11e8048e7..4beb79672 100644 --- a/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java +++ b/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java @@ -9,62 +9,55 @@ import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import dan200.computercraft.ComputerCraft; -import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.client.FrameInfo; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.terminal.TextBuffer; +import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Palette; import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.util.math.vector.Matrix4f; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL20; +import org.lwjgl.opengl.GL31; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.FloatBuffer; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.getColour; + class MonitorTextureBufferShader { + public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4; + static final int TEXTURE_INDEX = GL13.GL_TEXTURE3; private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 ); - private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 ); private static int uniformMv; private static int uniformFont; - private static int uniformWidth; - private static int uniformHeight; private static int uniformTbo; - private static int uniformPalette; + private static int uniformMonitor; + private static int uniformCursorBlink; private static boolean initialised; private static boolean ok; private static int program; - static void setupUniform( Matrix4f transform, int width, int height, Palette palette, boolean greyscale ) + static void setupUniform( Matrix4f transform, int tboUniform ) { MATRIX_BUFFER.rewind(); transform.store( MATRIX_BUFFER ); MATRIX_BUFFER.rewind(); RenderSystem.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER ); - RenderSystem.glUniform1i( uniformWidth, width ); - RenderSystem.glUniform1i( uniformHeight, height ); + GL31.glBindBufferBase( GL31.GL_UNIFORM_BUFFER, uniformMonitor, tboUniform ); - PALETTE_BUFFER.rewind(); - for( int i = 0; i < 16; i++ ) - { - double[] colour = palette.getColour( i ); - if( greyscale ) - { - float f = FixedWidthFontRenderer.toGreyscale( colour ); - PALETTE_BUFFER.put( f ).put( f ).put( f ); - } - else - { - PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] ); - } - } - PALETTE_BUFFER.flip(); - RenderSystem.glUniform3( uniformPalette, PALETTE_BUFFER ); + int cursorAlpha = FrameInfo.getGlobalCursorBlink() ? 1 : 0; + RenderSystem.glUniform1i( uniformCursorBlink, cursorAlpha ); } static boolean use() @@ -116,10 +109,10 @@ private static boolean load() uniformMv = getUniformLocation( program, "u_mv" ); uniformFont = getUniformLocation( program, "u_font" ); - uniformWidth = getUniformLocation( program, "u_width" ); - uniformHeight = getUniformLocation( program, "u_height" ); uniformTbo = getUniformLocation( program, "u_tbo" ); - uniformPalette = getUniformLocation( program, "u_palette" ); + uniformMonitor = GL31.glGetUniformBlockIndex( program, "u_monitor" ); + if( uniformMonitor == -1 ) throw new IllegalStateException( "Could not find uniformMonitor uniform." ); + uniformCursorBlink = getUniformLocation( program, "u_cursorBlink" ); ComputerCraft.log.info( "Loaded monitor shader." ); return true; @@ -159,4 +152,54 @@ private static int getUniformLocation( int program, String name ) if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name ); return uniform; } + + public static void setTerminalData( ByteBuffer buffer, Terminal terminal ) + { + int width = terminal.getWidth(), height = terminal.getHeight(); + + int pos = 0; + for( int y = 0; y < height; y++ ) + { + TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y ); + for( int x = 0; x < width; x++ ) + { + buffer.put( pos, (byte) (text.charAt( x ) & 0xFF) ); + buffer.put( pos + 1, (byte) getColour( textColour.charAt( x ), Colour.WHITE ) ); + buffer.put( pos + 2, (byte) getColour( background.charAt( x ), Colour.BLACK ) ); + pos += 3; + } + } + + buffer.limit( pos ); + } + + public static void setUniformData( ByteBuffer buffer, Terminal terminal, boolean greyscale ) + { + int pos = 0; + Palette palette = terminal.getPalette(); + for( int i = 0; i < 16; i++ ) + { + double[] colour = palette.getColour( i ); + if( greyscale ) + { + float f = FixedWidthFontRenderer.toGreyscale( colour ); + buffer.putFloat( pos, f ).putFloat( pos + 4, f ).putFloat( pos + 8, f ); + } + else + { + buffer.putFloat( pos, (float) colour[0] ).putFloat( pos + 4, (float) colour[1] ).putFloat( pos + 8, (float) colour[2] ); + } + + pos += 4 * 4; // std140 requires these are 4-wide + } + + boolean showCursor = FixedWidthFontRenderer.isCursorVisible( terminal ); + buffer + .putInt( pos, terminal.getWidth() ).putInt( pos + 4, terminal.getHeight() ) + .putInt( pos + 8, showCursor ? terminal.getCursorX() : -2 ) + .putInt( pos + 12, showCursor ? terminal.getCursorY() : -2 ) + .putInt( pos + 16, 15 - terminal.getTextColour() ); + + buffer.limit( UNIFORM_SIZE ); + } } diff --git a/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java b/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java index 4538edd72..558dc9af6 100644 --- a/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java @@ -6,18 +6,14 @@ package dan200.computercraft.client.render; import com.mojang.blaze3d.vertex.IVertexBuilder; -import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.shared.util.Palette; import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.RenderState; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.vector.Matrix4f; -import org.lwjgl.opengl.GL11; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE; public final class PrintoutRenderer @@ -60,37 +56,37 @@ public final class PrintoutRenderer private PrintoutRenderer() {} - public static void drawText( Matrix4f transform, IRenderTypeBuffer renderer, int x, int y, int start, TextBuffer[] text, TextBuffer[] colours ) + public static void drawText( Matrix4f transform, IRenderTypeBuffer renderer, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours ) { - IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE ); + IVertexBuilder buffer = renderer.getBuffer( RenderTypes.PRINTOUT_TEXT ); for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ ) { FixedWidthFontRenderer.drawString( transform, buffer, - x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT, - false, 0, 0 + x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], + Palette.DEFAULT, false, light ); } } - public static void drawText( Matrix4f transform, IRenderTypeBuffer renderer, int x, int y, int start, String[] text, String[] colours ) + public static void drawText( Matrix4f transform, IRenderTypeBuffer renderer, int x, int y, int start, int light, String[] text, String[] colours ) { - IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE ); + IVertexBuilder buffer = renderer.getBuffer( RenderTypes.PRINTOUT_TEXT ); for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ ) { FixedWidthFontRenderer.drawString( transform, buffer, x, y + line * FONT_HEIGHT, new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ), - null, Palette.DEFAULT, false, 0, 0 + Palette.DEFAULT, false, light ); } } - public static void drawBorder( Matrix4f transform, IRenderTypeBuffer renderer, float x, float y, float z, int page, int pages, boolean isBook ) + public static void drawBorder( Matrix4f transform, IRenderTypeBuffer renderer, float x, float y, float z, int page, int pages, boolean isBook, int light ) { int leftPages = page; int rightPages = pages - page - 1; - IVertexBuilder buffer = renderer.getBuffer( Type.TYPE ); + IVertexBuilder buffer = renderer.getBuffer( RenderTypes.PRINTOUT_BACKGROUND ); if( isBook ) { @@ -100,86 +96,73 @@ public static void drawBorder( Matrix4f transform, IRenderTypeBuffer renderer, f float right = x + X_SIZE + offset - 4; // Left and right border - drawTexture( transform, buffer, left - 4, y - 8, z - 0.02f, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 ); - drawTexture( transform, buffer, right, y - 8, z - 0.02f, COVER_X + COVER_SIZE, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 ); + drawTexture( transform, buffer, left - 4, y - 8, z - 0.02f, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2, light ); + drawTexture( transform, buffer, right, y - 8, z - 0.02f, COVER_X + COVER_SIZE, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2, light ); // Draw centre panel (just stretched texture, sorry). drawTexture( transform, buffer, x - offset, y, z - 0.02f, X_SIZE + offset * 2, Y_SIZE, - COVER_X + COVER_SIZE / 2.0f, COVER_SIZE, COVER_SIZE, Y_SIZE + COVER_X + COVER_SIZE / 2.0f, COVER_SIZE, COVER_SIZE, Y_SIZE, light ); float borderX = left; while( borderX < right ) { double thisWidth = Math.min( right - borderX, X_SIZE ); - drawTexture( transform, buffer, borderX, y - 8, z - 0.02f, 0, COVER_Y, (float) thisWidth, COVER_SIZE ); - drawTexture( transform, buffer, borderX, y + Y_SIZE - 4, z - 0.02f, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE ); + drawTexture( transform, buffer, borderX, y - 8, z - 0.02f, 0, COVER_Y, (float) thisWidth, COVER_SIZE, light ); + drawTexture( transform, buffer, borderX, y + Y_SIZE - 4, z - 0.02f, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE, light ); borderX += thisWidth; } } // Left half - drawTexture( transform, buffer, x, y, z, X_FOLD_SIZE * 2, 0, X_SIZE / 2.0f, Y_SIZE ); + drawTexture( transform, buffer, x, y, z, X_FOLD_SIZE * 2, 0, X_SIZE / 2.0f, Y_SIZE, light ); for( int n = 0; n <= leftPages; n++ ) { drawTexture( transform, buffer, x - offsetAt( n ), y, z - 1e-3f * n, // Use the left "bold" fold for the outermost page n == leftPages ? 0 : X_FOLD_SIZE, 0, - X_FOLD_SIZE, Y_SIZE + X_FOLD_SIZE, Y_SIZE, light ); } // Right half - drawTexture( transform, buffer, x + X_SIZE / 2.0f, y, z, X_FOLD_SIZE * 2 + X_SIZE / 2.0f, 0, X_SIZE / 2.0f, Y_SIZE ); + drawTexture( transform, buffer, x + X_SIZE / 2.0f, y, z, X_FOLD_SIZE * 2 + X_SIZE / 2.0f, 0, X_SIZE / 2.0f, Y_SIZE, light ); for( int n = 0; n <= rightPages; n++ ) { drawTexture( transform, buffer, x + (X_SIZE - X_FOLD_SIZE) + offsetAt( n ), y, z - 1e-3f * n, // Two folds, then the main page. Use the right "bold" fold for the outermost page. X_FOLD_SIZE * 2 + X_SIZE + (n == rightPages ? X_FOLD_SIZE : 0), 0, - X_FOLD_SIZE, Y_SIZE + X_FOLD_SIZE, Y_SIZE, light ); } } - private static void drawTexture( Matrix4f matrix, IVertexBuilder buffer, float x, float y, float z, float u, float v, float width, float height ) + private static void drawTexture( Matrix4f matrix, IVertexBuilder buffer, float x, float y, float z, float u, float v, float width, float height, int light ) { - buffer.vertex( matrix, x, y + height, z ).uv( u / BG_SIZE, (v + height) / BG_SIZE ).endVertex(); - buffer.vertex( matrix, x + width, y + height, z ).uv( (u + width) / BG_SIZE, (v + height) / BG_SIZE ).endVertex(); - buffer.vertex( matrix, x + width, y, z ).uv( (u + width) / BG_SIZE, v / BG_SIZE ).endVertex(); - buffer.vertex( matrix, x, y, z ).uv( u / BG_SIZE, v / BG_SIZE ).endVertex(); + vertex( buffer, matrix, x, y + height, z, u / BG_SIZE, (v + height) / BG_SIZE, light ); + vertex( buffer, matrix, x + width, y + height, z, (u + width) / BG_SIZE, (v + height) / BG_SIZE, light ); + vertex( buffer, matrix, x + width, y, z, (u + width) / BG_SIZE, v / BG_SIZE, light ); + vertex( buffer, matrix, x, y, z, u / BG_SIZE, v / BG_SIZE, light ); } - private static void drawTexture( Matrix4f matrix, IVertexBuilder buffer, float x, float y, float z, float width, float height, float u, float v, float tWidth, float tHeight ) + private static void drawTexture( Matrix4f matrix, IVertexBuilder buffer, float x, float y, float z, float width, float height, float u, float v, float tWidth, float tHeight, int light ) { - buffer.vertex( matrix, x, y + height, z ).uv( u / BG_SIZE, (v + tHeight) / BG_SIZE ).endVertex(); - buffer.vertex( matrix, x + width, y + height, z ).uv( (u + tWidth) / BG_SIZE, (v + tHeight) / BG_SIZE ).endVertex(); - buffer.vertex( matrix, x + width, y, z ).uv( (u + tWidth) / BG_SIZE, v / BG_SIZE ).endVertex(); - buffer.vertex( matrix, x, y, z ).uv( u / BG_SIZE, v / BG_SIZE ).endVertex(); + vertex( buffer, matrix, x, y + height, z, u / BG_SIZE, (v + tHeight) / BG_SIZE, light ); + vertex( buffer, matrix, x + width, y + height, z, (u + tWidth) / BG_SIZE, (v + tHeight) / BG_SIZE, light ); + vertex( buffer, matrix, x + width, y, z, (u + tWidth) / BG_SIZE, v / BG_SIZE, light ); + vertex( buffer, matrix, x, y, z, u / BG_SIZE, v / BG_SIZE, light ); + } + + private static void vertex( IVertexBuilder buffer, Matrix4f matrix, float x, float y, float z, float u, float v, int light ) + { + buffer.vertex( matrix, x, y, z ).color( 255, 255, 255, 255 ).uv( u, v ).uv2( light ).endVertex(); } public static float offsetAt( int page ) { return (float) (32 * (1 - Math.pow( 1.2, -page ))); } - - private static final class Type extends RenderState - { - static final RenderType TYPE = RenderType.create( - "printout_background", DefaultVertexFormats.POSITION_TEX, GL11.GL_QUADS, 1024, - false, false, // useDelegate, needsSorting - RenderType.State.builder() - .setTextureState( new RenderState.TextureState( BG, false, false ) ) // blur, minimap - .setAlphaState( DEFAULT_ALPHA ) - .setLightmapState( NO_LIGHTMAP ) - .createCompositeState( false ) - ); - - private Type( String name, Runnable setup, Runnable destroy ) - { - super( name, setup, destroy ); - } - } } diff --git a/src/main/java/dan200/computercraft/client/render/RenderTypes.java b/src/main/java/dan200/computercraft/client/render/RenderTypes.java new file mode 100644 index 000000000..772f379f0 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/render/RenderTypes.java @@ -0,0 +1,91 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.render; + +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; +import net.minecraft.client.renderer.RenderState; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; + +public class RenderTypes +{ + public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20); + + /** + * Renders a fullbright terminal without writing to the depth layer. This is used in combination with + * {@link #TERMINAL_BLOCKER} to ensure we can render a terminal without z-fighting. + */ + public static final RenderType TERMINAL_WITHOUT_DEPTH = Types.TERMINAL_WITHOUT_DEPTH; + + /** + * A transparent texture which only writes to the depth layer. + */ + public static final RenderType TERMINAL_BLOCKER = Types.TERMINAL_BLOCKER; + + /** + * Renders a fullbright terminal which also writes to the depth layer. This is used when z-fighting isn't an issue - + * for instance rendering an empty terminal or inside a GUI. + * + * This is identical to vanilla's {@link RenderType#text}. Forge overrides one with a definition which sets + * sortOnUpload to true, which is entirely broken! + */ + public static final RenderType TERMINAL_WITH_DEPTH = Types.TERMINAL_WITH_DEPTH; + + /** + * A variant of {@link #TERMINAL_WITH_DEPTH} which uses the lightmap rather than rendering fullbright. + */ + public static final RenderType PRINTOUT_TEXT = RenderType.text( FixedWidthFontRenderer.FONT ); + + /** + * Printout's background texture. {@link RenderType#text(ResourceLocation)} is a little questionable, but + * it is what maps use, so should behave the same as vanilla in both item frames and in-hand. + */ + public static final RenderType PRINTOUT_BACKGROUND = RenderType.text( new ResourceLocation( "computercraft", "textures/gui/printout.png" ) ); + + private static final class Types extends RenderState + { + private static final RenderState.TextureState TERM_FONT_TEXTURE = new RenderState.TextureState( + FixedWidthFontRenderer.FONT, + false, false // blur, minimap + ); + private static final VertexFormat TERM_FORMAT = DefaultVertexFormats.POSITION_COLOR_TEX; + + static final RenderType TERMINAL_WITHOUT_DEPTH = RenderType.create( + "terminal_without_depth", TERM_FORMAT, GL11.GL_QUADS, 1024, + false, false, // useDelegate, needsSorting + RenderType.State.builder() + .setTextureState( TERM_FONT_TEXTURE ) + .setAlphaState( DEFAULT_ALPHA ) + .setWriteMaskState( COLOR_WRITE ) + .createCompositeState( false ) + ); + + static final RenderType TERMINAL_BLOCKER = RenderType.create( + "terminal_blocker", DefaultVertexFormats.POSITION, GL11.GL_QUADS, 256, + false, false, // useDelegate, needsSorting + RenderType.State.builder() + .setWriteMaskState( DEPTH_WRITE ) + .createCompositeState( false ) + ); + + static final RenderType TERMINAL_WITH_DEPTH = RenderType.create( + "terminal_with_depth", TERM_FORMAT, GL11.GL_QUADS, 1024, + false, false, // useDelegate, needsSorting + RenderType.State.builder() + .setTextureState( TERM_FONT_TEXTURE ) + .setAlphaState( DEFAULT_ALPHA ) + .createCompositeState( false ) + ); + + private Types( String name, Runnable setup, Runnable destroy ) + { + super( name, setup, destroy ); + } + } +} diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java index b947749e2..236a60880 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java @@ -7,25 +7,26 @@ import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.platform.GlStateManager; -import com.mojang.blaze3d.vertex.IVertexBuilder; import dan200.computercraft.client.FrameInfo; -import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; +import dan200.computercraft.client.util.DirectBuffers; +import dan200.computercraft.client.util.DirectVertexBuffer; import dan200.computercraft.core.terminal.Terminal; -import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.shared.peripheral.monitor.ClientMonitor; import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer; import dan200.computercraft.shared.peripheral.monitor.TileMonitor; -import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.DirectionUtil; -import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.tileentity.TileEntityRenderer; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.client.renderer.vertex.VertexBuffer; import net.minecraft.util.Direction; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Matrix4f; -import net.minecraft.util.math.vector.TransformationMatrix; import net.minecraft.util.math.vector.Vector3f; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; @@ -35,7 +36,8 @@ import javax.annotation.Nonnull; import java.nio.ByteBuffer; -import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH; public class TileEntityMonitorRenderer extends TileEntityRenderer { @@ -44,9 +46,7 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer * the monitor frame and contents. */ private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1); - private static ByteBuffer tboContents; - - private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix(); + private static ByteBuffer backingBuffer; public TileEntityMonitorRenderer( TileEntityRendererDispatcher rendererDispatcher ) { @@ -114,46 +114,31 @@ public void render( @Nonnull TileMonitor monitor, float partialTicks, @Nonnull M transform.scale( (float) xScale, (float) -yScale, 1.0f ); Matrix4f matrix = transform.last().pose(); + renderTerminal( renderer, matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) ); - // Sneaky hack here: we get a buffer now in order to flush existing ones and set up the appropriate - // render state. I've no clue how well this'll work in future versions of Minecraft, but it does the trick - // for now. - IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE ); - FixedWidthFontRenderer.TYPE.setupRenderState(); - - renderTerminal( matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) ); - - // We don't draw the cursor with the VBO, as it's dynamic and so we'll end up refreshing far more than is - // reasonable. - FixedWidthFontRenderer.drawCursor( matrix, buffer, 0, 0, terminal, !originTerminal.isColour() ); + // Force a flush of the buffer. WorldRenderer.updateCameraAndRender will "finish" all the built-in buffers + // before calling renderer.finish, which means the blocker isn't actually rendered at that point! + renderer.getBuffer( RenderType.solid() ); transform.popPose(); } else { FixedWidthFontRenderer.drawEmptyTerminal( - transform.last().pose(), renderer, + transform.last().pose(), renderer.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ), -MARGIN, MARGIN, (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2) ); } - FixedWidthFontRenderer.drawBlocker( - transform.last().pose(), renderer, - -MARGIN, MARGIN, - (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2) - ); - - // Force a flush of the blocker. WorldRenderer.updateCameraAndRender will "finish" all the built-in - // buffers before calling renderer.finish, which means the blocker isn't actually rendered at that point! - renderer.getBuffer( RenderType.solid() ); - transform.popPose(); } - private static void renderTerminal( Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin ) + private static void renderTerminal( IRenderTypeBuffer bufferSource, Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin ) { Terminal terminal = monitor.getTerminal(); + int width = terminal.getWidth(), height = terminal.getHeight(); + int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT; MonitorRenderer renderType = MonitorRenderer.current(); boolean redraw = monitor.pollTerminalChanged(); @@ -165,42 +150,29 @@ private static void renderTerminal( Matrix4f matrix, ClientMonitor monitor, floa { if( !MonitorTextureBufferShader.use() ) return; - int width = terminal.getWidth(), height = terminal.getHeight(); - int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT; - if( redraw ) { - int size = width * height * 3; - if( tboContents == null || tboContents.capacity() < size ) - { - tboContents = GLAllocation.createByteBuffer( size ); - } + ByteBuffer terminalBuffer = getBuffer( width * height * 3 ); + MonitorTextureBufferShader.setTerminalData( terminalBuffer, terminal ); + DirectBuffers.setBufferData( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer, terminalBuffer, GL20.GL_STATIC_DRAW ); - ByteBuffer monitorBuffer = tboContents; - monitorBuffer.clear(); - for( int y = 0; y < height; y++ ) - { - TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y ); - for( int x = 0; x < width; x++ ) - { - monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) ); - monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.WHITE ) ); - monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.BLACK ) ); - } - } - monitorBuffer.flip(); - - GlStateManager._glBindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer ); - GlStateManager._glBufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL20.GL_STATIC_DRAW ); - GlStateManager._glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 ); + ByteBuffer uniformBuffer = getBuffer( MonitorTextureBufferShader.UNIFORM_SIZE ); + MonitorTextureBufferShader.setUniformData( uniformBuffer, terminal, !monitor.isColour() ); + DirectBuffers.setBufferData( GL31.GL_UNIFORM_BUFFER, monitor.tboUniform, uniformBuffer, GL20.GL_STATIC_DRAW ); } + // Sneaky hack here: we get a buffer now in order to flush existing ones and set up the appropriate + // render state. I've no clue how well this'll work in future versions of Minecraft, but it does the trick + // for now. + bufferSource.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ); + RenderTypes.TERMINAL_WITH_DEPTH.setupRenderState(); + // Nobody knows what they're doing! GlStateManager._activeTexture( MonitorTextureBufferShader.TEXTURE_INDEX ); GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture ); GlStateManager._activeTexture( GL13.GL_TEXTURE0 ); - MonitorTextureBufferShader.setupUniform( matrix, width, height, terminal.getPalette(), !monitor.isColour() ); + MonitorTextureBufferShader.setupUniform( matrix, monitor.tboUniform ); Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuilder(); @@ -217,28 +189,59 @@ private static void renderTerminal( Matrix4f matrix, ClientMonitor monitor, floa case VBO: { - VertexBuffer vbo = monitor.buffer; + DirectVertexBuffer vbo = monitor.buffer; if( redraw ) { - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder builder = tessellator.getBuilder(); - builder.begin( FixedWidthFontRenderer.TYPE.mode(), FixedWidthFontRenderer.TYPE.format() ); - FixedWidthFontRenderer.drawTerminalWithoutCursor( - IDENTITY, builder, 0, 0, - terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin - ); + int vertexSize = RenderTypes.TERMINAL_WITHOUT_DEPTH.format().getVertexSize(); + ByteBuffer buffer = getBuffer( DirectFixedWidthFontRenderer.getVertexCount( terminal ) * vertexSize ); - builder.end(); - vbo.upload( builder ); + // Draw the main terminal and store how many vertices it has. + DirectFixedWidthFontRenderer.drawTerminalWithoutCursor( + buffer, 0, 0, terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin + ); + int termIndexes = buffer.position() / vertexSize; + + // If the cursor is visible, we append it to the end of our buffer. When rendering, we can either + // render n or n+1 quads and so toggle the cursor on and off. + DirectFixedWidthFontRenderer.drawCursor( buffer, 0, 0, terminal, !monitor.isColour() ); + + buffer.flip(); + + vbo.upload( termIndexes, RenderTypes.TERMINAL_WITHOUT_DEPTH.format(), buffer ); } - vbo.bind(); - FixedWidthFontRenderer.TYPE.format().setupBufferState( 0L ); - vbo.draw( matrix, FixedWidthFontRenderer.TYPE.mode() ); - VertexBuffer.unbind(); - FixedWidthFontRenderer.TYPE.format().clearBufferState(); + // As with the TBO backend we use getBuffer to flush existing buffers. This time we use TERMINAL_WITHOUT_DEPTH + // instead and render a separate depth blocker. + bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ); + RenderTypes.TERMINAL_WITHOUT_DEPTH.setupRenderState(); + + vbo.draw( + matrix, + // As mentioned in the uploading block, render the extra cursor quad if it is visible this frame. + // Each quad has an index count of 6. + FixedWidthFontRenderer.isCursorVisible( terminal ) && FrameInfo.getGlobalCursorBlink() ? vbo.getIndexCount() + 6 : vbo.getIndexCount() + ); + + FixedWidthFontRenderer.drawBlocker( + matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_BLOCKER ), + -xMargin, -yMargin, pixelWidth + xMargin, pixelHeight + yMargin + ); break; } } } + + @Nonnull + private static ByteBuffer getBuffer( int capacity ) + { + + ByteBuffer buffer = backingBuffer; + if( buffer == null || buffer.capacity() < capacity ) + { + buffer = backingBuffer = buffer == null ? DirectBuffers.createByteBuffer( capacity ) : DirectBuffers.resizeByteBuffer( buffer, capacity ); + } + + buffer.clear(); + return buffer; + } } diff --git a/src/main/java/dan200/computercraft/client/render/TurtlePlayerRenderer.java b/src/main/java/dan200/computercraft/client/render/TurtlePlayerRenderer.java deleted file mode 100644 index 2a0371264..000000000 --- a/src/main/java/dan200/computercraft/client/render/TurtlePlayerRenderer.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.client.render; - -import com.mojang.blaze3d.matrix.MatrixStack; -import dan200.computercraft.shared.turtle.core.TurtlePlayer; -import net.minecraft.client.renderer.IRenderTypeBuffer; -import net.minecraft.client.renderer.entity.EntityRenderer; -import net.minecraft.client.renderer.entity.EntityRendererManager; -import net.minecraft.util.ResourceLocation; - -import javax.annotation.Nonnull; - -public class TurtlePlayerRenderer extends EntityRenderer -{ - public TurtlePlayerRenderer( EntityRendererManager renderManager ) - { - super( renderManager ); - } - - @Nonnull - @Override - public ResourceLocation getTextureLocation( @Nonnull TurtlePlayer entity ) - { - return ComputerBorderRenderer.BACKGROUND_NORMAL; - } - - @Override - public void render( @Nonnull TurtlePlayer entityIn, float entityYaw, float partialTicks, @Nonnull MatrixStack transform, @Nonnull IRenderTypeBuffer buffer, int packedLightIn ) - { - } -} diff --git a/src/main/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java b/src/main/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java new file mode 100644 index 000000000..c528cc951 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java @@ -0,0 +1,231 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.render.text; + +import com.mojang.blaze3d.vertex.IVertexBuilder; +import dan200.computercraft.client.util.DirectBuffers; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.terminal.TextBuffer; +import dan200.computercraft.shared.util.Colour; +import dan200.computercraft.shared.util.Palette; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import org.lwjgl.system.MemoryUtil; + +import javax.annotation.Nonnull; +import java.nio.ByteBuffer; + +import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*; +import static org.lwjgl.system.MemoryUtil.memPutByte; +import static org.lwjgl.system.MemoryUtil.memPutFloat; + +/** + * An optimised copy of {@link FixedWidthFontRenderer} emitter emits directly to a {@link ByteBuffer} rather than + * emitting to {@link IVertexBuilder}. This allows us to emit vertices very quickly, when using the VBO renderer. + * + * There are some limitations here: + *
    + *
  • No transformation matrix (not needed for VBOs).
  • + *
  • Only works with {@link DefaultVertexFormats#POSITION_COLOR_TEX}.
  • + *
  • The buffer MUST be allocated with {@link DirectBuffers}, and not through any other means.
  • + *
+ * + * Note this is almost an exact copy of {@link FixedWidthFontRenderer}. While the code duplication is unfortunate, + * it is measurably faster than introducing polymorphism into {@link FixedWidthFontRenderer}. + * + * IMPORTANT: When making changes to this class, please check if you need to make the same changes to + * {@link FixedWidthFontRenderer}. + */ +public final class DirectFixedWidthFontRenderer +{ + private DirectFixedWidthFontRenderer() + { + } + + private static void drawChar( ByteBuffer buffer, float x, float y, int index, byte[] colour ) + { + // Short circuit to avoid the common case - the texture should be blank here after all. + if( index == '\0' || index == ' ' ) return; + + int column = index % 16; + int row = index / 16; + + int xStart = 1 + column * (FONT_WIDTH + 2); + int yStart = 1 + row * (FONT_HEIGHT + 2); + + quad( + buffer, x, y, x + FONT_WIDTH, y + FONT_HEIGHT, colour, + xStart / WIDTH, yStart / WIDTH, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH + ); + } + + private static void drawQuad( ByteBuffer emitter, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex ) + { + byte[] colour = palette.getByteColour( getColour( colourIndex, Colour.BLACK ), greyscale ); + quad( emitter, x, y, x + width, y + height, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END ); + } + + private static void drawBackground( + @Nonnull ByteBuffer buffer, float x, float y, @Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale, + float leftMarginSize, float rightMarginSize, float height + ) + { + if( leftMarginSize > 0 ) + { + drawQuad( buffer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) ); + } + + if( rightMarginSize > 0 ) + { + drawQuad( buffer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) ); + } + + // Batch together runs of identical background cells. + int blockStart = 0; + char blockColour = '\0'; + for( int i = 0; i < backgroundColour.length(); i++ ) + { + char colourIndex = backgroundColour.charAt( i ); + if( colourIndex == blockColour ) continue; + + if( blockColour != '\0' ) + { + drawQuad( buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour ); + } + + blockColour = colourIndex; + blockStart = i; + } + + if( blockColour != '\0' ) + { + drawQuad( buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour ); + } + } + + private static void drawString( @Nonnull ByteBuffer buffer, float x, float y, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nonnull Palette palette, boolean greyscale ) + { + for( int i = 0; i < text.length(); i++ ) + { + byte[] colour = palette.getByteColour( getColour( textColour.charAt( i ), Colour.BLACK ), greyscale ); + + int index = text.charAt( i ); + if( index > 255 ) index = '?'; + drawChar( buffer, x + i * FONT_WIDTH, y, index, colour ); + } + } + + public static void drawTerminalWithoutCursor( + @Nonnull ByteBuffer buffer, float x, float y, @Nonnull Terminal terminal, boolean greyscale, + float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize + ) + { + Palette palette = terminal.getPalette(); + int height = terminal.getHeight(); + + // Top and bottom margins + drawBackground( + buffer, x, y - topMarginSize, terminal.getBackgroundColourLine( 0 ), palette, greyscale, + leftMarginSize, rightMarginSize, topMarginSize + ); + + drawBackground( + buffer, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine( height - 1 ), palette, greyscale, + leftMarginSize, rightMarginSize, bottomMarginSize + ); + + // The main text + for( int i = 0; i < height; i++ ) + { + float rowY = y + FONT_HEIGHT * i; + drawBackground( + buffer, x, rowY, terminal.getBackgroundColourLine( i ), palette, greyscale, + leftMarginSize, rightMarginSize, FONT_HEIGHT + ); + drawString( + buffer, x, rowY, terminal.getLine( i ), terminal.getTextColourLine( i ), + palette, greyscale + ); + } + } + + public static void drawCursor( @Nonnull ByteBuffer buffer, float x, float y, @Nonnull Terminal terminal, boolean greyscale ) + { + if( isCursorVisible( terminal ) ) + { + byte[] colour = terminal.getPalette().getByteColour( 15 - terminal.getTextColour(), greyscale ); + drawChar( buffer, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour ); + } + } + + public static int getVertexCount( Terminal terminal ) + { + return (1 + (terminal.getHeight() + 2) * terminal.getWidth() * 2) * 4; + } + + private static void quad( ByteBuffer buffer, float x1, float y1, float x2, float y2, byte[] rgba, float u1, float v1, float u2, float v2 ) + { + // Emit a single quad to our buffer. This uses Unsafe (well, LWJGL's MemoryUtil) to directly blit bytes to the + // underlying buffer. This allows us to have a single bounds check up-front, rather than one for every write. + // This provides significant performance gains, at the cost of well, using Unsafe. + // Each vertex is 24 bytes, giving 96 bytes in total. Vertices are of the form (xyz:FFF)(rgba:BBBB)(uv:FF), + // which matches the POSITION_COLOR_TEX vertex format. + + int position = buffer.position(); + long addr = MemoryUtil.memAddress( buffer ); + + // We're doing terrible unsafe hacks below, so let's be really sure that what we're doing is reasonable. + if( position < 0 || 96 > buffer.limit() - position ) throw new IndexOutOfBoundsException(); + // Require the pointer to be aligned to a 32-bit boundary. + if( (addr & 3) != 0 ) throw new IllegalStateException( "Memory is not aligned" ); + // Also assert the length of the array. This appears to help elide bounds checks on the array in some circumstances. + if( rgba.length != 4 ) throw new IllegalStateException(); + + memPutFloat( addr + 0, x1 ); + memPutFloat( addr + 4, y1 ); + memPutFloat( addr + 8, 0 ); + memPutByte( addr + 12, rgba[0] ); + memPutByte( addr + 13, rgba[1] ); + memPutByte( addr + 14, rgba[2] ); + memPutByte( addr + 15, (byte) 255 ); + memPutFloat( addr + 16, u1 ); + memPutFloat( addr + 20, v1 ); + + memPutFloat( addr + 24, x1 ); + memPutFloat( addr + 28, y2 ); + memPutFloat( addr + 32, 0 ); + memPutByte( addr + 36, rgba[0] ); + memPutByte( addr + 37, rgba[1] ); + memPutByte( addr + 38, rgba[2] ); + memPutByte( addr + 39, (byte) 255 ); + memPutFloat( addr + 40, u1 ); + memPutFloat( addr + 44, v2 ); + + memPutFloat( addr + 48, x2 ); + memPutFloat( addr + 52, y2 ); + memPutFloat( addr + 56, 0 ); + memPutByte( addr + 60, rgba[0] ); + memPutByte( addr + 61, rgba[1] ); + memPutByte( addr + 62, rgba[2] ); + memPutByte( addr + 63, (byte) 255 ); + memPutFloat( addr + 64, u2 ); + memPutFloat( addr + 68, v2 ); + + memPutFloat( addr + 72, x2 ); + memPutFloat( addr + 76, y1 ); + memPutFloat( addr + 80, 0 ); + memPutByte( addr + 84, rgba[0] ); + memPutByte( addr + 85, rgba[1] ); + memPutByte( addr + 86, rgba[2] ); + memPutByte( addr + 87, (byte) 255 ); + memPutFloat( addr + 88, u2 ); + memPutFloat( addr + 92, v1 ); + + // Finally increment the position. + buffer.position( position + 96 ); + + // Well done for getting to the end of this method. I recommend you take a break and go look at cute puppies. + } +} diff --git a/src/main/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java b/src/main/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java new file mode 100644 index 000000000..ba978369d --- /dev/null +++ b/src/main/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java @@ -0,0 +1,243 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.render.text; + +import com.mojang.blaze3d.vertex.IVertexBuilder; +import dan200.computercraft.client.FrameInfo; +import dan200.computercraft.client.render.RenderTypes; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.terminal.TextBuffer; +import dan200.computercraft.shared.util.Colour; +import dan200.computercraft.shared.util.Palette; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.vector.Matrix4f; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP; + +/** + * Handles rendering fixed width text and computer terminals. + * + * This class has several modes of usage: + *
    + *
  • {@link #drawString}: Drawing basic text without a terminal (such as for printouts). Unlike the other methods, + * this accepts a lightmap coordinate as, unlike terminals, printed pages render fullbright.
  • + *
  • {@link #drawTerminalWithoutCursor}/{@link #drawCursor}: Draw a terminal without a cursor and then draw the cursor + * separately. This is used by the monitor renderer to render the terminal to a VBO and draw the cursor dynamically. + *
  • + *
  • {@link #drawTerminal}: Draw a terminal with a cursor. This is used by the various computer GUIs to render the + * whole term.
  • + *
  • {@link #drawBlocker}: When rendering a terminal using {@link RenderTypes#TERMINAL_WITHOUT_DEPTH} you need to + * render an additional "depth blocker" on top of the monitor.
  • + *
+ * + * IMPORTANT: When making changes to this class, please check if you need to make the same changes to + * {@link DirectFixedWidthFontRenderer}. + */ +public final class FixedWidthFontRenderer +{ + public static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" ); + + public static final int FONT_HEIGHT = 9; + public static final int FONT_WIDTH = 6; + static final float WIDTH = 256.0f; + + static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH; + static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH; + + private static final byte[] BLACK = new byte[] { byteColour( Colour.BLACK.getR() ), byteColour( Colour.BLACK.getR() ), byteColour( Colour.BLACK.getR() ), (byte) 255 }; + + private FixedWidthFontRenderer() + { + } + + private static byte byteColour( float c ) + { + return (byte) (int) (c * 255); + } + + public static float toGreyscale( double[] rgb ) + { + return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3); + } + + public static int getColour( char c, Colour def ) + { + return 15 - Terminal.getColour( c, def ); + } + + private static void drawChar( Matrix4f transform, IVertexBuilder buffer, float x, float y, int index, byte[] colour, int light ) + { + // Short circuit to avoid the common case - the texture should be blank here after all. + if( index == '\0' || index == ' ' ) return; + + int column = index % 16; + int row = index / 16; + + int xStart = 1 + column * (FONT_WIDTH + 2); + int yStart = 1 + row * (FONT_HEIGHT + 2); + + quad( + transform, buffer, x, y, x + FONT_WIDTH, y + FONT_HEIGHT, 0, colour, + xStart / WIDTH, yStart / WIDTH, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light + ); + } + + public static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float z, float width, float height, byte[] colour, int light ) + { + quad( transform, buffer, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light ); + } + + private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex, int light ) + { + byte[] colour = palette.getByteColour( getColour( colourIndex, Colour.BLACK ), greyscale ); + drawQuad( transform, buffer, x, y, 0, width, height, colour, light ); + } + + private static void drawBackground( + @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, + @Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale, + float leftMarginSize, float rightMarginSize, float height, int light + ) + { + if( leftMarginSize > 0 ) + { + drawQuad( transform, buffer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ), light ); + } + + if( rightMarginSize > 0 ) + { + drawQuad( transform, buffer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ), light ); + } + + // Batch together runs of identical background cells. + int blockStart = 0; + char blockColour = '\0'; + for( int i = 0; i < backgroundColour.length(); i++ ) + { + char colourIndex = backgroundColour.charAt( i ); + if( colourIndex == blockColour ) continue; + + if( blockColour != '\0' ) + { + drawQuad( transform, buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour, light ); + } + + blockColour = colourIndex; + blockStart = i; + } + + if( blockColour != '\0' ) + { + drawQuad( transform, buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour, light ); + } + } + + public static void drawString( + @Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y, + @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nonnull Palette palette, boolean greyscale, int light + ) + { + for( int i = 0; i < text.length(); i++ ) + { + byte[] colour = palette.getByteColour( getColour( textColour.charAt( i ), Colour.BLACK ), greyscale ); + + int index = text.charAt( i ); + if( index > 255 ) index = '?'; + drawChar( transform, renderer, x + i * FONT_WIDTH, y, index, colour, light ); + } + } + + public static void drawTerminalWithoutCursor( + @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, + @Nonnull Terminal terminal, boolean greyscale, + float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize + ) + { + Palette palette = terminal.getPalette(); + int height = terminal.getHeight(); + + // Top and bottom margins + drawBackground( + transform, buffer, x, y - topMarginSize, + terminal.getBackgroundColourLine( 0 ), palette, greyscale, + leftMarginSize, rightMarginSize, topMarginSize, FULL_BRIGHT_LIGHTMAP + ); + + drawBackground( + transform, buffer, x, y + height * FONT_HEIGHT, + terminal.getBackgroundColourLine( height - 1 ), palette, greyscale, + leftMarginSize, rightMarginSize, bottomMarginSize, FULL_BRIGHT_LIGHTMAP + ); + + // The main text + for( int i = 0; i < height; i++ ) + { + float rowY = y + FONT_HEIGHT * i; + drawBackground( + transform, buffer, x, rowY, terminal.getBackgroundColourLine( i ), + palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT, FULL_BRIGHT_LIGHTMAP + ); + + drawString( + transform, buffer, x, rowY, terminal.getLine( i ), terminal.getTextColourLine( i ), + palette, greyscale, FULL_BRIGHT_LIGHTMAP + ); + } + } + + public static boolean isCursorVisible( Terminal terminal ) + { + if( !terminal.getCursorBlink() ) return false; + + int cursorX = terminal.getCursorX(); + int cursorY = terminal.getCursorY(); + return cursorX >= 0 && cursorX < terminal.getWidth() && cursorY >= 0 && cursorY < terminal.getHeight(); + } + + public static void drawCursor( + @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, + @Nonnull Terminal terminal, boolean greyscale + ) + { + if( isCursorVisible( terminal ) && FrameInfo.getGlobalCursorBlink() ) + { + byte[] colour = terminal.getPalette().getByteColour( 15 - terminal.getTextColour(), greyscale ); + drawChar( transform, buffer, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour, FULL_BRIGHT_LIGHTMAP ); + } + } + + public static void drawTerminal( + @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, + @Nonnull Terminal terminal, boolean greyscale, + float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize + ) + { + drawTerminalWithoutCursor( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize ); + drawCursor( transform, buffer, x, y, terminal, greyscale ); + } + + public static void drawEmptyTerminal( @Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y, float width, float height ) + { + drawQuad( transform, renderer, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP ); + } + + public static void drawBlocker( @Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y, float width, float height ) + { + drawQuad( transform, buffer, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP ); + } + + private static void quad( Matrix4f matrix, IVertexBuilder buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2, int light ) + { + byte r = rgba[0], g = rgba[1], b = rgba[2], a = rgba[3]; + + buffer.vertex( matrix, x1, y1, z ).color( r, g, b, a ).uv( u1, v1 ).uv2( light ).endVertex(); + buffer.vertex( matrix, x1, y2, z ).color( r, g, b, a ).uv( u1, v2 ).uv2( light ).endVertex(); + buffer.vertex( matrix, x2, y2, z ).color( r, g, b, a ).uv( u2, v2 ).uv2( light ).endVertex(); + buffer.vertex( matrix, x2, y1, z ).color( r, g, b, a ).uv( u2, v1 ).uv2( light ).endVertex(); + } +} diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java b/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java index 62fed7d8c..d6c94dbba 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerInstance.java @@ -6,11 +6,11 @@ package dan200.computercraft.client.sound; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import io.netty.buffer.ByteBuf; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.SoundHandler; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.vector.Vector3d; /** * An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound. @@ -44,7 +44,7 @@ public synchronized void pushAudio( ByteBuf buffer ) } } - public void playAudio( Vector3d position, float volume ) + public void playAudio( SpeakerPosition position, float volume ) { SoundHandler soundManager = Minecraft.getInstance().getSoundManager(); @@ -63,7 +63,7 @@ public void playAudio( Vector3d position, float volume ) } } - public void playSound( Vector3d position, ResourceLocation location, float volume, float pitch ) + public void playSound( SpeakerPosition position, ResourceLocation location, float volume, float pitch ) { SoundHandler soundManager = Minecraft.getInstance().getSoundManager(); currentStream = null; @@ -78,7 +78,7 @@ public void playSound( Vector3d position, ResourceLocation location, float volum soundManager.play( sound ); } - void setPosition( Vector3d position ) + void setPosition( SpeakerPosition position ) { if( sound != null ) sound.setPosition( position ); } diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java b/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java index 0f28c562e..2879d52d3 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerManager.java @@ -5,7 +5,7 @@ */ package dan200.computercraft.client.sound; -import net.minecraft.util.math.vector.Vector3d; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.sound.PlayStreamingSourceEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -48,7 +48,7 @@ public static void stopSound( UUID source ) if( sound != null ) sound.stop(); } - public static void moveSound( UUID source, Vector3d position ) + public static void moveSound( UUID source, SpeakerPosition position ) { SpeakerInstance sound = sounds.get( source ); if( sound != null ) sound.setPosition( position ); diff --git a/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java b/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java index d39390993..e1ef98159 100644 --- a/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java +++ b/src/main/java/dan200/computercraft/client/sound/SpeakerSound.java @@ -5,13 +5,14 @@ */ package dan200.computercraft.client.sound; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import net.minecraft.client.audio.IAudioStream; import net.minecraft.client.audio.ITickableSound; import net.minecraft.client.audio.LocatableSound; import net.minecraft.client.audio.SoundSource; +import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundCategory; -import net.minecraft.util.math.vector.Vector3d; import javax.annotation.Nullable; import java.util.concurrent.Executor; @@ -22,7 +23,11 @@ public class SpeakerSound extends LocatableSound implements ITickableSound Executor executor; DfpwmStream stream; - SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vector3d position, float volume, float pitch ) + private Entity entity; + + private boolean stopped = false; + + SpeakerSound( ResourceLocation sound, DfpwmStream stream, SpeakerPosition position, float volume, float pitch ) { super( sound, SoundCategory.RECORDS ); setPosition( position ); @@ -32,22 +37,35 @@ public class SpeakerSound extends LocatableSound implements ITickableSound attenuation = AttenuationType.LINEAR; } - void setPosition( Vector3d position ) + void setPosition( SpeakerPosition position ) { - x = (float) position.x(); - y = (float) position.y(); - z = (float) position.z(); + x = position.position().x; + y = position.position().y; + z = position.position().z; + entity = position.entity(); } @Override public boolean isStopped() { - return false; + return stopped; } @Override public void tick() { + if( entity == null ) return; + if( !entity.isAlive() ) + { + stopped = true; + looping = false; + } + else + { + x = entity.getX(); + y = entity.getY(); + z = entity.getZ(); + } } @Nullable diff --git a/src/main/java/dan200/computercraft/client/util/DirectBuffers.java b/src/main/java/dan200/computercraft/client/util/DirectBuffers.java new file mode 100644 index 000000000..fcddf00aa --- /dev/null +++ b/src/main/java/dan200/computercraft/client/util/DirectBuffers.java @@ -0,0 +1,83 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.util; + +import com.mojang.blaze3d.platform.GlStateManager; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL15C; +import org.lwjgl.opengl.GL45C; +import org.lwjgl.opengl.GLCapabilities; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; + +/** + * Provides utilities to interact with OpenGL's buffer objects, either using direct state access or binding/unbinding + * it. + */ +public class DirectBuffers +{ + public static final boolean HAS_DSA; + + static + { + GLCapabilities capabilities = GL.getCapabilities(); + HAS_DSA = capabilities.OpenGL45 || capabilities.GL_ARB_direct_state_access; + } + + public static int createBuffer() + { + return HAS_DSA ? GL45C.glCreateBuffers() : GL15C.glGenBuffers(); + } + + public static void setBufferData( int type, int id, ByteBuffer buffer, int flags ) + { + if( HAS_DSA ) + { + GL45C.glNamedBufferData( id, buffer, flags ); + } + else + { + GlStateManager._glBindBuffer( type, id ); + GlStateManager._glBufferData( type, buffer, GL15C.GL_STATIC_DRAW ); + GlStateManager._glBindBuffer( type, 0 ); + } + } + + public static void setEmptyBufferData( int type, int id, int flags ) + { + if( HAS_DSA ) + { + GL45C.glNamedBufferData( id, 0, flags ); + } + else + { + GlStateManager._glBindBuffer( type, id ); + GL45C.glBufferData( type, 0, GL15C.GL_STATIC_DRAW ); + GlStateManager._glBindBuffer( type, 0 ); + } + } + + private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator( false ); + + public static ByteBuffer createByteBuffer( int size ) + { + long i = ALLOCATOR.malloc( size ); + if( i == 0L ) throw new OutOfMemoryError( "Failed to allocate " + size + " bytes" ); + return MemoryUtil.memByteBuffer( i, size ); + } + + public static ByteBuffer resizeByteBuffer( ByteBuffer buffer, int size ) + { + long i = ALLOCATOR.realloc( MemoryUtil.memAddress0( buffer ), size ); + if( i == 0L ) + { + throw new OutOfMemoryError( "Failed to resize buffer from " + buffer.capacity() + " bytes to " + size + " bytes" ); + } + + return MemoryUtil.memByteBuffer( i, size ); + } +} diff --git a/src/main/java/dan200/computercraft/client/util/DirectVertexBuffer.java b/src/main/java/dan200/computercraft/client/util/DirectVertexBuffer.java new file mode 100644 index 000000000..4b2dc9569 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/util/DirectVertexBuffer.java @@ -0,0 +1,79 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.util; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.renderer.vertex.VertexBuffer; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.math.vector.Matrix4f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL15; + +import java.nio.ByteBuffer; + +/** + * A version of {@link VertexBuffer} which allows uploading {@link ByteBuffer}s directly. + */ +public class DirectVertexBuffer implements AutoCloseable +{ + private int vertextBufferId; + private int indexCount; + private VertexFormat format; + + public DirectVertexBuffer() + { + vertextBufferId = DirectBuffers.createBuffer(); + } + + public void upload( int vertexCount, VertexFormat format, ByteBuffer buffer ) + { + RenderSystem.assertThread( RenderSystem::isOnGameThread ); + + DirectBuffers.setBufferData( GL15.GL_ARRAY_BUFFER, vertextBufferId, buffer, GL15.GL_STATIC_DRAW ); + + this.format = format; + indexCount = vertexCount; + } + + public void draw( Matrix4f matrix, int indexCount ) + { + bind(); + format.setupBufferState( 0 ); + + RenderSystem.pushMatrix(); + RenderSystem.loadIdentity(); + RenderSystem.multMatrix( matrix ); + RenderSystem.drawArrays( GL11.GL_QUADS, 0, indexCount ); + RenderSystem.popMatrix(); + + unbind(); + } + + public int getIndexCount() + { + return indexCount; + } + + @Override + public void close() + { + if( vertextBufferId >= 0 ) + { + RenderSystem.glDeleteBuffers( vertextBufferId ); + vertextBufferId = -1; + } + } + + private void bind() + { + RenderSystem.glBindBuffer( GL15.GL_ARRAY_BUFFER, () -> vertextBufferId ); + } + + private static void unbind() + { + RenderSystem.glBindBuffer( GL15.GL_ARRAY_BUFFER, () -> 0 ); + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/FSAPI.java b/src/main/java/dan200/computercraft/core/apis/FSAPI.java index 79bbea53b..12d84fb75 100644 --- a/src/main/java/dan200/computercraft/core/apis/FSAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/FSAPI.java @@ -30,8 +30,8 @@ import java.util.function.Function; /** - * The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and - * paths. This includes: + * Interact with the computer's files and filesystem, allowing you to manipulate files, directories and paths. This + * includes: * *
    *
  • **Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java index 2c4801cf4..de83a06c8 100644 --- a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java @@ -28,7 +28,7 @@ import static dan200.computercraft.core.apis.TableHelper.*; /** - * The http library allows communicating with web servers, sending and receiving data from them. + * Placeholder description, please ignore. * * @cc.module http * @hidden diff --git a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java index 8600656d6..9d13b6f2d 100644 --- a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java @@ -11,7 +11,7 @@ import dan200.computercraft.core.computer.ComputerSide; /** - * Interact with redstone attached to this computer. + * Get and set redstone signals adjacent to this computer. * * The {@link RedstoneAPI} library exposes three "types" of redstone control: * - Binary input/output ({@link #setOutput}/{@link #getInput}): These simply check if a redstone wire has any input or diff --git a/src/main/java/dan200/computercraft/core/apis/TermAPI.java b/src/main/java/dan200/computercraft/core/apis/TermAPI.java index 5821c0e28..716b9ddb4 100644 --- a/src/main/java/dan200/computercraft/core/apis/TermAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/TermAPI.java @@ -16,7 +16,8 @@ import javax.annotation.Nonnull; /** - * The Terminal API provides functions for writing text to the terminal and monitors, and drawing ASCII graphics. + * Interact with a computer's terminal or monitors, writing text and drawing + * ASCII graphics. * * @cc.module term */ diff --git a/src/main/java/dan200/computercraft/core/apis/TermMethods.java b/src/main/java/dan200/computercraft/core/apis/TermMethods.java index 84c104e34..5692065a6 100644 --- a/src/main/java/dan200/computercraft/core/apis/TermMethods.java +++ b/src/main/java/dan200/computercraft/core/apis/TermMethods.java @@ -14,6 +14,7 @@ import org.apache.commons.lang3.ArrayUtils; import javax.annotation.Nonnull; +import java.nio.ByteBuffer; /** * A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors. @@ -283,9 +284,9 @@ public final boolean getIsColour() throws LuaException * } */ @LuaFunction - public final void blit( String text, String textColour, String backgroundColour ) throws LuaException + public final void blit( ByteBuffer text, ByteBuffer textColour, ByteBuffer backgroundColour ) throws LuaException { - if( textColour.length() != text.length() || backgroundColour.length() != text.length() ) + if( textColour.remaining() != text.remaining() || backgroundColour.remaining() != text.remaining() ) { throw new LuaException( "Arguments must be the same length" ); } @@ -294,7 +295,7 @@ public final void blit( String text, String textColour, String backgroundColour synchronized( terminal ) { terminal.blit( text, textColour, backgroundColour ); - terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); + terminal.setCursorPos( terminal.getCursorX() + text.remaining(), terminal.getCursorY() ); } } diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java index 69106a0e1..d9f0a95a5 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java +++ b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java @@ -32,10 +32,10 @@ public static UnmodifiableConfig makeRule( String host, Action action ) config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." ); config.add( "timeout", AddressRule.TIMEOUT ); - config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." ); + config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request.\nNote that responses may receive more data than allowed, but this data will not\nbe returned to the client." ); config.set( "max_download", AddressRule.MAX_DOWNLOAD ); - config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." ); + config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This\nincludes headers and POST text." ); config.set( "max_upload", AddressRule.MAX_UPLOAD ); config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." ); diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index fb59fad7f..c6a3a7a34 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -53,6 +53,7 @@ */ final class ComputerExecutor { + static ILuaMachine.Factory luaFactory = CobaltLuaMachine::new; private static final int QUEUE_LIMIT = 256; private final Computer computer; @@ -400,7 +401,7 @@ private ILuaMachine createLuaMachine() } // Create the lua machine - ILuaMachine machine = new CobaltLuaMachine( computer, timeout ); + ILuaMachine machine = luaFactory.create( computer, timeout ); // Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object. for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api ); diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java index 028b7b056..d557fabe5 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java @@ -13,6 +13,7 @@ import java.util.TreeSet; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.LockSupport; @@ -49,11 +50,11 @@ public final class ComputerThread { /** - * How often the computer thread monitor should run, in milliseconds. + * How often the computer thread monitor should run. * * @see Monitor */ - private static final int MONITOR_WAKEUP = 100; + private static final long MONITOR_WAKEUP = TimeUnit.MILLISECONDS.toNanos( 100 ); /** * The target latency between executing two tasks on a single machine. @@ -76,6 +77,13 @@ public final class ComputerThread */ private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD; + /** + * Time difference between reporting crashed threads. + * + * @see TaskRunner#reportTimeout(ComputerExecutor, long) + */ + private static final long REPORT_DEBOUNCE = TimeUnit.SECONDS.toNanos( 1 ); + /** * Lock used for modifications to the array of current threads. */ @@ -102,6 +110,8 @@ public final class ComputerThread private static final ReentrantLock computerLock = new ReentrantLock(); private static final Condition hasWork = computerLock.newCondition(); + private static final AtomicInteger idleWorkers = new AtomicInteger( 0 ); + private static final Condition monitorWakeup = computerLock.newCondition(); /** * Active queues to execute. @@ -135,7 +145,7 @@ static void start() if( runners == null ) { - // TODO: Change the runners length on config reloads + // TODO: Update this on config reloads. Or possibly on world restarts? runners = new TaskRunner[ComputerCraft.computerThreads]; // latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for @@ -227,9 +237,14 @@ static void queue( @Nonnull ComputerExecutor executor ) executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime ); + boolean wasBusy = isBusy(); // Add to the queue, and signal the workers. computerQueue.add( executor ); hasWork.signal(); + + // If we've transitioned into a busy state, notify the monitor. This will cause it to sleep for scaledPeriod + // instead of the longer wakeup duration. + if( !wasBusy && isBusy() ) monitorWakeup.signal(); } finally { @@ -346,6 +361,17 @@ static boolean hasPendingWork() return !computerQueue.isEmpty(); } + /** + * Check if we have more work queued than we have capacity for. Effectively a more fine-grained version of + * {@link #hasPendingWork()}. + * + * @return If the computer threads are busy. + */ + private static boolean isBusy() + { + return computerQueue.size() > idleWorkers.get(); + } + /** * Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard * abort limit. @@ -357,76 +383,93 @@ private static final class Monitor implements Runnable @Override public void run() { - try + while( true ) { - while( true ) + computerLock.lock(); + try { - Thread.sleep( MONITOR_WAKEUP ); + // If we've got more work than we have capacity for it, then we'll need to pause a task soon, so + // sleep for a single pause duration. Otherwise we only need to wake up to set the soft/hard abort + // flags, which are far less granular. + monitorWakeup.awaitNanos( isBusy() ? scaledPeriod() : MONITOR_WAKEUP ); + } + catch( InterruptedException e ) + { + ComputerCraft.log.error( "Monitor thread interrupted. Computers may behave very badly!", e ); + break; + } + finally + { + computerLock.unlock(); + } - TaskRunner[] currentRunners = ComputerThread.runners; - if( currentRunners != null ) + checkRunners(); + } + } + + private static void checkRunners() + { + TaskRunner[] currentRunners = ComputerThread.runners; + if( currentRunners == null ) return; + + for( int i = 0; i < currentRunners.length; i++ ) + { + TaskRunner runner = currentRunners[i]; + // If we've no runner, skip. + if( runner == null || runner.owner == null || !runner.owner.isAlive() ) + { + if( !running ) continue; + + // Mark the old runner as dead and start a new one. + ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!", + runner != null && runner.owner != null ? runner.owner.getName() : runner ); + if( runner != null ) runner.running = false; + runnerFactory.newThread( runners[i] = new TaskRunner() ).start(); + } + + // If the runner has no work, skip + ComputerExecutor executor = runner.currentExecutor.get(); + if( executor == null ) continue; + + // Refresh the timeout state. Will set the pause/soft timeout flags as appropriate. + executor.timeout.refresh(); + + // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), + // then we can let the Lua machine do its work. + long afterStart = executor.timeout.nanoCumulative(); + long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; + if( afterHardAbort < 0 ) continue; + + // Set the hard abort flag. + executor.timeout.hardAbort(); + executor.abort(); + + if( afterHardAbort >= ABORT_TIMEOUT * 2 ) + { + // If we've hard aborted and interrupted, and we're still not dead, then mark the runner + // as dead, finish off the task, and spawn a new runner. + runner.reportTimeout( executor, afterStart ); + runner.running = false; + runner.owner.interrupt(); + + ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null ); + if( thisExecutor != null ) afterWork( runner, executor ); + + synchronized( threadLock ) { - for( int i = 0; i < currentRunners.length; i++ ) + if( running && runners.length > i && runners[i] == runner ) { - TaskRunner runner = currentRunners[i]; - // If we've no runner, skip. - if( runner == null || runner.owner == null || !runner.owner.isAlive() ) - { - if( !running ) continue; - - // Mark the old runner as dead and start a new one. - ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!", - runner != null && runner.owner != null ? runner.owner.getName() : runner ); - if( runner != null ) runner.running = false; - runnerFactory.newThread( runners[i] = new TaskRunner() ).start(); - } - - // If the runner has no work, skip - ComputerExecutor executor = runner.currentExecutor.get(); - if( executor == null ) continue; - - // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), - // then we can let the Lua machine do its work. - long afterStart = executor.timeout.nanoCumulative(); - long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; - if( afterHardAbort < 0 ) continue; - - // Set the hard abort flag. - executor.timeout.hardAbort(); - executor.abort(); - - if( afterHardAbort >= ABORT_TIMEOUT * 2 ) - { - // If we've hard aborted and interrupted, and we're still not dead, then mark the runner - // as dead, finish off the task, and spawn a new runner. - timeoutTask( executor, runner.owner, afterStart ); - runner.running = false; - runner.owner.interrupt(); - - ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null ); - if( thisExecutor != null ) afterWork( runner, executor ); - - synchronized( threadLock ) - { - if( running && runners.length > i && runners[i] == runner ) - { - runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start(); - } - } - } - else if( afterHardAbort >= ABORT_TIMEOUT ) - { - // If we've hard aborted but we're still not dead, dump the stack trace and interrupt - // the task. - timeoutTask( executor, runner.owner, afterStart ); - runner.owner.interrupt(); - } + runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start(); } } } - } - catch( InterruptedException ignored ) - { + else if( afterHardAbort >= ABORT_TIMEOUT ) + { + // If we've hard aborted but we're still not dead, dump the stack trace and interrupt + // the task. + runner.reportTimeout( executor, afterStart ); + runner.owner.interrupt(); + } } } } @@ -441,6 +484,7 @@ else if( afterHardAbort >= ABORT_TIMEOUT ) private static final class TaskRunner implements Runnable { Thread owner; + long lastReport = Long.MIN_VALUE; volatile boolean running = true; final AtomicReference currentExecutor = new AtomicReference<>(); @@ -460,6 +504,7 @@ public void run() computerLock.lockInterruptibly(); try { + idleWorkers.incrementAndGet(); while( computerQueue.isEmpty() ) hasWork.await(); executor = computerQueue.pollFirst(); assert executor != null : "hasWork should ensure we never receive null work"; @@ -467,6 +512,7 @@ public void run() finally { computerLock.unlock(); + idleWorkers.decrementAndGet(); } } catch( InterruptedException ignored ) @@ -516,27 +562,32 @@ public void run() } } } - } - private static void timeoutTask( ComputerExecutor executor, Thread thread, long time ) - { - if( !ComputerCraft.logComputerErrors ) return; - - StringBuilder builder = new StringBuilder() - .append( "Terminating computer #" ).append( executor.getComputer().getID() ) - .append( " due to timeout (running for " ).append( time * 1e-9 ) - .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) - .append( thread.getName() ) - .append( " is currently " ) - .append( thread.getState() ); - Object blocking = LockSupport.getBlocker( thread ); - if( blocking != null ) builder.append( "\n on " ).append( blocking ); - - for( StackTraceElement element : thread.getStackTrace() ) + private void reportTimeout( ComputerExecutor executor, long time ) { - builder.append( "\n at " ).append( element ); - } + if( !ComputerCraft.logComputerErrors ) return; - ComputerCraft.log.warn( builder.toString() ); + // Attempt to debounce stack trace reporting, limiting ourselves to one every second. + long now = System.nanoTime(); + if( lastReport != Long.MIN_VALUE && now - lastReport - REPORT_DEBOUNCE <= 0 ) return; + lastReport = now; + + StringBuilder builder = new StringBuilder() + .append( "Terminating computer #" ).append( executor.getComputer().getID() ) + .append( " due to timeout (running for " ).append( time * 1e-9 ) + .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) + .append( owner.getName() ) + .append( " is currently " ) + .append( owner.getState() ); + Object blocking = LockSupport.getBlocker( owner ); + if( blocking != null ) builder.append( "\n on " ).append( blocking ); + + for( StackTraceElement element : owner.getStackTrace() ) + { + builder.append( "\n at " ).append( element ); + } + + ComputerCraft.log.warn( builder.toString() ); + } } } diff --git a/src/main/java/dan200/computercraft/core/computer/MainThread.java b/src/main/java/dan200/computercraft/core/computer/MainThread.java index c1658aa67..222abb274 100644 --- a/src/main/java/dan200/computercraft/core/computer/MainThread.java +++ b/src/main/java/dan200/computercraft/core/computer/MainThread.java @@ -23,8 +23,8 @@ * {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks * (those run by tile entities, etc...) will also consume the budget * - * Next tick, we put {@link ComputerCraft#maxMainGlobalTime} into our budget (and clamp it to that value to). If we're - * still over budget, then we should not execute any work (either as part of {@link MainThread} or externally). + * Next tick, we add {@link ComputerCraft#maxMainGlobalTime} to our budget (clamp it to that value too). If we're still + * over budget, then we should not execute any work (either as part of {@link MainThread} or externally). */ public final class MainThread { diff --git a/src/main/java/dan200/computercraft/core/computer/TimeoutState.java b/src/main/java/dan200/computercraft/core/computer/TimeoutState.java index 875bf6788..80be47199 100644 --- a/src/main/java/dan200/computercraft/core/computer/TimeoutState.java +++ b/src/main/java/dan200/computercraft/core/computer/TimeoutState.java @@ -86,7 +86,7 @@ long nanoCurrent() /** * Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags. */ - public void refresh() + public synchronized void refresh() { // Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we // need to handle overflow. @@ -153,7 +153,7 @@ void startTimer() * * @see #nanoCumulative() */ - void pauseTimer() + synchronized void pauseTimer() { // We set the cumulative time to difference between current time and "nominal start time". cumulativeElapsed = System.nanoTime() - cumulativeStart; @@ -163,7 +163,7 @@ void pauseTimer() /** * Resets the cumulative time and resets the abort flags. */ - void stopTimer() + synchronized void stopTimer() { cumulativeElapsed = 0; paused = softAbort = hardAbort = false; diff --git a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java index 60e4d1bc9..b6a12ddec 100644 --- a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java @@ -6,7 +6,6 @@ package dan200.computercraft.core.lua; import dan200.computercraft.ComputerCraft; -import dan200.computercraft.api.lua.IArguments; import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.MethodResult; @@ -41,7 +40,7 @@ class BasicFunction extends VarArgFunction @Override public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError { - IArguments arguments = CobaltLuaMachine.toArguments( args ); + VarargArguments arguments = VarargArguments.of( args ); MethodResult results; try { @@ -61,7 +60,7 @@ public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError } finally { - arguments.releaseImmediate(); + arguments.close(); } if( results.getCallback() != null ) diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index 5fa141a1f..555466a20 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -14,8 +14,8 @@ import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.shared.util.ThreadUtils; -import org.squiddev.cobalt.*; import org.squiddev.cobalt.LuaTable; +import org.squiddev.cobalt.*; import org.squiddev.cobalt.compiler.CompileException; import org.squiddev.cobalt.compiler.LoadState; import org.squiddev.cobalt.debug.DebugFrame; @@ -424,11 +424,6 @@ static Object[] toObjects( Varargs values ) return objects; } - static IArguments toArguments( Varargs values ) - { - return values == Constants.NONE ? VarargArguments.EMPTY : new VarargArguments( values ); - } - /** * A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly. */ @@ -457,24 +452,9 @@ public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaErro // We check our current pause/abort state every 128 instructions. if( (count = (count + 1) & 127) == 0 ) { - // If we've been hard aborted or closed then abort. if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; - - timeout.refresh(); - if( timeout.isPaused() ) - { - // Preserve the current state - isPaused = true; - oldInHook = ds.inhook; - oldFlags = di.flags; - - // Suspend the state. This will probably throw, but we need to handle the case where it won't. - di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED; - LuaThread.suspend( ds.getLuaState() ); - resetPaused( ds, di ); - } - - handleSoftAbort(); + if( timeout.isPaused() ) handlePause( ds, di ); + if( timeout.isSoftAborted() ) handleSoftAbort(); } super.onInstruction( ds, di, pc ); @@ -483,13 +463,10 @@ public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaErro @Override public void poll() throws LuaError { - // If we've been hard aborted or closed then abort. LuaState state = CobaltLuaMachine.this.state; if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; - - timeout.refresh(); if( timeout.isPaused() ) LuaThread.suspendBlocking( state ); - handleSoftAbort(); + if( timeout.isSoftAborted() ) handleSoftAbort(); } private void resetPaused( DebugState ds, DebugFrame di ) @@ -503,11 +480,24 @@ private void resetPaused( DebugState ds, DebugFrame di ) private void handleSoftAbort() throws LuaError { // If we already thrown our soft abort error then don't do it again. - if( !timeout.isSoftAborted() || thrownSoftAbort ) return; + if( thrownSoftAbort ) return; thrownSoftAbort = true; throw new LuaError( TimeoutState.ABORT_MESSAGE ); } + + private void handlePause( DebugState ds, DebugFrame di ) throws LuaError, UnwindThrowable + { + // Preserve the current state + isPaused = true; + oldInHook = ds.inhook; + oldFlags = di.flags; + + // Suspend the state. This will probably throw, but we need to handle the case where it won't. + di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED; + LuaThread.suspend( ds.getLuaState() ); + resetPaused( ds, di ); + } } private static final class HardAbortError extends Error diff --git a/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java b/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java index f08f23788..1b981696a 100644 --- a/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/ILuaMachine.java @@ -7,6 +7,8 @@ import dan200.computercraft.api.lua.IDynamicLuaObject; import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.TimeoutState; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -63,4 +65,9 @@ public interface ILuaMachine * Close the Lua machine, aborting any running functions and deleting the internal state. */ void close(); + + interface Factory + { + ILuaMachine create( Computer computer, TimeoutState timeout ); + } } diff --git a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java index d7b9ec297..f848ebde2 100644 --- a/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/ResultInterpreterFunction.java @@ -51,7 +51,7 @@ static class Container @Override protected Varargs invoke( LuaState state, DebugFrame debugFrame, Varargs args ) throws LuaError, UnwindThrowable { - IArguments arguments = CobaltLuaMachine.toArguments( args ); + VarargArguments arguments = VarargArguments.of( args ); MethodResult results; try { @@ -71,7 +71,7 @@ protected Varargs invoke( LuaState state, DebugFrame debugFrame, Varargs args ) } finally { - arguments.releaseImmediate(); + arguments.close(); } ILuaCallback callback = results.getCallback(); diff --git a/src/main/java/dan200/computercraft/core/lua/TableImpl.java b/src/main/java/dan200/computercraft/core/lua/TableImpl.java index d2f966a5a..0ad8b68df 100644 --- a/src/main/java/dan200/computercraft/core/lua/TableImpl.java +++ b/src/main/java/dan200/computercraft/core/lua/TableImpl.java @@ -133,7 +133,7 @@ public Set> entrySet() private void checkValid() { - if( arguments.released ) + if( arguments.closed ) { throw new IllegalStateException( "Cannot use LuaTable after IArguments has been released" ); } diff --git a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java index dc8f99b9e..a501bad3c 100644 --- a/src/main/java/dan200/computercraft/core/lua/VarargArguments.java +++ b/src/main/java/dan200/computercraft/core/lua/VarargArguments.java @@ -15,19 +15,24 @@ import java.nio.ByteBuffer; import java.util.Optional; -class VarargArguments implements IArguments +final class VarargArguments implements IArguments { - static final IArguments EMPTY = new VarargArguments( Constants.NONE ); + private static final VarargArguments EMPTY = new VarargArguments( Constants.NONE ); - boolean released; + boolean closed; private final Varargs varargs; private Object[] cache; - VarargArguments( Varargs varargs ) + private VarargArguments( Varargs varargs ) { this.varargs = varargs; } + static VarargArguments of( Varargs values ) + { + return values == Constants.NONE ? EMPTY : new VarargArguments( values ); + } + @Override public int count() { @@ -104,9 +109,9 @@ public Optional optBytes( int index ) throws LuaException @Override public dan200.computercraft.api.lua.LuaTable getTableUnsafe( int index ) throws LuaException { - if( released ) + if( closed ) { - throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been released" ); + throw new IllegalStateException( "Cannot use getTableUnsafe after IArguments has been closed." ); } LuaValue value = varargs.arg( index + 1 ); @@ -118,9 +123,9 @@ public Optional optBytes( int index ) throws LuaException @Override public Optional> optTableUnsafe( int index ) throws LuaException { - if( released ) + if( closed ) { - throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been released" ); + throw new IllegalStateException( "Cannot use optTableUnsafe after IArguments has been closed." ); } LuaValue value = varargs.arg( index + 1 ); @@ -129,9 +134,8 @@ public Optional optBytes( int index ) throws LuaException return Optional.of( new TableImpl( this, (LuaTable) value ) ); } - @Override - public void releaseImmediate() + public void close() { - released = true; + closed = true; } } diff --git a/src/main/java/dan200/computercraft/core/terminal/Terminal.java b/src/main/java/dan200/computercraft/core/terminal/Terminal.java index b8c2b8411..45fa8b6b4 100644 --- a/src/main/java/dan200/computercraft/core/terminal/Terminal.java +++ b/src/main/java/dan200/computercraft/core/terminal/Terminal.java @@ -11,6 +11,7 @@ import net.minecraft.network.PacketBuffer; import javax.annotation.Nonnull; +import java.nio.ByteBuffer; public class Terminal { @@ -191,7 +192,7 @@ public Palette getPalette() return palette; } - public synchronized void blit( String text, String textColour, String backgroundColour ) + public synchronized void blit( ByteBuffer text, ByteBuffer textColour, ByteBuffer backgroundColour ) { int x = cursorX; int y = cursorY; @@ -323,9 +324,9 @@ public synchronized void write( PacketBuffer buffer ) TextBuffer textColour = this.textColour[y]; TextBuffer backColour = backgroundColour[y]; + for( int x = 0; x < width; x++ ) buffer.writeByte( text.charAt( x ) & 0xFF ); for( int x = 0; x < width; x++ ) { - buffer.writeByte( text.charAt( x ) & 0xFF ); buffer.writeByte( getColour( backColour.charAt( x ), Colour.BLACK ) << 4 | getColour( textColour.charAt( x ), Colour.WHITE ) @@ -352,10 +353,9 @@ public synchronized void read( PacketBuffer buffer ) TextBuffer textColour = this.textColour[y]; TextBuffer backColour = backgroundColour[y]; + for( int x = 0; x < width; x++ ) text.setChar( x, (char) (buffer.readByte() & 0xFF) ); for( int x = 0; x < width; x++ ) { - text.setChar( x, (char) (buffer.readByte() & 0xFF) ); - byte colour = buffer.readByte(); backColour.setChar( x, base16.charAt( (colour >> 4) & 0xF ) ); textColour.setChar( x, base16.charAt( colour & 0xF ) ); diff --git a/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java b/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java index e1211639b..2809546e0 100644 --- a/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java +++ b/src/main/java/dan200/computercraft/core/terminal/TextBuffer.java @@ -5,6 +5,8 @@ */ package dan200.computercraft.core.terminal; +import java.nio.ByteBuffer; + public class TextBuffer { private final char[] text; @@ -42,6 +44,19 @@ public void write( String text, int start ) } } + public void write( ByteBuffer text, int start ) + { + int pos = start; + start = Math.max( start, 0 ); + int length = text.remaining(); + int end = Math.min( start + length, pos + length ); + end = Math.min( end, this.text.length ); + for( int i = start; i < end; i++ ) + { + this.text[i] = (char) (text.get( i - pos ) & 0xFF); + } + } + public void write( TextBuffer text ) { int end = Math.min( text.length(), this.text.length ); diff --git a/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java b/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java index a76471a34..8d1fe8acc 100644 --- a/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java +++ b/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java @@ -57,7 +57,8 @@ public static void register() { ManagementFactory.getPlatformMBeanServer().registerMBean( instance = new ComputerMBean(), new ObjectName( "dan200.computercraft:type=Computers" ) ); } - catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e ) + catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | + MalformedObjectNameException e ) { ComputerCraft.log.warn( "Failed to register JMX bean", e ); } diff --git a/src/main/java/dan200/computercraft/shared/CommonHooks.java b/src/main/java/dan200/computercraft/shared/CommonHooks.java index 842a8af3d..864772eba 100644 --- a/src/main/java/dan200/computercraft/shared/CommonHooks.java +++ b/src/main/java/dan200/computercraft/shared/CommonHooks.java @@ -29,7 +29,6 @@ import net.minecraftforge.event.entity.player.PlayerContainerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.server.FMLServerStartedEvent; import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppedEvent; @@ -90,22 +89,21 @@ public static void onServerStarting( FMLServerStartingEvent event ) { ComputerMBean.register(); } - } - @SubscribeEvent - public static void onServerStarted( FMLServerStartedEvent event ) - { - ComputerCraft.serverComputerRegistry.reset(); - WirelessNetwork.resetNetworks(); - Tracking.reset(); + resetState(); ComputerMBean.registerTracker(); - NetworkUtils.reset(); } @SubscribeEvent public static void onServerStopped( FMLServerStoppedEvent event ) + { + resetState(); + } + + private static void resetState() { ComputerCraft.serverComputerRegistry.reset(); + MainThread.reset(); WirelessNetwork.resetNetworks(); Tracking.reset(); NetworkUtils.reset(); diff --git a/src/main/java/dan200/computercraft/shared/Config.java b/src/main/java/dan200/computercraft/shared/Config.java index ff62e13a4..246bf7a21 100644 --- a/src/main/java/dan200/computercraft/shared/Config.java +++ b/src/main/java/dan200/computercraft/shared/Config.java @@ -98,12 +98,12 @@ private Config() {} { // General computers computerSpaceLimit = builder - .comment( "The disk space limit for computers and turtles, in bytes" ) + .comment( "The disk space limit for computers and turtles, in bytes." ) .translation( TRANSLATION_PREFIX + "computer_space_limit" ) .define( "computer_space_limit", ComputerCraft.computerSpaceLimit ); floppySpaceLimit = builder - .comment( "The disk space limit for floppy disks, in bytes" ) + .comment( "The disk space limit for floppy disks, in bytes." ) .translation( TRANSLATION_PREFIX + "floppy_space_limit" ) .define( "floppy_space_limit", ComputerCraft.floppySpaceLimit ); @@ -113,49 +113,36 @@ private Config() {} .defineInRange( "maximum_open_files", ComputerCraft.maximumFilesOpen, 0, Integer.MAX_VALUE ); disableLua51Features = builder - .comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future update. " + - "Useful for ensuring forward compatibility of your programs now." ) + .comment( "Set this to true to disable Lua 5.1 functions that will be removed in a future\nupdate. Useful for ensuring forward compatibility of your programs now." ) .define( "disable_lua51_features", ComputerCraft.disableLua51Features ); defaultComputerSettings = builder - .comment( "A comma separated list of default system settings to set on new computers. Example: " + - "\"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all " + - "autocompletion" ) + .comment( "A comma separated list of default system settings to set on new computers.\nExample: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwill disable all autocompletion." ) .define( "default_computer_settings", ComputerCraft.defaultComputerSettings ); logComputerErrors = builder - .comment( "Log exceptions thrown by peripherals and other Lua objects.\n" + - "This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." ) + .comment( "Log exceptions thrown by peripherals and other Lua objects. This makes it easier\nfor mod authors to debug problems, but may result in log spam should people use\nbuggy methods." ) .define( "log_computer_errors", ComputerCraft.logComputerErrors ); commandRequireCreative = builder - .comment( "Require players to be in creative mode and be opped in order to interact with command computers." + - "This is the default behaviour for vanilla's Command blocks." ) - .define( "command_require_creative", ComputerCraft.commandRequireCreative ); + .comment( "Require players to be in creative mode and be opped in order to interact with\ncommand computers. This is the default behaviour for vanilla's Command blocks." ).define( "command_require_creative", ComputerCraft.commandRequireCreative ); } { - builder.comment( "Controls execution behaviour of computers. This is largely intended for fine-tuning " + - "servers, and generally shouldn't need to be touched" ); + builder.comment( "Controls execution behaviour of computers. This is largely intended for\nfine-tuning servers, and generally shouldn't need to be touched." ); builder.push( "execution" ); computerThreads = builder - .comment( "Set the number of threads computers can run on. A higher number means more computers can run " + - "at once, but may induce lag.\n" + - "Please note that some mods may not work with a thread count higher than 1. Use with caution." ) + .comment( "Set the number of threads computers can run on. A higher number means more\ncomputers can run at once, but may induce lag. Please note that some mods may\nnot work with a thread count higher than 1. Use with caution." ) .worldRestart() .defineInRange( "computer_threads", ComputerCraft.computerThreads, 1, Integer.MAX_VALUE ); maxMainGlobalTime = builder - .comment( "The maximum time that can be spent executing tasks in a single tick, in milliseconds.\n" + - "Note, we will quite possibly go over this limit, as there's no way to tell how long a will take " + - "- this aims to be the upper bound of the average time." ) + .comment( "The maximum time that can be spent executing tasks in a single tick, in\nmilliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time." ) .defineInRange( "max_main_global_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainGlobalTime ), 1, Integer.MAX_VALUE ); maxMainComputerTime = builder - .comment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\n" + - "Note, we will quite possibly go over this limit, as there's no way to tell how long a will take " + - "- this aims to be the upper bound of the average time." ) + .comment( "The ideal maximum time a computer can execute for in a tick, in milliseconds.\nNote, we will quite possibly go over this limit, as there's no way to tell how\nlong a will take - this aims to be the upper bound of the average time." ) .defineInRange( "max_main_computer_time", (int) TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainComputerTime ), 1, Integer.MAX_VALUE ); builder.pop(); @@ -174,17 +161,14 @@ private Config() {} .define( "websocket_enabled", ComputerCraft.httpWebsocketEnabled ); httpRules = builder - .comment( "A list of rules which control behaviour of the \"http\" API for specific domains or IPs.\n" + - "Each rule is an item with a 'host' to match against, and a series of properties. " + - "The host may be a domain name (\"pastebin.com\"),\n" + - "wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). If no rules, the domain is blocked." ) + .comment( "A list of rules which control behaviour of the \"http\" API for specific domains or\nIPs. Each rule is an item with a 'host' to match against, and a series of\nproperties. Rules are evaluated in order, meaning earlier rules override later\nones.\nThe host may be a domain name (\"pastebin.com\"), wildcard (\"*.pastebin.com\") or\nCIDR notation (\"127.0.0.0/8\").\nIf no rules, the domain is blocked." ) .defineList( "rules", Arrays.asList( AddressRuleConfig.makeRule( "$private", Action.DENY ), AddressRuleConfig.makeRule( "*", Action.ALLOW ) ), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule( (UnmodifiableConfig) x ) ); httpMaxRequests = builder - .comment( "The number of http requests a computer can make at one time. Additional requests will be queued, and sent when the running requests have finished. Set to 0 for unlimited." ) + .comment( "The number of http requests a computer can make at one time. Additional requests\nwill be queued, and sent when the running requests have finished. Set to 0 for\nunlimited." ) .defineInRange( "max_requests", ComputerCraft.httpMaxRequests, 0, Integer.MAX_VALUE ); httpMaxWebsockets = builder @@ -192,15 +176,15 @@ private Config() {} .defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE ); builder - .comment( "Limits bandwidth used by computers" ) + .comment( "Limits bandwidth used by computers." ) .push( "bandwidth" ); httpDownloadBandwidth = builder - .comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)" ) + .comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)." ) .defineInRange( "global_download", ComputerCraft.httpDownloadBandwidth, 1, Integer.MAX_VALUE ); httpUploadBandwidth = builder - .comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)" ) + .comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)." ) .defineInRange( "global_upload", ComputerCraft.httpUploadBandwidth, 1, Integer.MAX_VALUE ); builder.pop(); @@ -217,33 +201,27 @@ private Config() {} .define( "command_block_enabled", ComputerCraft.enableCommandBlock ); modemRange = builder - .comment( "The range of Wireless Modems at low altitude in clear weather, in meters" ) + .comment( "The range of Wireless Modems at low altitude in clear weather, in meters." ) .defineInRange( "modem_range", ComputerCraft.modemRange, 0, MODEM_MAX_RANGE ); modemHighAltitudeRange = builder - .comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters" ) + .comment( "The range of Wireless Modems at maximum altitude in clear weather, in meters." ) .defineInRange( "modem_high_altitude_range", ComputerCraft.modemHighAltitudeRange, 0, MODEM_MAX_RANGE ); modemRangeDuringStorm = builder - .comment( "The range of Wireless Modems at low altitude in stormy weather, in meters" ) + .comment( "The range of Wireless Modems at low altitude in stormy weather, in meters." ) .defineInRange( "modem_range_during_storm", ComputerCraft.modemRangeDuringStorm, 0, MODEM_MAX_RANGE ); modemHighAltitudeRangeDuringStorm = builder - .comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters" ) + .comment( "The range of Wireless Modems at maximum altitude in stormy weather, in meters." ) .defineInRange( "modem_high_altitude_range_during_storm", ComputerCraft.modemHighAltitudeRangeDuringStorm, 0, MODEM_MAX_RANGE ); maxNotesPerTick = builder - .comment( "Maximum amount of notes a speaker can play at once" ) + .comment( "Maximum amount of notes a speaker can play at once." ) .defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE ); monitorBandwidth = builder - .comment( "The limit to how much monitor data can be sent *per tick*. Note:\n" + - " - Bandwidth is measured before compression, so the data sent to the client is smaller.\n" + - " - This ignores the number of players a packet is sent to. Updating a monitor for one player consumes " + - "the same bandwidth limit as sending to 20.\n" + - " - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40 monitors to be updated " + - "in a single tick. \n" + - "Set to 0 to disable." ) + .comment( "The limit to how much monitor data can be sent *per tick*. Note:\n - Bandwidth is measured before compression, so the data sent to the client is\n smaller.\n - This ignores the number of players a packet is sent to. Updating a monitor for\n one player consumes the same bandwidth limit as sending to 20.\n - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40\n monitors to be updated in a single tick.\nSet to 0 to disable." ) .defineInRange( "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth, 0, Integer.MAX_VALUE ); builder.pop(); @@ -254,23 +232,24 @@ private Config() {} builder.push( "turtle" ); turtlesNeedFuel = builder - .comment( "Set whether Turtles require fuel to move" ) + .comment( "Set whether Turtles require fuel to move." ) .define( "need_fuel", ComputerCraft.turtlesNeedFuel ); turtleFuelLimit = builder - .comment( "The fuel limit for Turtles" ) + .comment( "The fuel limit for Turtles." ) .defineInRange( "normal_fuel_limit", ComputerCraft.turtleFuelLimit, 0, Integer.MAX_VALUE ); advancedTurtleFuelLimit = builder - .comment( "The fuel limit for Advanced Turtles" ) + .comment( "The fuel limit for Advanced Turtles." ) .defineInRange( "advanced_fuel_limit", ComputerCraft.advancedTurtleFuelLimit, 0, Integer.MAX_VALUE ); turtlesObeyBlockProtection = builder - .comment( "If set to true, Turtles will be unable to build, dig, or enter protected areas (such as near the server spawn point)" ) + .comment( "If set to true, Turtles will be unable to build, dig, or enter protected areas\n(such as near the server spawn point)." ) .define( "obey_block_protection", ComputerCraft.turtlesObeyBlockProtection ); turtlesCanPush = builder - .comment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" ) + .comment( "If set to true, Turtles will push entities out of the way instead of stopping if\n" + + "there is space to do so." ) .define( "can_push", ComputerCraft.turtlesCanPush ); turtleDisabledActions = builder @@ -281,20 +260,19 @@ private Config() {} } { - builder.comment( "Configure the size of various computer's terminals.\n" + - "Larger terminals require more bandwidth, so use with care." ).push( "term_sizes" ); + builder.comment( "Configure the size of various computer's terminals.\nLarger terminals require more bandwidth, so use with care." ).push( "term_sizes" ); - builder.comment( "Terminal size of computers" ).push( "computer" ); + builder.comment( "Terminal size of computers." ).push( "computer" ); computerTermWidth = builder.defineInRange( "width", ComputerCraft.computerTermWidth, 1, 255 ); computerTermHeight = builder.defineInRange( "height", ComputerCraft.computerTermHeight, 1, 255 ); builder.pop(); - builder.comment( "Terminal size of pocket computers" ).push( "pocket_computer" ); + builder.comment( "Terminal size of pocket computers." ).push( "pocket_computer" ); pocketTermWidth = builder.defineInRange( "width", ComputerCraft.pocketTermWidth, 1, 255 ); pocketTermHeight = builder.defineInRange( "height", ComputerCraft.pocketTermHeight, 1, 255 ); builder.pop(); - builder.comment( "Maximum size of monitors (in blocks)" ).push( "monitor" ); + builder.comment( "Maximum size of monitors (in blocks)." ).push( "monitor" ); monitorWidth = builder.defineInRange( "width", ComputerCraft.monitorWidth, 1, 32 ); monitorHeight = builder.defineInRange( "height", ComputerCraft.monitorHeight, 1, 32 ); builder.pop(); @@ -306,12 +284,10 @@ private Config() {} Builder clientBuilder = new Builder(); monitorRenderer = clientBuilder - .comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if " + - "monitors have performance issues, you may wish to experiment with alternative renderers." ) + .comment( "The renderer to use for monitors. Generally this should be kept at \"best\" - if\nmonitors have performance issues, you may wish to experiment with alternative\nrenderers." ) .defineEnum( "monitor_renderer", MonitorRenderer.BEST ); monitorDistance = clientBuilder - .comment( "The maximum distance monitors will render at. This defaults to the standard tile entity limit, " + - "but may be extended if you wish to build larger monitors." ) + .comment( "The maximum distance monitors will render at. This defaults to the standard tile\nentity limit, but may be extended if you wish to build larger monitors." ) .defineInRange( "monitor_distance", 64, 16, 1024 ); clientSpec = clientBuilder.build(); } diff --git a/src/main/java/dan200/computercraft/shared/Registry.java b/src/main/java/dan200/computercraft/shared/Registry.java index 3d838fa58..cf141a0a6 100644 --- a/src/main/java/dan200/computercraft/shared/Registry.java +++ b/src/main/java/dan200/computercraft/shared/Registry.java @@ -82,7 +82,6 @@ import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; -import net.minecraftforge.fml.DeferredWorkQueue; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.common.Mod; @@ -339,12 +338,11 @@ public static void registerRecipeSerializers( RegistryEvent.Register { + event.enqueueWork( () -> { registerProviders(); ArgumentSerializers.register(); registerLoot(); diff --git a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java index f26284228..70e000046 100644 --- a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java +++ b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java @@ -141,9 +141,10 @@ else if( b.getWorld() == world ) .then( command( "shutdown" ) .requires( UserLevel.OWNER_OP ) .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) - .executes( ( context, computers ) -> { + .executes( ( context, computerSelectors ) -> { int shutdown = 0; - for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + Set computers = unwrap( context.getSource(), computerSelectors ); + for( ServerComputer computer : computers ) { if( computer.isOn() ) shutdown++; computer.shutdown(); @@ -155,9 +156,10 @@ else if( b.getWorld() == world ) .then( command( "turn-on" ) .requires( UserLevel.OWNER_OP ) .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) - .executes( ( context, computers ) -> { + .executes( ( context, computerSelectors ) -> { int on = 0; - for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + Set computers = unwrap( context.getSource(), computerSelectors ); + for( ServerComputer computer : computers ) { if( !computer.isOn() ) on++; computer.turnOn(); diff --git a/src/main/java/dan200/computercraft/shared/common/TileGeneric.java b/src/main/java/dan200/computercraft/shared/common/TileGeneric.java index 86dd17504..cc24dd1c6 100644 --- a/src/main/java/dan200/computercraft/shared/common/TileGeneric.java +++ b/src/main/java/dan200/computercraft/shared/common/TileGeneric.java @@ -19,9 +19,17 @@ import net.minecraftforge.common.util.Constants; import javax.annotation.Nonnull; +import java.util.concurrent.atomic.AtomicBoolean; public abstract class TileGeneric extends TileEntity { + /** + * Is this block enqueued to be updated next tick? This should only be read/written by the tick scheduler. + * + * @see dan200.computercraft.shared.util.TickScheduler + */ + public final AtomicBoolean scheduled = new AtomicBoolean(); + public TileGeneric( TileEntityType type ) { super( type ); @@ -62,10 +70,9 @@ protected double getInteractRange( PlayerEntity player ) return 8.0; } - public boolean isUsable( PlayerEntity player, boolean ignoreRange ) + public boolean isUsable( PlayerEntity player ) { if( player == null || !player.isAlive() || getLevel().getBlockEntity( getBlockPos() ) != this ) return false; - if( ignoreRange ) return true; double range = getInteractRange( player ); BlockPos pos = getBlockPos(); diff --git a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index af891135a..0c3e19697 100644 --- a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -8,11 +8,11 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.detail.BlockReference; import dan200.computercraft.api.lua.*; import dan200.computercraft.shared.computer.blocks.TileCommandComputer; import dan200.computercraft.shared.peripheral.generic.data.BlockData; import dan200.computercraft.shared.util.NBTUtil; -import net.minecraft.block.BlockState; import net.minecraft.command.CommandSource; import net.minecraft.command.Commands; import net.minecraft.nbt.CompoundNBT; @@ -77,10 +77,10 @@ private Object[] doCommand( String command ) private static Map getBlockInfo( World world, BlockPos pos ) { // Get the details of the block - BlockState state = world.getBlockState( pos ); - Map table = BlockData.fill( new HashMap<>(), state ); + BlockReference block = new BlockReference( world, pos ); + Map table = BlockData.fill( new HashMap<>(), block ); - TileEntity tile = world.getBlockEntity( pos ); + TileEntity tile = block.getBlockEntity(); if( tile != null ) table.put( "nbt", NBTUtil.toLua( tile.save( new CompoundNBT() ) ) ); return table; diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java index f26f15c78..61bdc76aa 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java @@ -18,6 +18,8 @@ import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraftforge.fml.RegistryObject; import javax.annotation.Nonnull; @@ -50,6 +52,22 @@ public BlockState getStateForPlacement( BlockItemUseContext placement ) return defaultBlockState().setValue( FACING, placement.getHorizontalDirection().getOpposite() ); } + @Nonnull + @Override + @Deprecated + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + @Deprecated + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } + @Nonnull @Override protected ItemStack getItem( TileComputerBase tile ) diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java index 8f569fbed..d7e21dfdb 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java @@ -54,7 +54,7 @@ public void onPlace( @Nonnull BlockState state, @Nonnull World world, @Nonnull B super.onPlace( state, world, pos, oldState, isMoving ); TileEntity tile = world.getBlockEntity( pos ); - if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInputsImmediately( ); + if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInputsImmediately(); } @Override diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java index 5e04cbd03..4fd1664a1 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java @@ -97,7 +97,7 @@ public CommandSource getSource() } return new CommandSource( receiver, - new Vector3d( worldPosition.getX() + 0.5, worldPosition.getY() + 0.5, worldPosition.getZ() + 0.5 ), Vector2f.ZERO, + Vector3d.atCenterOf( worldPosition ), Vector2f.ZERO, (ServerWorld) getLevel(), 2, name, new StringTextComponent( name ), getLevel().getServer(), null @@ -113,12 +113,12 @@ protected ServerComputer createComputer( int instanceID, int id ) } @Override - public boolean isUsable( PlayerEntity player, boolean ignoreRange ) + public boolean isUsable( PlayerEntity player ) { - return isUsable( player ) && super.isUsable( player, ignoreRange ); + return isCommandUsable( player ) && super.isUsable( player ); } - public static boolean isUsable( PlayerEntity player ) + public static boolean isCommandUsable( PlayerEntity player ) { MinecraftServer server = player.getServer(); if( server == null || !server.isCommandBlockEnabled() ) diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputer.java index 8859bf8ca..2ef8b8ea1 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputer.java @@ -53,7 +53,7 @@ protected ServerComputer createComputer( int instanceID, int id ) protected boolean isUsableByPlayer( PlayerEntity player ) { - return isUsable( player, false ); + return isUsable( player ); } @Override diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java index 400fab60e..88bad785a 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java @@ -26,6 +26,7 @@ import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.play.server.SUpdateTileEntityPacket; import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.tileentity.LockableTileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.ActionResultType; import net.minecraft.util.Direction; @@ -36,6 +37,7 @@ import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.LockCode; import net.minecraftforge.common.util.NonNullConsumer; import javax.annotation.Nonnull; @@ -58,6 +60,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT private int invalidSides = 0; private final NonNullConsumer[] invalidate; + private LockCode lockCode = LockCode.NO_LOCK; + private final ComputerFamily family; public TileComputerBase( TileEntityType type, ComputerFamily family ) @@ -112,6 +116,12 @@ protected boolean canNameWithTag( PlayerEntity player ) return false; } + @Override + public boolean isUsable( PlayerEntity player ) + { + return super.isUsable( player ) && LockableTileEntity.canUnlock( player, lockCode, getDisplayName() ); + } + @Nonnull @Override public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTraceResult hit ) @@ -130,7 +140,7 @@ public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTrac else if( !player.isCrouching() ) { // Regular right click to activate computer - if( !getLevel().isClientSide && isUsable( player, false ) ) + if( !getLevel().isClientSide && isUsable( player ) ) { createServerComputer().turnOn(); new ComputerContainerData( createServerComputer() ).open( player, this ); @@ -201,6 +211,8 @@ public CompoundNBT save( @Nonnull CompoundNBT nbt ) if( label != null ) nbt.putString( NBT_LABEL, label ); nbt.putBoolean( NBT_ON, on ); + lockCode.addToTag( nbt ); + return super.save( nbt ); } @@ -213,6 +225,8 @@ public void load( @Nonnull BlockState state, @Nonnull CompoundNBT nbt ) computerID = nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; label = nbt.contains( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; on = startOn = nbt.getBoolean( NBT_ON ); + + lockCode = LockCode.fromTag( nbt ); } protected boolean isPeripheralBlockedOnSide( ComputerSide localSide ) diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java index a863b7305..5bdb9060d 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java @@ -43,7 +43,7 @@ private static boolean canInteractWith( @Nonnull ServerComputer computer, @Nonnu } // If we're a command computer then ensure we're in creative - if( computer.getFamily() == ComputerFamily.COMMAND && !TileCommandComputer.isUsable( player ) ) + if( computer.getFamily() == ComputerFamily.COMMAND && !TileCommandComputer.isCommandUsable( player ) ) { return false; } diff --git a/src/main/java/dan200/computercraft/shared/integration/Optifine.java b/src/main/java/dan200/computercraft/shared/integration/Optifine.java new file mode 100644 index 000000000..4c5a7d432 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/integration/Optifine.java @@ -0,0 +1,39 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.integration; + +/** + * Detect whether Optifine is installed. + */ +public final class Optifine +{ + private static final boolean LOADED; + + static + { + boolean loaded; + try + { + Class.forName( "optifine.Installer", false, Optifine.class.getClassLoader() ); + loaded = true; + } + catch( ReflectiveOperationException | LinkageError ignore ) + { + loaded = false; + } + + LOADED = loaded; + } + + private Optifine() + { + } + + public static boolean isLoaded() + { + return LOADED; + } +} diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java index e28f5d23e..5dca012cc 100644 --- a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java +++ b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java @@ -23,7 +23,6 @@ import net.minecraftforge.fml.network.simple.SimpleChannel; import java.util.function.Function; -import java.util.function.Supplier; public final class NetworkHandler { @@ -111,10 +110,4 @@ private static void registerMainThread( int id, Netwo } ) .add(); } - - @SuppressWarnings( "unchecked" ) - private static Class getType( Supplier supplier ) - { - return (Class) supplier.get().getClass(); - } } diff --git a/src/main/java/dan200/computercraft/shared/network/client/SpeakerAudioClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/SpeakerAudioClientMessage.java index 6aec3ed58..57f8ba342 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/SpeakerAudioClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/SpeakerAudioClientMessage.java @@ -7,8 +7,8 @@ import dan200.computercraft.client.sound.SpeakerManager; import dan200.computercraft.shared.network.NetworkMessage; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import net.minecraft.network.PacketBuffer; -import net.minecraft.util.math.vector.Vector3d; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.network.NetworkEvent; @@ -27,14 +27,14 @@ public class SpeakerAudioClientMessage implements NetworkMessage { private final UUID source; - private final Vector3d pos; + private final SpeakerPosition.Message pos; private final ByteBuffer content; private final float volume; - public SpeakerAudioClientMessage( UUID source, Vector3d pos, float volume, ByteBuffer content ) + public SpeakerAudioClientMessage( UUID source, SpeakerPosition pos, float volume, ByteBuffer content ) { this.source = source; - this.pos = pos; + this.pos = pos.asMessage(); this.content = content; this.volume = volume; } @@ -42,7 +42,7 @@ public SpeakerAudioClientMessage( UUID source, Vector3d pos, float volume, ByteB public SpeakerAudioClientMessage( PacketBuffer buf ) { source = buf.readUUID(); - pos = new Vector3d( buf.readDouble(), buf.readDouble(), buf.readDouble() ); + pos = SpeakerPosition.Message.read( buf ); volume = buf.readFloat(); SpeakerManager.getSound( source ).pushAudio( buf ); @@ -53,9 +53,7 @@ public SpeakerAudioClientMessage( PacketBuffer buf ) public void toBytes( @Nonnull PacketBuffer buf ) { buf.writeUUID( source ); - buf.writeDouble( pos.x() ); - buf.writeDouble( pos.y() ); - buf.writeDouble( pos.z() ); + pos.write( buf ); buf.writeFloat( volume ); buf.writeBytes( content.duplicate() ); } @@ -64,6 +62,6 @@ public void toBytes( @Nonnull PacketBuffer buf ) @OnlyIn( Dist.CLIENT ) public void handle( NetworkEvent.Context context ) { - SpeakerManager.getSound( source ).playAudio( pos, volume ); + SpeakerManager.getSound( source ).playAudio( pos.reify(), volume ); } } diff --git a/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java index 9082d2457..2fc17a053 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java @@ -7,8 +7,8 @@ import dan200.computercraft.client.sound.SpeakerManager; import dan200.computercraft.shared.network.NetworkMessage; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import net.minecraft.network.PacketBuffer; -import net.minecraft.util.math.vector.Vector3d; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.network.NetworkEvent; @@ -26,33 +26,31 @@ public class SpeakerMoveClientMessage implements NetworkMessage { private final UUID source; - private final Vector3d pos; + private final SpeakerPosition.Message pos; - public SpeakerMoveClientMessage( UUID source, Vector3d pos ) + public SpeakerMoveClientMessage( UUID source, SpeakerPosition pos ) { this.source = source; - this.pos = pos; + this.pos = pos.asMessage(); } public SpeakerMoveClientMessage( PacketBuffer buf ) { source = buf.readUUID(); - pos = new Vector3d( buf.readDouble(), buf.readDouble(), buf.readDouble() ); + pos = SpeakerPosition.Message.read( buf ); } @Override public void toBytes( @Nonnull PacketBuffer buf ) { buf.writeUUID( source ); - buf.writeDouble( pos.x() ); - buf.writeDouble( pos.y() ); - buf.writeDouble( pos.z() ); + pos.write( buf ); } @Override @OnlyIn( Dist.CLIENT ) public void handle( NetworkEvent.Context context ) { - SpeakerManager.moveSound( source, pos ); + SpeakerManager.moveSound( source, pos.reify() ); } } diff --git a/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java index 419bb6ae4..692eb7733 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java @@ -7,9 +7,9 @@ import dan200.computercraft.client.sound.SpeakerManager; import dan200.computercraft.shared.network.NetworkMessage; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.vector.Vector3d; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.network.NetworkEvent; @@ -27,15 +27,15 @@ public class SpeakerPlayClientMessage implements NetworkMessage { private final UUID source; - private final Vector3d pos; + private final SpeakerPosition.Message pos; private final ResourceLocation sound; private final float volume; private final float pitch; - public SpeakerPlayClientMessage( UUID source, Vector3d pos, ResourceLocation event, float volume, float pitch ) + public SpeakerPlayClientMessage( UUID source, SpeakerPosition pos, ResourceLocation event, float volume, float pitch ) { this.source = source; - this.pos = pos; + this.pos = pos.asMessage(); sound = event; this.volume = volume; this.pitch = pitch; @@ -44,7 +44,7 @@ public SpeakerPlayClientMessage( UUID source, Vector3d pos, ResourceLocation eve public SpeakerPlayClientMessage( PacketBuffer buf ) { source = buf.readUUID(); - pos = new Vector3d( buf.readDouble(), buf.readDouble(), buf.readDouble() ); + pos = SpeakerPosition.Message.read( buf ); sound = buf.readResourceLocation(); volume = buf.readFloat(); pitch = buf.readFloat(); @@ -54,9 +54,7 @@ public SpeakerPlayClientMessage( PacketBuffer buf ) public void toBytes( @Nonnull PacketBuffer buf ) { buf.writeUUID( source ); - buf.writeDouble( pos.x() ); - buf.writeDouble( pos.y() ); - buf.writeDouble( pos.z() ); + pos.write( buf ); buf.writeResourceLocation( sound ); buf.writeFloat( volume ); buf.writeFloat( pitch ); @@ -66,6 +64,6 @@ public void toBytes( @Nonnull PacketBuffer buf ) @OnlyIn( Dist.CLIENT ) public void handle( NetworkEvent.Context context ) { - SpeakerManager.getSound( source ).playSound( pos, sound, volume, pitch ); + SpeakerManager.getSound( source ).playSound( pos.reify(), sound, volume, pitch ); } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java index 42f1b57f6..e426bdcad 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java @@ -21,6 +21,8 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; import net.minecraft.util.INameable; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -47,6 +49,22 @@ protected void createBlockStateDefinition( StateContainer.Builder computers = new HashMap<>(); @@ -92,6 +95,12 @@ protected void invalidateCaps() peripheralCap = CapabilityUtil.invalidate( peripheralCap ); } + @Override + public boolean isUsable( PlayerEntity player ) + { + return super.isUsable( player ) && LockableTileEntity.canUnlock( player, lockCode, getDisplayName() ); + } + @Nonnull @Override public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTraceResult hit ) @@ -111,7 +120,10 @@ public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTrac else { // Open the GUI - if( !getLevel().isClientSide ) NetworkHooks.openGui( (ServerPlayerEntity) player, this ); + if( !getLevel().isClientSide && isUsable( player ) ) + { + NetworkHooks.openGui( (ServerPlayerEntity) player, this ); + } return ActionResultType.SUCCESS; } } @@ -132,6 +144,8 @@ public void load( @Nonnull BlockState state, @Nonnull CompoundNBT nbt ) diskStack = ItemStack.of( item ); diskMount = null; } + + lockCode = LockCode.fromTag( nbt ); } @Nonnull @@ -146,6 +160,9 @@ public CompoundNBT save( @Nonnull CompoundNBT nbt ) diskStack.save( item ); nbt.put( NBT_ITEM, item ); } + + lockCode.addToTag( nbt ); + return super.save( nbt ); } @@ -297,7 +314,7 @@ public void setChanged() @Override public boolean stillValid( @Nonnull PlayerEntity player ) { - return isUsable( player, false ); + return isUsable( player ); } @Override diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java index 8f6e3fba1..dde7ecbb1 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/BlockData.java @@ -5,6 +5,7 @@ */ package dan200.computercraft.shared.peripheral.generic.data; +import dan200.computercraft.api.detail.BlockReference; import net.minecraft.block.BlockState; import net.minecraft.state.Property; @@ -15,8 +16,10 @@ public class BlockData { @Nonnull - public static > T fill( @Nonnull T data, @Nonnull BlockState state ) + public static > T fill( @Nonnull T data, @Nonnull BlockReference block ) { + BlockState state = block.getState(); + data.put( "name", DataHelpers.getId( state.getBlock() ) ); Map stateTable = new HashMap<>(); @@ -28,6 +31,8 @@ public static > T fill( @Nonnull T data, @ data.put( "state", stateTable ); data.put( "tags", DataHelpers.getTags( state.getBlock().getTags() ) ); + DetailProviders.fillData( BlockReference.class, data, block ); + return data; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DetailProviders.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DetailProviders.java new file mode 100644 index 000000000..313eecc18 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/DetailProviders.java @@ -0,0 +1,43 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.peripheral.generic.data; + +import dan200.computercraft.api.detail.BlockReference; +import dan200.computercraft.api.detail.IDetailProvider; +import net.minecraft.item.ItemStack; +import net.minecraftforge.fluids.FluidStack; + +import java.util.*; + +public final class DetailProviders +{ + private static final Map, Collection>> allProviders = new HashMap<>(); + + public static synchronized void registerProvider( Class type, IDetailProvider provider ) + { + Objects.requireNonNull( type, "type cannot be null" ); + Objects.requireNonNull( provider, "provider cannot be null" ); + + if ( type != BlockReference.class && type != ItemStack.class && type != FluidStack.class ) + { + throw new IllegalArgumentException( "type must be assignable from BlockReference, ItemStack or FluidStack" ); + } + + allProviders.computeIfAbsent( type, k -> new LinkedHashSet<>() ).add( provider ); + } + + @SuppressWarnings( "unchecked" ) + public static void fillData( Class type, Map data, T value ) + { + Collection> providers = (Collection>) (Object) allProviders.get( type ); + if ( providers == null ) return; + + for ( IDetailProvider provider : providers ) + { + provider.provideDetails( data, value ); + } + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java index 6c18aa6e3..0bc067179 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/FluidData.java @@ -25,6 +25,9 @@ public static > T fill( @Nonnull T data, @ { fillBasic( data, stack ); data.put( "tags", DataHelpers.getTags( stack.getFluid().getTags() ) ); + + DetailProviders.fillData( FluidStack.class, data, stack ); + return data; } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java index fbf723c7c..d39deec4a 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/generic/data/ItemData.java @@ -98,6 +98,8 @@ public static > T fill( @Nonnull T data, @ data.put( "unbreakable", true ); } + DetailProviders.fillData( ItemStack.class, data, stack ); + return data; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java index 9e769a216..e7d34fbb1 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java @@ -60,8 +60,7 @@ public World getWorld() @Override public Vector3d getPosition() { - BlockPos pos = getBlockPos(); - return new Vector3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + return Vector3d.atCenterOf( getBlockPos() ); } @Override @@ -106,8 +105,7 @@ protected WiredModemLocalPeripheral getLocalPeripheral() @Override public Vector3d getPosition() { - BlockPos pos = getBlockPos().relative( modemDirection ); - return new Vector3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + return Vector3d.atCenterOf( getBlockPos().relative( modemDirection ) ); } @Nonnull @@ -264,7 +262,7 @@ private void refreshPeripheral() @Override public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTraceResult hit ) { - if( player.isCrouching() ) return ActionResultType.PASS; + if( player.isCrouching() || !player.mayBuild() ) return ActionResultType.PASS; if( !canAttachPeripheral() ) return ActionResultType.FAIL; if( getLevel().isClientSide ) return ActionResultType.SUCCESS; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java index d4dd83701..34963cc1b 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java @@ -87,8 +87,7 @@ public World getWorld() @Override public Vector3d getPosition() { - BlockPos pos = entity.getBlockPos(); - return new Vector3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + return Vector3d.atCenterOf( entity.getBlockPos() ); } } @@ -200,6 +199,7 @@ private void refreshPeripheral( @Nonnull Direction facing ) @Override public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTraceResult hit ) { + if( player.isCrouching() || !player.mayBuild() ) return ActionResultType.PASS; if( getLevel().isClientSide ) return ActionResultType.SUCCESS; // On server, we interacted if a peripheral was found @@ -418,8 +418,7 @@ protected WiredModemLocalPeripheral getLocalPeripheral() @Override public Vector3d getPosition() { - BlockPos pos = getBlockPos().relative( side ); - return new Vector3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + return Vector3d.atCenterOf( getBlockPos().relative( side ) ); } @Nonnull diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java index 599bd2791..6b87f7c3a 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java @@ -18,6 +18,8 @@ import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.shapes.ISelectionContext; import net.minecraft.util.math.shapes.VoxelShape; @@ -94,4 +96,20 @@ public BlockState getStateForPlacement( BlockItemUseContext placement ) .setValue( FACING, placement.getClickedFace().getOpposite() ) .setValue( WATERLOGGED, getWaterloggedStateForPlacement( placement ) ); } + + @Nonnull + @Override + @Deprecated + public BlockState mirror( BlockState state, Mirror mirrorIn ) + { + return state.rotate( mirrorIn.getRotation( state.getValue( FACING ) ) ); + } + + @Nonnull + @Override + @Deprecated + public BlockState rotate( BlockState state, Rotation rot ) + { + return state.setValue( FACING, rot.rotate( state.getValue( FACING ) ) ); + } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java index 700543f47..f5b35696f 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java @@ -14,7 +14,6 @@ import net.minecraft.block.BlockState; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.world.World; import net.minecraftforge.common.capabilities.Capability; @@ -48,8 +47,7 @@ public World getWorld() @Override public Vector3d getPosition() { - BlockPos pos = entity.getBlockPos().relative( entity.modemDirection ); - return new Vector3d( pos.getX(), pos.getY(), pos.getZ() ); + return Vector3d.atLowerCornerOf( entity.getBlockPos().relative( entity.modemDirection ) ); } @Override diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java index 36690e94a..ae77ad488 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java @@ -18,6 +18,8 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.common.util.FakePlayer; @@ -51,6 +53,22 @@ protected void createBlockStateDefinition( StateContainer.Builder type, boolean advanced ) { super( type ); @@ -624,9 +629,25 @@ void removeComputer( IComputerAccess computer ) @Override public AxisAlignedBB getRenderBoundingBox() { + // We attempt to cache the bounding box to save having to do property lookups (and allocations!) on every frame. + // Unfortunately the AABB does depend on quite a lot of state, so we need to add a bunch of extra fields - + // ideally these'd be a single object, but I don't think worth doing until Java has value types. + if( boundingBox != null && getBlockState().equals( bbState ) && getBlockPos().equals( bbPos ) && + xIndex == bbX && yIndex == bbY && width == bbWidth && height == bbHeight ) + { + return boundingBox; + } + + bbState = getBlockState(); + bbPos = getBlockPos(); + bbX = xIndex; + bbY = yIndex; + bbWidth = width; + bbHeight = height; + BlockPos startPos = toWorldPos( 0, 0 ); BlockPos endPos = toWorldPos( width, height ); - return new AxisAlignedBB( + return boundingBox = new AxisAlignedBB( Math.min( startPos.getX(), endPos.getX() ), Math.min( startPos.getY(), endPos.getY() ), Math.min( startPos.getZ(), endPos.getZ() ), diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java index a47a8cb77..a03611816 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java @@ -21,6 +21,8 @@ import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; import net.minecraft.util.INameable; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -48,6 +50,22 @@ protected void createBlockStateDefinition( StateContainer.Builder false ) ); // Player inv for( int y = 0; y < 3; y++ ) diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java index 3136c0975..1c03fb766 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java @@ -22,12 +22,14 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.LockableTileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.*; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.math.vector.Vector3d; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.LockCode; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fml.network.NetworkHooks; @@ -54,6 +56,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent private static final int[] SIDE_SLOTS = new int[] { 0 }; ITextComponent customName; + private LockCode lockCode; private final NonNullList inventory = NonNullList.withSize( SLOTS, ItemStack.EMPTY ); private final SidedCaps itemHandlerCaps = @@ -83,13 +86,22 @@ protected void invalidateCaps() peripheralCap = CapabilityUtil.invalidate( peripheralCap ); } + @Override + public boolean isUsable( PlayerEntity player ) + { + return super.isUsable( player ) && LockableTileEntity.canUnlock( player, lockCode, getDisplayName() ); + } + @Nonnull @Override public ActionResultType onActivate( PlayerEntity player, Hand hand, BlockRayTraceResult hit ) { if( player.isCrouching() ) return ActionResultType.PASS; - if( !getLevel().isClientSide ) NetworkHooks.openGui( (ServerPlayerEntity) player, this ); + if( !getLevel().isClientSide && isUsable( player ) ) + { + NetworkHooks.openGui( (ServerPlayerEntity) player, this ); + } return ActionResultType.SUCCESS; } @@ -110,6 +122,8 @@ public void load( @Nonnull BlockState state, @Nonnull CompoundNBT nbt ) // Read inventory ItemStackHelper.loadAllItems( nbt, inventory ); + + lockCode = LockCode.fromTag( nbt ); } @Nonnull @@ -129,6 +143,8 @@ public CompoundNBT save( @Nonnull CompoundNBT nbt ) // Write inventory ItemStackHelper.saveAllItems( nbt, inventory ); + lockCode.addToTag( nbt ); + return super.save( nbt ); } @@ -231,7 +247,7 @@ else if( slot >= TOP_SLOTS[0] && slot <= TOP_SLOTS[TOP_SLOTS.length - 1] ) @Override public boolean stillValid( @Nonnull PlayerEntity playerEntity ) { - return isUsable( playerEntity, false ); + return isUsable( playerEntity ); } // ISidedInventory implementation @@ -308,7 +324,7 @@ static boolean isInk( @Nonnull ItemStack stack ) return ColourUtils.getStackColour( stack ) != null; } - private static boolean isPaper( @Nonnull ItemStack stack ) + static boolean isPaper( @Nonnull ItemStack stack ) { Item item = stack.getItem(); return item == Items.PAPER diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java index b695ecbd6..0867b3a8b 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java @@ -14,7 +14,10 @@ import net.minecraft.state.StateContainer; import net.minecraft.state.properties.BlockStateProperties; import net.minecraft.util.Direction; +import net.minecraft.util.Mirror; +import net.minecraft.util.Rotation; +import javax.annotation.Nonnull; import javax.annotation.Nullable; public class BlockSpeaker extends BlockGeneric @@ -34,6 +37,22 @@ protected void createBlockStateDefinition( StateContainer.BuilderRecipe + * + * * @cc.module speaker * @cc.since 1.80pr1 */ @@ -57,7 +59,7 @@ public abstract class SpeakerPeripheral implements IPeripheral private long clock = 0; private long lastPositionTime; - private Vector3d lastPosition; + private SpeakerPosition lastPosition; private long lastPlayTime; @@ -72,10 +74,11 @@ public void update() { clock++; - Vector3d pos = getPosition(); - World world = getWorld(); - if( world == null ) return; - MinecraftServer server = world.getServer(); + SpeakerPosition position = getPosition(); + World level = position.level(); + Vector3d pos = position.position(); + if( level == null ) return; + MinecraftServer server = level.getServer(); synchronized( pendingNotes ) { @@ -83,7 +86,7 @@ public void update() { lastPlayTime = clock; server.getPlayerList().broadcast( - null, pos.x, pos.y, pos.z, sound.volume * 16, world.dimension(), + null, pos.x, pos.y, pos.z, sound.volume * 16, level.dimension(), new SPlaySoundPacket( sound.location, SoundCategory.RECORDS, pos, sound.volume, sound.pitch ) ); } @@ -125,20 +128,20 @@ public void update() { lastPlayTime = clock; NetworkHandler.sendToAllAround( - new SpeakerPlayClientMessage( getSource(), pos, sound.location, sound.volume, sound.pitch ), - world, pos, sound.volume * 16 + new SpeakerPlayClientMessage( getSource(), position, sound.location, sound.volume, sound.pitch ), + level, pos, sound.volume * 16 ); - syncedPosition( pos ); + syncedPosition( position ); } else if( dfpwmState != null && dfpwmState.shouldSendPending( now ) ) { // If clients need to receive another batch of audio, send it and then notify computers our internal buffer is // free again. NetworkHandler.sendToAllTracking( - new SpeakerAudioClientMessage( getSource(), pos, dfpwmState.getVolume(), dfpwmState.pullPending( now ) ), - getWorld().getChunkAt( new BlockPos( pos ) ) + new SpeakerAudioClientMessage( getSource(), position, dfpwmState.getVolume(), dfpwmState.pullPending( now ) ), + level.getChunkAt( new BlockPos( pos ) ) ); - syncedPosition( pos ); + syncedPosition( position ); // And notify computers that we have space for more audio. synchronized( computers ) @@ -153,25 +156,19 @@ else if( dfpwmState != null && dfpwmState.shouldSendPending( now ) ) // Push position updates to any speakers which have ever played a note, // have moved by a non-trivial amount and haven't had a position update // in the last second. - if( lastPosition != null && (clock - lastPositionTime) >= 20 ) + if( lastPosition != null && (clock - lastPositionTime) >= 20 && !lastPosition.withinDistance( position, 0.1 ) ) { - Vector3d position = getPosition(); - if( lastPosition.distanceToSqr( position ) >= 0.1 ) - { - NetworkHandler.sendToAllTracking( - new SpeakerMoveClientMessage( getSource(), position ), - getWorld().getChunkAt( new BlockPos( position ) ) - ); - syncedPosition( position ); - } + // TODO: What to do when entities move away? How do we notify people left behind that they're gone. + NetworkHandler.sendToAllTracking( + new SpeakerMoveClientMessage( getSource(), position ), + level.getChunkAt( new BlockPos( pos ) ) + ); + syncedPosition( position ); } } - @Nullable - public abstract World getWorld(); - @Nonnull - public abstract Vector3d getPosition(); + public abstract SpeakerPosition getPosition(); @Nonnull public UUID getSource() @@ -308,7 +305,7 @@ public final boolean playSound( ILuaContext context, String name, Optional table = BlockData.fill( new HashMap<>(), state ); + Map table = BlockData.fill( new HashMap<>(), block ); // Fire the event, exiting if it is cancelled TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction ); diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java index 1ec4b1d62..988acffd0 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java @@ -12,12 +12,11 @@ import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleUpgradeType; import dan200.computercraft.shared.Registry; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; import net.minecraft.client.renderer.model.ModelResourceLocation; import net.minecraft.util.ResourceLocation; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.vector.Vector3d; -import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -37,18 +36,11 @@ private static class Peripheral extends UpgradeSpeakerPeripheral this.turtle = turtle; } - @Override - public World getWorld() - { - return turtle.getWorld(); - } - @Nonnull @Override - public Vector3d getPosition() + public SpeakerPosition getPosition() { - BlockPos pos = turtle.getPosition(); - return new Vector3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + return SpeakerPosition.of( turtle.getWorld(), Vector3d.atCenterOf( turtle.getPosition() ) ); } @Override diff --git a/src/main/java/dan200/computercraft/shared/util/Palette.java b/src/main/java/dan200/computercraft/shared/util/Palette.java index 55e3dcd08..c176ba525 100644 --- a/src/main/java/dan200/computercraft/shared/util/Palette.java +++ b/src/main/java/dan200/computercraft/shared/util/Palette.java @@ -5,13 +5,18 @@ */ package dan200.computercraft.shared.util; +import dan200.computercraft.client.render.text.FixedWidthFontRenderer; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; +import javax.annotation.Nonnull; + public class Palette { private static final int PALETTE_SIZE = 16; private final double[][] colours = new double[PALETTE_SIZE][3]; + private final byte[][] byteColours = new byte[PALETTE_SIZE][4]; + private final byte[][] greyByteColours = new byte[PALETTE_SIZE][4]; public static final Palette DEFAULT = new Palette(); @@ -19,16 +24,23 @@ public Palette() { // Get the default palette resetColours(); + + for( int i = 0; i < PALETTE_SIZE; i++ ) byteColours[i][3] = greyByteColours[i][3] = (byte) 255; } public void setColour( int i, double r, double g, double b ) { - if( i >= 0 && i < colours.length ) - { - colours[i][0] = r; - colours[i][1] = g; - colours[i][2] = b; - } + if( i < 0 || i >= colours.length ) return; + colours[i][0] = r; + colours[i][1] = g; + colours[i][2] = b; + + byteColours[i][0] = (byte) (int) (r * 255); + byteColours[i][1] = (byte) (int) (g * 255); + byteColours[i][2] = (byte) (int) (b * 255); + + byte grey = (byte) (int) ((r + g + b) / 3 * 255); + greyByteColours[i][0] = greyByteColours[i][1] = greyByteColours[i][2] = grey; } public void setColour( int i, Colour colour ) @@ -38,19 +50,29 @@ public void setColour( int i, Colour colour ) public double[] getColour( int i ) { - if( i >= 0 && i < colours.length ) - { - return colours[i]; - } - return null; + return i >= 0 && i < colours.length ? colours[i] : null; + } + + /** + * Get the colour as a set of bytes rather than floats. This is called frequently by {@link FixedWidthFontRenderer}, + * as our vertex format uses bytes. + * + * This allows us to do the conversion once (when setting the colour) rather than for every vertex, at the cost of + * some memory overhead. + * + * @param i The colour index. + * @param greyscale Whether this number should be converted to greyscale. + * @return The number as a tuple of bytes. + */ + @Nonnull + public byte[] getByteColour( int i, boolean greyscale ) + { + return greyscale ? greyByteColours[i] : byteColours[i]; } public void resetColour( int i ) { - if( i >= 0 && i < colours.length ) - { - setColour( i, Colour.VALUES[i] ); - } + if( i >= 0 && i < colours.length ) setColour( i, Colour.VALUES[i] ); } public void resetColours() @@ -89,9 +111,12 @@ public void write( PacketBuffer buffer ) public void read( PacketBuffer buffer ) { - for( double[] colour : colours ) + for( int i = 0; i < PALETTE_SIZE; i++ ) { - for( int i = 0; i < colour.length; i++ ) colour[i] = (buffer.readByte() & 0xFF) / 255.0; + double r = (buffer.readByte() & 0xFF) / 255.0; + double g = (buffer.readByte() & 0xFF) / 255.0; + double b = (buffer.readByte() & 0xFF) / 255.0; + setColour( i, r, g, b ); } } @@ -117,7 +142,8 @@ public void readFromNBT( CompoundNBT nbt ) for( int i = 0; i < colours.length; i++ ) { - colours[i] = decodeRGB8( rgb8[i] ); + double[] colours = decodeRGB8( rgb8[i] ); + setColour( i, colours[0], colours[1], colours[2] ); } } } diff --git a/src/main/java/dan200/computercraft/shared/util/TickScheduler.java b/src/main/java/dan200/computercraft/shared/util/TickScheduler.java index d5452099d..5fcf7ffe4 100644 --- a/src/main/java/dan200/computercraft/shared/util/TickScheduler.java +++ b/src/main/java/dan200/computercraft/shared/util/TickScheduler.java @@ -5,10 +5,8 @@ */ package dan200.computercraft.shared.util; -import com.google.common.collect.MapMaker; import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.common.TileGeneric; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.math.BlockPos; import net.minecraft.world.ITickList; import net.minecraft.world.World; @@ -16,9 +14,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import java.util.Collections; -import java.util.Iterator; -import java.util.Set; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; /** * A thread-safe version of {@link ITickList#scheduleTick(BlockPos, Object, int)}. @@ -32,16 +29,12 @@ private TickScheduler() { } - private static final Set toTick = Collections.newSetFromMap( - new MapMaker() - .weakKeys() - .makeMap() - ); + private static final Queue toTick = new ConcurrentLinkedDeque<>(); public static void schedule( TileGeneric tile ) { World world = tile.getLevel(); - if( world != null && !world.isClientSide ) toTick.add( tile ); + if( world != null && !world.isClientSide && !tile.scheduled.getAndSet( true ) ) toTick.add( tile ); } @SubscribeEvent @@ -49,11 +42,11 @@ public static void tick( TickEvent.ServerTickEvent event ) { if( event.phase != TickEvent.Phase.START ) return; - Iterator iterator = toTick.iterator(); - while( iterator.hasNext() ) + TileGeneric tile; + while( (tile = toTick.poll()) != null ) { - TileEntity tile = iterator.next(); - iterator.remove(); + tile.scheduled.set( false ); + if( tile.isRemoved() ) continue; World world = tile.getLevel(); BlockPos pos = tile.getBlockPos(); diff --git a/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java b/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java index 4b0da0ae0..66f07fcd9 100644 --- a/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java +++ b/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java @@ -10,17 +10,21 @@ import net.minecraft.item.ItemStack; import javax.annotation.Nonnull; +import java.util.function.Predicate; public class ValidatingSlot extends Slot { - public ValidatingSlot( IInventory inventoryIn, int index, int xPosition, int yPosition ) + private final Predicate predicate; + + public ValidatingSlot( IInventory inventoryIn, int index, int xPosition, int yPosition, Predicate predicate ) { super( inventoryIn, index, xPosition, yPosition ); + this.predicate = predicate; } @Override public boolean mayPlace( @Nonnull ItemStack stack ) { - return true; // inventory.isItemValidForSlot( slotNumber, stack ); + return predicate.test( stack ); } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 55032ba56..438eb9b1e 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -20,6 +20,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a [[dependencies.computercraft]] modId="forge" mandatory=true - versionRange="[36.2.20,37)" + versionRange="[36.2.34,37)" ordering="NONE" side="BOTH" diff --git a/src/main/resources/assets/computercraft/shaders/monitor.frag b/src/main/resources/assets/computercraft/shaders/monitor.frag index f19a6a9eb..d99968b5c 100644 --- a/src/main/resources/assets/computercraft/shaders/monitor.frag +++ b/src/main/resources/assets/computercraft/shaders/monitor.frag @@ -4,10 +4,16 @@ #define FONT_HEIGHT 9.0 uniform sampler2D u_font; -uniform int u_width; -uniform int u_height; uniform usamplerBuffer u_tbo; -uniform vec3 u_palette[16]; + +layout(std140) uniform u_monitor { + vec3 u_palette[16]; + int u_width; + int u_height; + ivec2 u_cursorPos; + int u_cursorColour; +}; +uniform int u_cursorBlink; in vec2 f_pos; @@ -19,6 +25,10 @@ vec2 texture_corner(int index) { return vec2(x, y); } +vec4 recolour(vec4 texture, int colour) { + return vec4(texture.rgb * u_palette[colour], texture.rgba); +} + void main() { vec2 term_pos = vec2(f_pos.x / FONT_WIDTH, f_pos.y / FONT_HEIGHT); vec2 corner = floor(term_pos); @@ -35,6 +45,12 @@ void main() { int bg = int(texelFetch(u_tbo, index + 2).r); vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT); - vec4 img = texture(u_font, (texture_corner(character) + pos) / 256.0); - colour = vec4(mix(u_palette[bg], img.rgb * u_palette[fg], img.a * mult), 1.0); + vec4 charTex = recolour(texture(u_font, (texture_corner(character) + pos) / 256.0), fg); + + // Applies the cursor on top of the current character if we're blinking and in the current cursor's cell. We do it + // this funky way to avoid branches. + vec4 cursorTex = recolour(texture(u_font, (texture_corner(95) + pos) / 256.0), u_cursorColour); // 95 = '_' + vec4 img = mix(charTex, cursorTex, cursorTex.a * float(u_cursorBlink) * (u_cursorPos == cell ? 1.0 : 0.0)); + + colour = vec4(mix(u_palette[bg], img.rgb, img.a * mult), 1.0); } diff --git a/src/main/resources/data/computercraft/lua/rom/apis/colors.lua b/src/main/resources/data/computercraft/lua/rom/apis/colors.lua index 7b607757d..7140569d9 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/colors.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/colors.lua @@ -1,12 +1,13 @@ ---[[- The Colors API allows you to manipulate sets of colors. +--[[- Constants and functions for colour values, suitable for working with +@{term} and @{redstone}. -This is useful in conjunction with Bundled Cables from the RedPower mod, RedNet -Cables from the MineFactory Reloaded mod, and colors on Advanced Computers and -Advanced Monitors. +This is useful in conjunction with @{redstone.setBundledOutput|Bundled Cables} +from mods like Project Red, and @{term.setTextColour|colors on Advanced +Computers and Advanced Monitors}. -For the non-American English version just replace @{colors} with @{colours} and -it will use the other API, colours which is exactly the same, except in British -English (e.g. @{colors.gray} is spelt @{colours.grey}). +For the non-American English version just replace @{colors} with @{colours}. +This alternative API is exactly the same, except the colours use British English +(e.g. @{colors.gray} is spelt @{colours.grey}). On basic terminals (such as the Computer and Monitor), all the colors are converted to grayscale. This means you can still use all 16 colors on the @@ -16,7 +17,7 @@ terminal supports color by using the function @{term.isColor}. Grayscale colors are calculated by taking the average of the three components, i.e. `(red + green + blue) / 3`. - +
    @@ -268,7 +269,7 @@ end --- Combine a three-colour RGB value into one hexadecimal representation. -- -- @tparam number r The red channel, should be between 0 and 1. --- @tparam number g The red channel, should be between 0 and 1. +-- @tparam number g The green channel, should be between 0 and 1. -- @tparam number b The blue channel, should be between 0 and 1. -- @treturn number The combined hexadecimal colour. -- @usage @@ -291,7 +292,7 @@ end -- -- @tparam number rgb The combined hexadecimal colour. -- @treturn number The red channel, will be between 0 and 1. --- @treturn number The red channel, will be between 0 and 1. +-- @treturn number The green channel, will be between 0 and 1. -- @treturn number The blue channel, will be between 0 and 1. -- @usage -- ```lua diff --git a/src/main/resources/data/computercraft/lua/rom/apis/colours.lua b/src/main/resources/data/computercraft/lua/rom/apis/colours.lua index 287d73de9..eea54fd7e 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/colours.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/colours.lua @@ -1,4 +1,4 @@ ---- Colours for lovers of British spelling. +--- An alternative version of @{colors} for lovers of British spelling. -- -- @see colors -- @module colours diff --git a/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua b/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua index 1c77fa26e..55c20e207 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/command/commands.lua @@ -1,21 +1,26 @@ ---- The commands API allows your system to directly execute [Minecraft --- commands][mc] and gather data from the results. --- --- While one may use @{commands.exec} directly to execute a command, the --- commands API also provides helper methods to execute every command. For --- instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`. --- --- @{commands.async} provides a similar interface to execute asynchronous --- commands. `commands.async.say("Hi!")` is equivalent to --- `commands.execAsync("Hi!")`. --- --- [mc]: https://minecraft.gamepedia.com/Commands --- --- @module commands --- @usage Set the block above this computer to stone: --- --- commands.setblock("~", "~1", "~", "minecraft:stone") +--[[- Execute [Minecraft commands][mc] and gather data from the results from +a command computer. +:::note +This API is only available on Command computers. It is not accessible to normal +players. +::: + +While one may use @{commands.exec} directly to execute a command, the +commands API also provides helper methods to execute every command. For +instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`. + +@{commands.async} provides a similar interface to execute asynchronous +commands. `commands.async.say("Hi!")` is equivalent to +`commands.execAsync("say Hi!")`. + +[mc]: https://minecraft.gamepedia.com/Commands + +@module commands +@usage Set the block above this computer to stone: + + commands.setblock("~", "~1", "~", "minecraft:stone") +]] if not commands then error("Cannot load command API on normal computer", 2) end diff --git a/src/main/resources/data/computercraft/lua/rom/apis/disk.lua b/src/main/resources/data/computercraft/lua/rom/apis/disk.lua index 53fc56dbc..c00486b69 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/disk.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/disk.lua @@ -1,4 +1,4 @@ ---[[- The Disk API allows you to interact with disk drives. +--[[- Interact with disk drives. These functions can operate on locally attached or remote disk drives. To use a locally attached drive, specify “side” as one of the six sides (e.g. `left`); to diff --git a/src/main/resources/data/computercraft/lua/rom/apis/gps.lua b/src/main/resources/data/computercraft/lua/rom/apis/gps.lua index e6f6ea2f8..f157389ba 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/gps.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/gps.lua @@ -1,5 +1,5 @@ ---[[- The GPS API provides a method for turtles and computers to retrieve their -own locations. +--[[- Use @{modem|modems} to locate the position of the current turtle or +computers. It broadcasts a PING message over @{rednet} and wait for responses. In order for this system to work, there must be at least 4 computers used as gps hosts which diff --git a/src/main/resources/data/computercraft/lua/rom/apis/help.lua b/src/main/resources/data/computercraft/lua/rom/apis/help.lua index 503555de2..f347b13d2 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/help.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/help.lua @@ -1,4 +1,4 @@ ---- Provides an API to read help files. +--- Find help files on the current computer. -- -- @module help -- @since 1.2 diff --git a/src/main/resources/data/computercraft/lua/rom/apis/keys.lua b/src/main/resources/data/computercraft/lua/rom/apis/keys.lua index aeac84b7a..300cf6eb2 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/keys.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/keys.lua @@ -1,5 +1,4 @@ ---- The Keys API provides a table of numerical codes corresponding to keyboard --- keys, suitable for decoding key events. +--- Constants for all keyboard "key codes", as queued by the @{key} event. -- -- These values are not guaranteed to remain the same between versions. It is -- recommended that you use the constants provided by this file, rather than diff --git a/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua index d0ed2ba24..048ce6ab8 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua @@ -1,5 +1,5 @@ ---- An API for advanced systems which can draw pixels and lines, load and draw --- image files. You can use the `colors` API for easier color manipulation. +--- Utilities for drawing more complex graphics, such as pixels, lines and +-- images. -- -- @module paintutils -- @since 1.45 diff --git a/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua b/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua index 0fe670c08..170e7f4dc 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/parallel.lua @@ -1,8 +1,8 @@ ---[[- Provides a simple implementation of multitasking. +--[[- A simple way to run several functions at once. Functions are not actually executed simultaniously, but rather this API will -automatically switch between them whenever they yield (eg whenever they call -@{coroutine.yield}, or functions that call that - eg @{os.pullEvent} - or +automatically switch between them whenever they yield (e.g. whenever they call +@{coroutine.yield}, or functions that call that - such as @{os.pullEvent} - or functions that call that, etc - basically, anything that causes the function to "pause"). @@ -12,6 +12,27 @@ script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API, etc) can safely be used in one without affecting the event queue accessed by the other. + +:::caution +When using this API, be careful to pass the functions you want to run in +parallel, and _not_ the result of calling those functions. + +For instance, the following is correct: + +```lua +local function do_sleep() sleep(1) end +parallel.waitForAny(do_sleep, rednet.receive) +``` + +but the following is **NOT**: + +```lua +local function do_sleep() sleep(1) end +parallel.waitForAny(do_sleep(), rednet.receive) +``` + +::: + @module parallel @since 1.2 ]] diff --git a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua index 8e8d27a6f..ae4063195 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/peripheral.lua @@ -1,4 +1,6 @@ ---[[- Peripherals are blocks (or turtle and pocket computer upgrades) which can +--[[- Find and control peripherals attached to this computer. + +Peripherals are blocks (or turtle and pocket computer upgrades) which can be controlled by a computer. For instance, the @{speaker} peripheral allows a computer to play music and the @{monitor} peripheral allows you to display text in the world. diff --git a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua index d5ed812d8..23d8b3cd8 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua @@ -1,6 +1,6 @@ ---[[- The Rednet API allows computers to communicate between each other by using -@{modem|modems}. It provides a layer of abstraction on top of the main @{modem} -peripheral, making it slightly easier to use. +--[[- Communicate with other computers by using @{modem|modems}. @{rednet} +provides a layer of abstraction on top of the main @{modem} peripheral, making +it slightly easier to use. ## Basic usage In order to send a message between two computers, each computer must have a diff --git a/src/main/resources/data/computercraft/lua/rom/apis/settings.lua b/src/main/resources/data/computercraft/lua/rom/apis/settings.lua index 6ef015178..ce29eaf0c 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/settings.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/settings.lua @@ -1,5 +1,4 @@ ---- The settings API allows to store values and save them to a file for --- persistent configurations for CraftOS and your programs. +--- Read and write configuration options for CraftOS and your programs. -- -- By default, the settings API will load its configuration from the -- `/.settings` file. One can then use @{settings.save} to update the file. diff --git a/src/main/resources/data/computercraft/lua/rom/apis/term.lua b/src/main/resources/data/computercraft/lua/rom/apis/term.lua index 125d7c408..2d2b4afb4 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/term.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/term.lua @@ -1,7 +1,4 @@ ---- The Terminal API provides functions for writing text to the terminal and --- monitors, and drawing ASCII graphics. --- --- @module term +--- @module term local expect = dofile("rom/modules/main/cc/expect.lua").expect diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 43343d7e5..e5bc459f5 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -1,5 +1,4 @@ ---- The @{textutils} API provides helpful utilities for formatting and --- manipulating strings. +--- Helpful utilities for formatting and manipulating strings. -- -- @module textutils -- @since 1.2 @@ -632,7 +631,13 @@ do end if c == "" then return expected(pos, c, "']'") end - if c == "]" then return empty_json_array, pos + 1 end + if c == "]" then + if opts.parse_empty_array ~= false then + return empty_json_array, pos + 1 + else + return {}, pos + 1 + end + end while true do n, arr[n], pos = n + 1, decode_impl(str, pos, opts) @@ -653,32 +658,51 @@ do error_at(pos, "Unexpected character %q.", c) end - --- Converts a serialised JSON string back into a reassembled Lua object. - -- - -- This may be used with @{textutils.serializeJSON}, or when communicating - -- with command blocks or web APIs. - -- - -- @tparam string s The serialised string to deserialise. - -- @tparam[opt] { nbt_style? = boolean, parse_null? = boolean } options - -- Options which control how this JSON object is parsed. - -- - -- - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings, - -- as produced by many commands. - -- - `parse_null`: When true, `null` will be parsed as @{json_null}, rather - -- than `nil`. - -- - -- [nbt]: https://minecraft.gamepedia.com/NBT_format - -- @return[1] The deserialised object - -- @treturn[2] nil If the object could not be deserialised. - -- @treturn string A message describing why the JSON string is invalid. - -- @since 1.87.0 + --[[- Converts a serialised JSON string back into a reassembled Lua object. + + This may be used with @{textutils.serializeJSON}, or when communicating + with command blocks or web APIs. + + If a `null` value is encountered, it is converted into `nil`. It can be converted + into @{textutils.json_null} with the `parse_null` option. + + If an empty array is encountered, it is converted into @{textutils.empty_json_array}. + It can be converted into a new empty table with the `parse_empty_array` option. + + @tparam string s The serialised string to deserialise. + @tparam[opt] { nbt_style? = boolean, parse_null? = boolean, parse_empty_array? = boolean } options + Options which control how this JSON object is parsed. + + - `nbt_style`: When true, this will accept [stringified NBT][nbt] strings, + as produced by many commands. + - `parse_null`: When true, `null` will be parsed as @{json_null}, rather than + `nil`. + - `parse_empty_array`: When false, empty arrays will be parsed as a new table. + By default (or when this value is true), they are parsed as @{empty_json_array}. + + [nbt]: https://minecraft.gamepedia.com/NBT_format + @return[1] The deserialised object + @treturn[2] nil If the object could not be deserialised. + @treturn string A message describing why the JSON string is invalid. + @since 1.87.0 + @see textutils.json_null Use to serialize a JSON `null` value. + @see textutils.empty_json_array Use to serialize a JSON empty array. + @usage Unserialise a basic JSON object + + textutils.unserialiseJSON('{"name": "Steve", "age": null}') + + @usage Unserialise a basic JSON object, returning null values as @{json_null}. + + textutils.unserialiseJSON('{"name": "Steve", "age": null}', { parse_null = true }) + ]] unserialise_json = function(s, options) expect(1, s, "string") expect(2, options, "table", "nil") if options then field(options, "nbt_style", "boolean", "nil") - field(options, "nbt_style", "boolean", "nil") + field(options, "parse_null", "boolean", "nil") + field(options, "parse_empty_array", "boolean", "nil") else options = {} end @@ -785,6 +809,8 @@ unserialise = unserialize -- GB version -- times. -- @usage textutils.serializeJSON({ values = { 1, "2", true } }) -- @since 1.7 +-- @see textutils.json_null Use to serialize a JSON `null` value. +-- @see textutils.empty_json_array Use to serialize a JSON empty array. function serializeJSON(t, bNBTStyle) expect(1, t, "table", "string", "number", "boolean") expect(2, bNBTStyle, "boolean", "nil") diff --git a/src/main/resources/data/computercraft/lua/rom/apis/vector.lua b/src/main/resources/data/computercraft/lua/rom/apis/vector.lua index a5227c853..0b90d373c 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/vector.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/vector.lua @@ -1,4 +1,6 @@ ---- The vector API provides methods to create and manipulate vectors. +--- A basic 3D vector type and some common vector operations. This may be useful +-- when working with coordinates in Minecraft's world (such as those from the +-- @{gps} API). -- -- An introduction to vectors can be found on [Wikipedia][wiki]. -- diff --git a/src/main/resources/data/computercraft/lua/rom/apis/window.lua b/src/main/resources/data/computercraft/lua/rom/apis/window.lua index 724cda155..fd57b79fa 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/window.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/window.lua @@ -1,32 +1,33 @@ ---- The Window API allows easy definition of spaces within the display that can --- be written/drawn to, then later redrawn/repositioned/etc as need be. The API --- itself contains only one function, @{window.create}, which returns the --- windows themselves. --- --- Windows are considered terminal objects - as such, they have access to nearly --- all the commands in the term API (plus a few extras of their own, listed --- within said API) and are valid targets to redirect to. --- --- Each window has a "parent" terminal object, which can be the computer's own --- display, a monitor, another window or even other, user-defined terminal --- objects. Whenever a window is rendered to, the actual screen-writing is --- performed via that parent (or, if that has one too, then that parent, and so --- forth). Bear in mind that the cursor of a window's parent will hence be moved --- around etc when writing a given child window. --- --- Windows retain a memory of everything rendered "through" them (hence acting --- as display buffers), and if the parent's display is wiped, the window's --- content can be easily redrawn later. A window may also be flagged as --- invisible, preventing any changes to it from being rendered until it's --- flagged as visible once more. --- --- A parent terminal object may have multiple children assigned to it, and --- windows may overlap. For example, the Multishell system functions by --- assigning each tab a window covering the screen, each using the starting --- terminal display as its parent, and only one of which is visible at a time. --- --- @module window --- @since 1.6 +--[[- A @{term.Redirect|terminal redirect} occupying a smaller area of an +existing terminal. This allows for easy definition of spaces within the display +that can be written/drawn to, then later redrawn/repositioned/etc as need +be. The API itself contains only one function, @{window.create}, which returns +the windows themselves. + +Windows are considered terminal objects - as such, they have access to nearly +all the commands in the term API (plus a few extras of their own, listed within +said API) and are valid targets to redirect to. + +Each window has a "parent" terminal object, which can be the computer's own +display, a monitor, another window or even other, user-defined terminal +objects. Whenever a window is rendered to, the actual screen-writing is +performed via that parent (or, if that has one too, then that parent, and so +forth). Bear in mind that the cursor of a window's parent will hence be moved +around etc when writing a given child window. + +Windows retain a memory of everything rendered "through" them (hence acting as +display buffers), and if the parent's display is wiped, the window's content can +be easily redrawn later. A window may also be flagged as invisible, preventing +any changes to it from being rendered until it's flagged as visible once more. + +A parent terminal object may have multiple children assigned to it, and windows +may overlap. For example, the Multishell system functions by assigning each tab +a window covering the screen, each using the starting terminal display as its +parent, and only one of which is visible at a time. + +@module window +@since 1.6 +]] local expect = dofile("rom/modules/main/cc/expect.lua").expect diff --git a/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/src/main/resources/data/computercraft/lua/rom/help/changelog.md index a27cc478c..b39f2aad4 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,12 @@ +# New features in CC: Tweaked 1.100.5 + +* Generic peripherals now use capabilities on the given side if one isn't provided on the internal side. +* Improve performance of monitor rendering. + +Several bug fixes: +* Various documentation fixes (bclindner, Hasaabitt) +* Speaker sounds are now correctly positioned on the centre of the speaker block. + # New features in CC: Tweaked 1.100.4 Several bug fixes: diff --git a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index 190f08559..cf8a8280a 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,6 +1,10 @@ -New features in CC: Tweaked 1.100.4 +New features in CC: Tweaked 1.100.5 + +* Generic peripherals now use capabilities on the given side if one isn't provided on the internal side. +* Improve performance of monitor rendering. Several bug fixes: -* Fix the monitor watching blocking the main thread when chunks are slow to load. +* Various documentation fixes (bclindner, Hasaabitt) +* Speaker sounds are now correctly positioned on the centre of the speaker block. Type "help changelog" to see the full version history. diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua index a3fa21d33..2b5e2d183 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua @@ -1,5 +1,5 @@ --[[- -Provides utilities for converting between streams of DFPWM audio data and a list of amplitudes. +Convert between streams of DFPWM audio data and a list of amplitudes. DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by GreaseMonkey. It's a relatively compact format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode @@ -18,11 +18,12 @@ for each one you write. ## Converting audio to DFPWM DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it. -Instead, you can convert audio files online using [music.madefor.cc] or with the [LionRay Wav Converter][LionRay] Java -application. +Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java +application or development builds of [FFmpeg]. [music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked" [LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter " +[FFmpeg]: https://ffmpeg.org "FFmpeg command-line audio manipulation library" @see guide!speaker_audio Gives a more general introduction to audio processing and the speaker. @see speaker.playAudio To play the decoded audio data. diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua index c188e7823..0950bebd7 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/image/nft.lua @@ -1,8 +1,8 @@ ---- Provides utilities for working with "nft" images. +--- Read and draw nbt ("Nitrogen Fingers Text") images. -- -- nft ("Nitrogen Fingers Text") is a file format for drawing basic images. -- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured --- text. +-- text as well as simple coloured pixels. -- -- @module cc.image.nft -- @since 1.90.0 diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua index b4337381d..4e7bbdaa1 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/pretty.lua @@ -1,5 +1,5 @@ ---[[- Provides a "pretty printer", for rendering data structures in an -aesthetically pleasing manner. +--[[- 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 diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua index e0fa83aa9..28391580c 100644 --- a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua @@ -1,5 +1,5 @@ ---[[- This provides a pure Lua implementation of the builtin @{require} function -and @{package} library. +--[[- A pure Lua implementation of the builtin @{require} function and +@{package} library. Generally you do not need to use this module - it is injected into the every program's environment. However, it may be useful when building a custom shell or diff --git a/src/main/resources/data/computercraft/lua/rom/programs/edit.lua b/src/main/resources/data/computercraft/lua/rom/programs/edit.lua index c913aa234..5331854d9 100644 --- a/src/main/resources/data/computercraft/lua/rom/programs/edit.lua +++ b/src/main/resources/data/computercraft/lua/rom/programs/edit.lua @@ -431,7 +431,7 @@ local tMenuFuncs = { file.write(runHandler:format(sTitle, table.concat(tLines, "\n"), "@" .. fs.getName(sPath))) end) if ok then - local nTask = shell.openTab(sTempPath) + local nTask = shell.openTab("/" .. sTempPath) if nTask then shell.switchTab(nTask) else diff --git a/src/main/resources/data/computercraft/lua/rom/programs/shell.lua b/src/main/resources/data/computercraft/lua/rom/programs/shell.lua index f24c0c4fc..19c1bb02d 100644 --- a/src/main/resources/data/computercraft/lua/rom/programs/shell.lua +++ b/src/main/resources/data/computercraft/lua/rom/programs/shell.lua @@ -3,7 +3,7 @@ -- It allows you to @{run|start programs}, @{setCompletionFunction|add -- completion for a program}, and much more. -- --- @{shell} is not a "true" API. Instead, it is a standard program, which its +-- @{shell} is not a "true" API. Instead, it is a standard program, which injects its -- API into the programs that it launches. This allows for multiple shells to -- run at the same time, but means that the API is not available in the global -- environment, and so is unavailable to other @{os.loadAPI|APIs}. @@ -242,8 +242,8 @@ end -- @since 1.2 -- @usage Locate the `hello` program. -- --- shell.resolveProgram("hello") --- -- => rom/programs/fun/hello.lua +-- shell.resolveProgram("hello") +-- -- => rom/programs/fun/hello.lua function shell.resolveProgram(command) expect(1, command, "string") -- Substitute aliases firsts diff --git a/src/test/java/dan200/computercraft/client/sound/DfpwmStreamTest.java b/src/test/java/dan200/computercraft/client/sound/DfpwmStreamTest.java new file mode 100644 index 000000000..b15693779 --- /dev/null +++ b/src/test/java/dan200/computercraft/client/sound/DfpwmStreamTest.java @@ -0,0 +1,38 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.client.sound; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DfpwmStreamTest +{ + @Test + public void testDecodesBytes() + { + DfpwmStream stream = new DfpwmStream(); + + ByteBuf input = ByteBufAllocator.DEFAULT.buffer(); + input.writeBytes( new byte[] { 43, -31, 33, 44, 30, -16, -85, 23, -3, -55, 46, -70, 68, -67, 74, -96, -68, 16, 94, -87, -5, 87, 11, -16, 19, 92, 85, -71, 126, 5, -84, 64, 17, -6, 85, -11, -1, -87, -12, 1, 85, -56, 33, -80, 82, 104, -93, 17, 126, 23, 91, -30, 37, -32, 117, -72, -58, 11, -76, 19, -108, 86, -65, -10, -1, -68, -25, 10, -46, 85, 124, -54, 15, -24, 43, -94, 117, 63, -36, 15, -6, 88, 87, -26, -83, 106, 41, 13, -28, -113, -10, -66, 119, -87, -113, 68, -55, 40, -107, 62, 20, 72, 3, -96, 114, -87, -2, 39, -104, 30, 20, 42, 84, 24, 47, 64, 43, 61, -35, 95, -65, 42, 61, 42, -50, 4, -9, 81 } ); + stream.push( input ); + + ByteBuffer buffer = stream.read( 2048 + 1 ); + assertEquals( 2048, buffer.remaining(), "Must have read 1024 bytes" ); + + byte[] decoded = new byte[] { 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, -1, -2, -2, -1, 0, 1, 0, -1, -3, -5, -5, -5, -7, -9, -11, -11, -9, -9, -9, -9, -10, -12, -12, -10, -8, -6, -6, -8, -10, -12, -14, -16, -18, -17, -15, -12, -9, -6, -3, -2, -2, -2, -2, -2, -2, 0, 3, 6, 7, 7, 7, 4, 1, 1, 1, 1, 3, 5, 7, 9, 12, 15, 15, 12, 12, 12, 9, 9, 11, 12, 12, 14, 16, 17, 17, 17, 14, 11, 11, 11, 10, 12, 14, 14, 13, 13, 10, 9, 9, 7, 5, 4, 4, 4, 4, 4, 6, 8, 10, 10, 10, 10, 10, 10, 10, 9, 8, 8, 8, 7, 6, 4, 2, 0, 0, 0, 0, 0, -1, -1, 0, 1, 3, 3, 3, 3, 2, 0, -2, -2, -2, -3, -5, -7, -7, -5, -3, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, 0, 1, 1, 1, 2, 3, 4, 5, 6, 7, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 9, 8, 7, 6, 4, 2, 0, 0, 2, 4, 6, 8, 10, 10, 8, 7, 7, 5, 3, 1, -1, 0, 2, 4, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 4, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 9, 9, 9, 9, 9, 8, 7, 6, 5, 3, 1, 1, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -3, -3, -3, -3, -2, -3, -4, -4, -3, -4, -5, -6, -6, -5, -5, -4, -3, -2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 20, 17, 16, 16, 15, 15, 15, 15, 13, 13, 13, 13, 14, 15, 16, 18, 18, 16, 14, 12, 10, 8, 5, 5, 5, 4, 4, 4, 4, 4, 4, 2, 0, -2, -2, -2, -4, -4, -2, 0, 0, -2, -4, -6, -6, -6, -8, -10, -12, -14, -16, -15, -13, -12, -11, -11, -11, -11, -13, -13, -13, -13, -13, -14, -16, -18, -18, -18, -18, -16, -16, -16, -14, -13, -14, -15, -15, -14, -14, -12, -11, -12, -13, -13, -12, -13, -14, -15, -15, -13, -11, -9, -7, -5, -5, -5, -3, -1, -1, -1, -1, -3, -5, -5, -3, -3, -3, -1, -1, -1, -1, -3, -3, -3, -4, -6, -6, -4, -2, 0, 0, 0, 0, -2, -2, -2, -3, -5, -7, -9, -11, -13, -13, -11, -9, -7, -6, -6, -6, -6, -4, -2, -2, -4, -6, -8, -7, -5, -3, -2, -2, -2, -2, 0, 0, -2, -4, -4, -2, 0, 2, 2, 1, 1, -1, -3, -5, -7, -10, -10, -10, -10, -8, -7, -7, -5, -3, -2, -4, -4, -4, -6, -8, -10, -12, -12, -12, -12, -12, -14, -13, -13, -13, -11, -11, -11, -11, -11, -11, -11, -9, -7, -5, -3, -1, -1, -1, -1, -1, 1, 1, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 22, 19, 18, 20, 22, 24, 23, 22, 24, 26, 28, 27, 24, 23, 25, 28, 28, 28, 27, 26, 26, 23, 20, 17, 14, 14, 14, 11, 11, 11, 11, 13, 15, 16, 16, 16, 15, 15, 14, 14, 12, 10, 9, 11, 13, 15, 17, 17, 14, 13, 13, 12, 12, 10, 9, 11, 13, 15, 17, 19, 19, 16, 13, 10, 7, 4, 1, 1, 2, 2, 4, 7, 10, 13, 13, 13, 12, 12, 12, 9, 6, 6, 6, 3, 0, 0, 0, 0, 2, 3, 3, 3, 3, 5, 7, 7, 7, 9, 11, 13, 15, 18, 18, 15, 12, 9, 8, 10, 13, 13, 13, 15, 18, 21, 24, 27, 27, 23, 19, 15, 11, 10, 9, 9, 12, 16, 19, 22, 23, 19, 14, 13, 16, 16, 15, 15, 14, 17, 20, 20, 19, 19, 18, 17, 14, 13, 15, 15, 12, 11, 13, 16, 19, 19, 18, 20, 20, 19, 18, 18, 17, 17, 16, 16, 16, 15, 17, 17, 16, 16, 13, 12, 12, 11, 11, 9, 9, 9, 9, 11, 11, 9, 7, 5, 3, 1, 1, 1, -1, -1, 1, 3, 5, 7, 9, 11, 12, 9, 6, 6, 6, 6, 8, 8, 7, 9, 11, 13, 13, 12, 14, 16, 18, 20, 20, 20, 22, 24, 26, 25, 25, 27, 29, 28, 27, 26, 23, 22, 22, 21, 21, 20, 22, 24, 26, 28, 27, 24, 21, 21, 21, 18, 17, 17, 14, 11, 11, 11, 10, 10, 7, 6, 6, 4, 3, 5, 5, 3, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 0, -1, -1, 0, 0, 1, 2, 3, 4, 3, 1, -1, -3, -3, -3, -3, -2, -3, -4, -6, -8, -10, -10, -10, -12, -12, -12, -12, -10, -10, -11, -12, -14, -16, -18, -20, -22, -24, -26, -28, -27, -27, -26, -26, -25, -25, -27, -26, -24, -22, -22, -22, -22, -24, -24, -24, -24, -23, -23, -22, -22, -21, -20, -19, -17, -15, -13, -11, -9, -7, -7, -9, -9, -9, -11, -13, -15, -17, -16, -14, -13, -15, -14, -14, -14, -12, -10, -8, -7, -9, -11, -13, -15, -14, -14, -13, -13, -15, -17, -19, -18, -18, -17, -17, -16, -16, -18, -20, -22, -21, -21, -21, -21, -21, -20, -21, -22, -24, -24, -22, -22, -24, -26, -25, -23, -21, -19, -18, -17, -17, -19, -21, -23, -25, -27, -29, -31, -30, -29, -28, -26, -25, -24, -24, -23, -23, -25, -24, -24, -24, -22, -20, -18, -18, -20, -20, -20, -20, -18, -16, -16, -16, -14, -12, -10, -8, -6, -4, -4, -4, -4, -4, -2, 0, 2, 4, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 4, 5, 6, 5, 3, 1, 1, 1, 1, 1, 1, 1, 0, -1, -1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, -1, -2, -3, -4, -4, -2, 0, 0, 0, 1, 3, 5, 7, 7, 5, 3, 3, 3, 3, 3 }; + for( int i = 0; i < 1024; i++ ) + { + assertEquals( (short) (decoded[i] << 8), buffer.getShort(), "Bad element at " + i ); + } + + assertEquals( 0, buffer.remaining(), "Must have read all bytes" ); + } +} diff --git a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java index 51e98d257..4ebe80167 100644 --- a/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java +++ b/src/test/java/dan200/computercraft/core/asm/GeneratorTest.java @@ -18,7 +18,7 @@ import java.util.Map; import java.util.Optional; -import static dan200.computercraft.ContramapMatcher.contramap; +import static dan200.computercraft.support.ContramapMatcher.contramap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java new file mode 100644 index 000000000..c6cb4060d --- /dev/null +++ b/src/test/java/dan200/computercraft/core/computer/ComputerThreadTest.java @@ -0,0 +1,98 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.core.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.lua.MachineResult; +import dan200.computercraft.support.ConcurrentHelpers; +import dan200.computercraft.support.IsolatedRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.closeTo; +import static org.junit.jupiter.api.Assertions.*; + +@Timeout( value = 15 ) +@ExtendWith( IsolatedRunner.class ) +@Execution( ExecutionMode.CONCURRENT ) +public class ComputerThreadTest +{ + @Test + public void testSoftAbort() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + assertFalse( timeout.isSoftAborted(), "Should not start soft-aborted" ); + + long delay = ConcurrentHelpers.waitUntil( timeout::isSoftAborted ); + assertThat( "Should be soft aborted", delay * 1e-9, closeTo( 7, 0.5 ) ); + ComputerCraft.log.info( "Slept for {}", delay ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.startAndWait( computer ); + } + + @Test + public void testHardAbort() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + assertFalse( timeout.isHardAborted(), "Should not start soft-aborted" ); + + assertThrows( InterruptedException.class, () -> Thread.sleep( 11_000 ), "Sleep should be hard aborted" ); + assertTrue( timeout.isHardAborted(), "Thread should be hard aborted" ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.startAndWait( computer ); + } + + @Test + public void testNoPauseIfNoOtherMachines() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + boolean didPause = ConcurrentHelpers.waitUntil( timeout::isPaused, 5, TimeUnit.SECONDS ); + assertFalse( didPause, "Machine shouldn't have paused within 5s" ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.startAndWait( computer ); + } + + @Test + public void testPauseIfSomeOtherMachine() throws Exception + { + Computer computer = FakeComputerManager.create(); + FakeComputerManager.enqueue( computer, timeout -> { + long budget = ComputerThread.scaledPeriod(); + assertEquals( budget, TimeUnit.MILLISECONDS.toNanos( 25 ), "Budget should be 25ms" ); + + long delay = ConcurrentHelpers.waitUntil( timeout::isPaused ); + assertThat( "Paused within 25ms", delay * 1e-9, closeTo( 0.03, 0.02 ) ); + + computer.shutdown(); + return MachineResult.OK; + } ); + + FakeComputerManager.createLoopingComputer(); + + FakeComputerManager.startAndWait( computer ); + } +} diff --git a/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java b/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java new file mode 100644 index 000000000..ccbf101ba --- /dev/null +++ b/src/test/java/dan200/computercraft/core/computer/FakeComputerManager.java @@ -0,0 +1,219 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.core.computer; + +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.lua.MachineResult; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.support.IsolatedRunner; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Creates "fake" computers, which just run user-defined tasks rather than Lua code. + * + * Note, this will clobber some parts of the global state. It's recommended you use this inside an {@link IsolatedRunner}. + */ +public class FakeComputerManager +{ + interface Task + { + MachineResult run( TimeoutState state ) throws Exception; + } + + private static final Map> machines = new HashMap<>(); + + private static final Lock errorLock = new ReentrantLock(); + private static final Condition hasError = errorLock.newCondition(); + private static volatile Throwable error; + + static + { + ComputerExecutor.luaFactory = ( computer, timeout ) -> new DummyLuaMachine( timeout, machines.get( computer ) ); + } + + /** + * Create a new computer which pulls from our task queue. + * + * @return The computer. This will not be started yet, you must call {@link Computer#turnOn()} and + * {@link Computer#tick()} to do so. + */ + @Nonnull + public static Computer create() + { + Computer computer = new Computer( new BasicEnvironment(), new Terminal( 51, 19 ), 0 ); + machines.put( computer, new ConcurrentLinkedQueue<>() ); + return computer; + } + + /** + * Create and start a new computer which loops forever. + */ + public static void createLoopingComputer() + { + Computer computer = create(); + enqueueForever( computer, t -> { + Thread.sleep( 100 ); + return MachineResult.OK; + } ); + computer.turnOn(); + computer.tick(); + } + + /** + * Enqueue a task on a computer. + * + * @param computer The computer to enqueue the work on. + * @param task The task to run. + */ + public static void enqueue( @Nonnull Computer computer, @Nonnull Task task ) + { + machines.get( computer ).offer( task ); + } + + /** + * Enqueue a repeated task on a computer. This is automatically requeued when the task finishes, meaning the task + * queue is never empty. + * + * @param computer The computer to enqueue the work on. + * @param task The task to run. + */ + private static void enqueueForever( @Nonnull Computer computer, @Nonnull Task task ) + { + machines.get( computer ).offer( t -> { + MachineResult result = task.run( t ); + + enqueueForever( computer, task ); + computer.queueEvent( "some_event", null ); + return result; + } ); + } + + /** + * Sleep for a given period, immediately propagating any exceptions thrown by a computer. + * + * @param delay The duration to sleep for. + * @param unit The time unit the duration is measured in. + * @throws Exception An exception thrown by a running computer. + */ + public static void sleep( long delay, TimeUnit unit ) throws Exception + { + errorLock.lock(); + try + { + rethrowIfNeeded(); + if( hasError.await( delay, unit ) ) rethrowIfNeeded(); + } + finally + { + errorLock.unlock(); + } + } + + /** + * Start a computer and wait for it to finish. + * + * @param computer The computer to wait for. + * @throws Exception An exception thrown by a running computer. + */ + public static void startAndWait( Computer computer ) throws Exception + { + computer.turnOn(); + computer.tick(); + + do + { + sleep( 100, TimeUnit.MILLISECONDS ); + } while( ComputerThread.hasPendingWork() || computer.isOn() ); + + rethrowIfNeeded(); + } + + private static void rethrowIfNeeded() throws Exception + { + if( error == null ) return; + if( error instanceof Exception ) throw (Exception) error; + if( error instanceof Error ) throw (Error) error; + rethrow( error ); + } + + @SuppressWarnings( "unchecked" ) + private static void rethrow( Throwable e ) throws T + { + throw (T) e; + } + + private static class DummyLuaMachine implements ILuaMachine + { + private final TimeoutState state; + private final Queue handleEvent; + + DummyLuaMachine( TimeoutState state, Queue handleEvent ) + { + this.state = state; + this.handleEvent = handleEvent; + } + + @Override + public void addAPI( @Nonnull ILuaAPI api ) + { + } + + @Override + public MachineResult loadBios( @Nonnull InputStream bios ) + { + return MachineResult.OK; + } + + @Override + public MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments ) + { + try + { + return handleEvent.remove().run( state ); + } + catch( Throwable e ) + { + errorLock.lock(); + try + { + if( error == null ) + { + error = e; + hasError.signal(); + } + else + { + error.addSuppressed( e ); + } + } + finally + { + errorLock.unlock(); + } + + if( !(e instanceof Exception) && !(e instanceof AssertionError) ) rethrow( e ); + return MachineResult.error( e.getMessage() ); + } + } + + @Override + public void close() + { + } + } +} diff --git a/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java b/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java index e72932472..3359f5c16 100644 --- a/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java +++ b/src/test/java/dan200/computercraft/core/terminal/TerminalMatchers.java @@ -5,7 +5,7 @@ */ package dan200.computercraft.core.terminal; -import dan200.computercraft.ContramapMatcher; +import dan200.computercraft.support.ContramapMatcher; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -36,11 +36,11 @@ public static Matcher linesMatch( String kind, LineProvider getLine, S public static Matcher linesMatchWith( String kind, LineProvider getLine, Matcher[] lines ) { - return new ContramapMatcher<>( kind, terminal -> { + return ContramapMatcher.contramap( Matchers.array( lines ), kind, terminal -> { String[] termLines = new String[terminal.getHeight()]; for( int i = 0; i < termLines.length; i++ ) termLines[i] = getLine.getLine( terminal, i ).toString(); return termLines; - }, Matchers.array( lines ) ); + } ); } @FunctionalInterface diff --git a/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java b/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java index 37e033150..044aca0d8 100644 --- a/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java +++ b/src/test/java/dan200/computercraft/core/terminal/TerminalTest.java @@ -5,8 +5,9 @@ */ package dan200.computercraft.core.terminal; +import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.shared.util.Colour; -import dan200.computercraft.utils.CallCounter; +import dan200.computercraft.support.CallCounter; import io.netty.buffer.Unpooled; import net.minecraft.nbt.CompoundNBT; import net.minecraft.network.PacketBuffer; @@ -293,7 +294,7 @@ void testBlitFromOrigin() CallCounter callCounter = new CallCounter(); Terminal terminal = new Terminal( 4, 3, callCounter ); - terminal.blit( "test", "1234", "abcd" ); + blit( terminal, "test", "1234", "abcd" ); assertThat( terminal, allOf( textMatches( new String[] { @@ -322,7 +323,7 @@ void testBlitWithOffset() terminal.setCursorPos( 2, 1 ); callCounter.reset(); - terminal.blit( "hi", "11", "ee" ); + blit( terminal, "hi", "11", "ee" ); assertThat( terminal, allOf( textMatches( new String[] { @@ -354,13 +355,13 @@ void testBlitOutOfBounds() terminal.setCursorPos( 2, -5 ); callCounter.reset(); - terminal.blit( "hi", "11", "ee" ); + blit( terminal, "hi", "11", "ee" ); assertThat( terminal, old.matches() ); callCounter.assertNotCalled(); terminal.setCursorPos( 2, 5 ); callCounter.reset(); - terminal.blit( "hi", "11", "ee" ); + blit( terminal, "hi", "11", "ee" ); assertThat( terminal, old.matches() ); callCounter.assertNotCalled(); } @@ -584,7 +585,7 @@ void testPacketBufferRoundtrip() { Terminal writeTerminal = new Terminal( 2, 1 ); - writeTerminal.blit( "hi", "11", "ee" ); + blit( writeTerminal, "hi", "11", "ee" ); writeTerminal.setCursorPos( 2, 5 ); writeTerminal.setTextColour( 3 ); writeTerminal.setBackgroundColour( 5 ); @@ -614,7 +615,7 @@ void testPacketBufferRoundtrip() void testNbtRoundtrip() { Terminal writeTerminal = new Terminal( 10, 5 ); - writeTerminal.blit( "hi", "11", "ee" ); + blit( writeTerminal, "hi", "11", "ee" ); writeTerminal.setCursorPos( 2, 5 ); writeTerminal.setTextColour( 3 ); writeTerminal.setBackgroundColour( 5 ); @@ -687,6 +688,11 @@ void testGetColour() assertEquals( 5, Terminal.getColour( 'Z', Colour.LIME ) ); } + private static void blit( Terminal terminal, String text, String fg, String bg ) + { + terminal.blit( LuaValues.encode( text ), LuaValues.encode( fg ), LuaValues.encode( bg ) ); + } + private static final class TerminalBufferSnapshot { final String[] textLines; diff --git a/src/test/java/dan200/computercraft/utils/CallCounter.java b/src/test/java/dan200/computercraft/support/CallCounter.java similarity index 95% rename from src/test/java/dan200/computercraft/utils/CallCounter.java rename to src/test/java/dan200/computercraft/support/CallCounter.java index 5dbae63f7..213ad13e0 100644 --- a/src/test/java/dan200/computercraft/utils/CallCounter.java +++ b/src/test/java/dan200/computercraft/support/CallCounter.java @@ -3,7 +3,7 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.utils; +package dan200.computercraft.support; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/dan200/computercraft/support/ConcurrentHelpers.java b/src/test/java/dan200/computercraft/support/ConcurrentHelpers.java new file mode 100644 index 000000000..640f55700 --- /dev/null +++ b/src/test/java/dan200/computercraft/support/ConcurrentHelpers.java @@ -0,0 +1,56 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.support; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; + +/** + * Utilities for working with concurrent systems. + */ +public class ConcurrentHelpers +{ + private static final long DELAY = TimeUnit.MILLISECONDS.toNanos( 2 ); + + /** + * Wait until a condition is true, checking the condition every 2ms. + * + * @param isTrue The condition to check + * @return How long we waited for. + */ + public static long waitUntil( BooleanSupplier isTrue ) + { + long start = System.nanoTime(); + while( true ) + { + if( isTrue.getAsBoolean() ) return System.nanoTime() - start; + LockSupport.parkNanos( DELAY ); + } + } + + /** + * Wait until a condition is true or a timeout is elapsed, checking the condition every 2ms. + * + * @param isTrue The condition to check + * @param timeout The delay after which we will timeout. + * @param unit The time unit the duration is measured in. + * @return {@literal true} if the condition was met, {@literal false} if we timed out instead. + */ + public static boolean waitUntil( BooleanSupplier isTrue, long timeout, TimeUnit unit ) + { + long start = System.nanoTime(); + long timeoutNs = unit.toNanos( timeout ); + while( true ) + { + long time = System.nanoTime() - start; + if( isTrue.getAsBoolean() ) return true; + if( time > timeoutNs ) return false; + + LockSupport.parkNanos( DELAY ); + } + } +} diff --git a/src/test/java/dan200/computercraft/ContramapMatcher.java b/src/test/java/dan200/computercraft/support/ContramapMatcher.java similarity index 50% rename from src/test/java/dan200/computercraft/ContramapMatcher.java rename to src/test/java/dan200/computercraft/support/ContramapMatcher.java index 5126ef280..6f09aab96 100644 --- a/src/test/java/dan200/computercraft/ContramapMatcher.java +++ b/src/test/java/dan200/computercraft/support/ContramapMatcher.java @@ -3,42 +3,34 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft; +package dan200.computercraft.support; -import org.hamcrest.Description; +import org.hamcrest.FeatureMatcher; import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; import java.util.function.Function; -public class ContramapMatcher extends TypeSafeDiagnosingMatcher +/** + * Given some function from {@code T} to {@code U}, converts a {@code Matcher} to {@code Matcher}. This is useful + * when you want to match on a particular field (or some other projection) as part of a larger matcher. + * + * @param The type of the object to be matched. + * @param The type of the projection/field to be matched. + */ +public final class ContramapMatcher extends FeatureMatcher { - private final String desc; private final Function convert; - private final Matcher matcher; public ContramapMatcher( String desc, Function convert, Matcher matcher ) { - this.desc = desc; + super( matcher, desc, desc ); this.convert = convert; - this.matcher = matcher; } @Override - protected boolean matchesSafely( T item, Description mismatchDescription ) + protected U featureValueOf( T actual ) { - U converted = convert.apply( item ); - if( matcher.matches( converted ) ) return true; - - mismatchDescription.appendText( desc ).appendText( " " ); - matcher.describeMismatch( converted, mismatchDescription ); - return false; - } - - @Override - public void describeTo( Description description ) - { - description.appendText( desc ).appendText( " " ).appendDescriptionOf( matcher ); + return convert.apply( actual ); } public static Matcher contramap( Matcher matcher, String desc, Function convert ) diff --git a/src/test/java/dan200/computercraft/support/IsolatedRunner.java b/src/test/java/dan200/computercraft/support/IsolatedRunner.java new file mode 100644 index 000000000..5d95fcd00 --- /dev/null +++ b/src/test/java/dan200/computercraft/support/IsolatedRunner.java @@ -0,0 +1,110 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.support; + +import com.google.common.io.ByteStreams; +import net.minecraftforge.fml.unsafe.UnsafeHacks; +import org.junit.jupiter.api.extension.*; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.CodeSource; +import java.security.SecureClassLoader; + +/** + * Runs a test method in an entirely isolated {@link ClassLoader}, so you can mess around with as much of + * {@link dan200.computercraft} as you like. + * + * This IS NOT a good idea, but helps us run some tests in parallel while having lots of (terrible) + * global state. + */ +public class IsolatedRunner implements InvocationInterceptor, BeforeEachCallback, AfterEachCallback +{ + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( new Object() ); + + @Override + public void beforeEach( ExtensionContext context ) throws Exception + { + ClassLoader loader = context.getStore( NAMESPACE ).getOrComputeIfAbsent( IsolatedClassLoader.class ); + + // Rename the global thread group to something more obvious. + ThreadGroup group = (ThreadGroup) loader.loadClass( "dan200.computercraft.shared.util.ThreadUtils" ).getMethod( "group" ).invoke( null ); + Field field = ThreadGroup.class.getDeclaredField( "name" ); + UnsafeHacks.setField( field, group, "<" + context.getDisplayName() + ">" ); + } + + @Override + public void afterEach( ExtensionContext context ) throws Exception + { + ClassLoader loader = context.getStore( NAMESPACE ).get( IsolatedClassLoader.class, IsolatedClassLoader.class ); + loader.loadClass( "dan200.computercraft.core.computer.ComputerThread" ) + .getDeclaredMethod( "stop" ) + .invoke( null ); + } + + + @Override + public void interceptTestMethod( Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext ) throws Throwable + { + invocation.skip(); + + ClassLoader loader = extensionContext.getStore( NAMESPACE ).get( IsolatedClassLoader.class, IsolatedClassLoader.class ); + Method method = invocationContext.getExecutable(); + + Class ourClass = loader.loadClass( method.getDeclaringClass().getName() ); + Method ourMethod = ourClass.getDeclaredMethod( method.getName(), method.getParameterTypes() ); + + try + { + ourMethod.invoke( ourClass.getConstructor().newInstance(), invocationContext.getArguments().toArray() ); + } + catch( InvocationTargetException e ) + { + throw e.getTargetException(); + } + } + + private static class IsolatedClassLoader extends SecureClassLoader + { + IsolatedClassLoader() + { + super( IsolatedClassLoader.class.getClassLoader() ); + } + + @Override + public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException + { + synchronized( getClassLoadingLock( name ) ) + { + Class c = findLoadedClass( name ); + if( c != null ) return c; + + if( name.startsWith( "dan200.computercraft." ) ) + { + CodeSource parentSource = getParent().loadClass( name ).getProtectionDomain().getCodeSource(); + + byte[] contents; + try( InputStream stream = getResourceAsStream( name.replace( '.', '/' ) + ".class" ) ) + { + if( stream == null ) throw new ClassNotFoundException( name ); + contents = ByteStreams.toByteArray( stream ); + } + catch( IOException e ) + { + throw new ClassNotFoundException( name, e ); + } + + return defineClass( name, contents, 0, contents.length, parentSource ); + } + } + + return super.loadClass( name, resolve ); + } + } +} diff --git a/src/test/kotlin/dan200/computercraft/client/sound/DfpwmStreamTest.java b/src/test/kotlin/dan200/computercraft/client/sound/DfpwmStreamTest.java deleted file mode 100644 index 52a4958a9..000000000 --- a/src/test/kotlin/dan200/computercraft/client/sound/DfpwmStreamTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.client.sound; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class DfpwmStreamTest -{ - @Test - public void testDecodesBytes() - { - DfpwmStream stream = new DfpwmStream(); - - ByteBuf input = ByteBufAllocator.DEFAULT.buffer(); - input.writeBytes( new byte[] { 43, -31, 33, 44, 30, -16, -85, 23, -3, -55, 46, -70, 68, -67, 74, -96, -68, 16, 94, -87, -5, 87, 11, -16, 19, 92, 85, -71, 126, 5, -84, 64, 17, -6, 85, -11, -1, -87, -12, 1, 85, -56, 33, -80, 82, 104, -93, 17, 126, 23, 91, -30, 37, -32, 117, -72, -58, 11, -76, 19, -108, 86, -65, -10, -1, -68, -25, 10, -46, 85, 124, -54, 15, -24, 43, -94, 117, 63, -36, 15, -6, 88, 87, -26, -83, 106, 41, 13, -28, -113, -10, -66, 119, -87, -113, 68, -55, 40, -107, 62, 20, 72, 3, -96, 114, -87, -2, 39, -104, 30, 20, 42, 84, 24, 47, 64, 43, 61, -35, 95, -65, 42, 61, 42, -50, 4, -9, 81 } ); - stream.push( input ); - - byte[] values = new byte[1024]; - ByteBuffer buffer = stream.read( 2048 ); - assertEquals( 1024, buffer.remaining(), "Must have read 1024 bytes" ); - buffer.get( values ); - assertEquals( 0, buffer.remaining() ); - - assertArrayEquals( - new byte[] { -127, -126, -126, -126, -126, -126, -126, -127, -127, -127, -128, 127, 126, 126, 127, -128, -127, -128, 127, 125, 123, 123, 123, 121, 119, 117, 117, 119, 119, 119, 119, 118, 116, 116, 118, 120, 122, 122, 120, 118, 116, 114, 112, 110, 111, 113, 116, 119, 122, 125, 126, 126, 126, 126, 126, 126, -128, -125, -122, -121, -121, -121, -124, -127, -127, -127, -127, -125, -123, -121, -119, -116, -113, -113, -116, -116, -116, -119, -119, -117, -116, -116, -114, -112, -111, -111, -111, -114, -117, -117, -117, -118, -116, -114, -114, -115, -115, -118, -119, -119, -121, -123, -124, -124, -124, -124, -124, -122, -120, -118, -118, -118, -118, -118, -118, -118, -119, -120, -120, -120, -121, -122, -124, -126, -128, -128, -128, -128, -128, 127, 127, -128, -127, -125, -125, -125, -125, -126, -128, 126, 126, 126, 125, 123, 121, 121, 123, 125, 127, 127, 127, 127, 127, 127, 126, 126, 127, 127, 127, 127, -128, -127, -127, -127, -126, -125, -124, -123, -122, -121, -119, -119, -119, -119, -119, -119, -119, -118, -118, -118, -118, -119, -120, -121, -122, -124, -126, -128, -128, -126, -124, -122, -120, -118, -118, -120, -121, -121, -123, -125, -127, 127, -128, -126, -124, -123, -123, -123, -124, -124, -124, -124, -124, -124, -124, -124, -124, -124, -125, -125, -124, -123, -123, -123, -123, -123, -122, -121, -120, -119, -118, -119, -119, -119, -119, -119, -120, -121, -122, -123, -125, -127, -127, -125, -125, -125, -125, -125, -125, -126, -127, -128, 127, 125, 125, 125, 125, 126, 125, 124, 124, 125, 124, 123, 122, 122, 123, 123, 124, 125, 126, -128, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -125, -124, -123, -122, -121, -120, -118, -116, -114, -112, -110, -108, -108, -111, -112, -112, -113, -113, -113, -113, -115, -115, -115, -115, -114, -113, -112, -110, -110, -112, -114, -116, -118, -120, -123, -123, -123, -124, -124, -124, -124, -124, -124, -126, -128, 126, 126, 126, 124, 124, 126, -128, -128, 126, 124, 122, 122, 122, 120, 118, 116, 114, 112, 113, 115, 116, 117, 117, 117, 117, 115, 115, 115, 115, 115, 114, 112, 110, 110, 110, 110, 112, 112, 112, 114, 115, 114, 113, 113, 114, 114, 116, 117, 116, 115, 115, 116, 115, 114, 113, 113, 115, 117, 119, 121, 123, 123, 123, 125, 127, 127, 127, 127, 125, 123, 123, 125, 125, 125, 127, 127, 127, 127, 125, 125, 125, 124, 122, 122, 124, 126, -128, -128, -128, -128, 126, 126, 126, 125, 123, 121, 119, 117, 115, 115, 117, 119, 121, 122, 122, 122, 122, 124, 126, 126, 124, 122, 120, 121, 123, 125, 126, 126, 126, 126, -128, -128, 126, 124, 124, 126, -128, -126, -126, -127, -127, 127, 125, 123, 121, 118, 118, 118, 118, 120, 121, 121, 123, 125, 126, 124, 124, 124, 122, 120, 118, 116, 116, 116, 116, 116, 114, 115, 115, 115, 117, 117, 117, 117, 117, 117, 117, 119, 121, 123, 125, 127, 127, 127, 127, 127, -127, -127, -127, -126, -124, -122, -120, -118, -116, -114, -112, -110, -108, -106, -106, -109, -110, -108, -106, -104, -105, -106, -104, -102, -100, -101, -104, -105, -103, -100, -100, -100, -101, -102, -102, -105, -108, -111, -114, -114, -114, -117, -117, -117, -117, -115, -113, -112, -112, -112, -113, -113, -114, -114, -116, -118, -119, -117, -115, -113, -111, -111, -114, -115, -115, -116, -116, -118, -119, -117, -115, -113, -111, -109, -109, -112, -115, -118, -121, -124, -127, -127, -126, -126, -124, -121, -118, -115, -115, -115, -116, -116, -116, -119, -122, -122, -122, -125, -128, -128, -128, -128, -126, -125, -125, -125, -125, -123, -121, -121, -121, -119, -117, -115, -113, -110, -110, -113, -116, -119, -120, -118, -115, -115, -115, -113, -110, -107, -104, -101, -101, -105, -109, -113, -117, -118, -119, -119, -116, -112, -109, -106, -105, -109, -114, -115, -112, -112, -113, -113, -114, -111, -108, -108, -109, -109, -110, -111, -114, -115, -113, -113, -116, -117, -115, -112, -109, -109, -110, -108, -108, -109, -110, -110, -111, -111, -112, -112, -112, -113, -111, -111, -112, -112, -115, -116, -116, -117, -117, -119, -119, -119, -119, -117, -117, -119, -121, -123, -125, -127, -127, -127, 127, 127, -127, -125, -123, -121, -119, -117, -116, -119, -122, -122, -122, -122, -120, -120, -121, -119, -117, -115, -115, -116, -114, -112, -110, -108, -108, -108, -106, -104, -102, -103, -103, -101, -99, -100, -101, -102, -105, -106, -106, -107, -107, -108, -106, -104, -102, -100, -101, -104, -107, -107, -107, -110, -111, -111, -114, -117, -117, -117, -118, -118, -121, -122, -122, -124, -125, -123, -123, -125, -127, -127, -127, -127, -127, 127, 127, 127, 127, 127, 127, -128, 127, 127, -128, -128, -127, -126, -125, -124, -125, -127, 127, 125, 125, 125, 125, 126, 125, 124, 122, 120, 118, 118, 118, 116, 116, 116, 116, 118, 118, 117, 116, 114, 112, 110, 108, 106, 104, 102, 100, 101, 101, 102, 102, 103, 103, 101, 102, 104, 106, 106, 106, 106, 104, 104, 104, 104, 105, 105, 106, 106, 107, 108, 109, 111, 113, 115, 117, 119, 121, 121, 119, 119, 119, 117, 115, 113, 111, 112, 114, 115, 113, 114, 114, 114, 116, 118, 120, 121, 119, 117, 115, 113, 114, 114, 115, 115, 113, 111, 109, 110, 110, 111, 111, 112, 112, 110, 108, 106, 107, 107, 107, 107, 107, 108, 107, 106, 104, 104, 106, 106, 104, 102, 103, 105, 107, 109, 110, 111, 111, 109, 107, 105, 103, 101, 99, 97, 98, 99, 100, 102, 103, 104, 104, 105, 105, 103, 104, 104, 104, 106, 108, 110, 110, 108, 108, 108, 108, 110, 112, 112, 112, 114, 116, 118, 120, 122, 124, 124, 124, 124, 124, 126, -128, -126, -124, -122, -122, -123, -123, -123, -123, -123, -123, -123, -123, -125, -125, -125, -125, -124, -123, -122, -123, -125, -127, -127, -127, -127, -127, -127, -127, -128, 127, 127, -128, -127, -127, -128, -128, -127, -127, -128, -128, -128, 127, 126, 125, 124, 124, 126, -128, -128, -128, -127, -125, -123, -121, -121, -123, -125, -125, -125, -125, -125 }, - values, - "Decoded values must match." - ); - } -} diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..33ac738be --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.config.dynamic.factor=4 diff --git a/src/test/resources/test-rom/spec/apis/textutils_spec.lua b/src/test/resources/test-rom/spec/apis/textutils_spec.lua index 71f426366..1bf3d7402 100644 --- a/src/test/resources/test-rom/spec/apis/textutils_spec.lua +++ b/src/test/resources/test-rom/spec/apis/textutils_spec.lua @@ -177,8 +177,15 @@ describe("The textutils library", function() expect(textutils.unserializeJSON("null", { parse_null = false })):eq(nil) end) - it("an empty array", function() - expect(textutils.unserializeJSON("[]", { parse_null = false })):eq(textutils.empty_json_array) + it("an empty array when parse_empty_array is true", function() + expect(textutils.unserializeJSON("[]")):eq(textutils.empty_json_array) + expect(textutils.unserializeJSON("[]", { parse_empty_array = true })):eq(textutils.empty_json_array) + end) + + it("an empty array when parse_empty_array is false", function() + expect(textutils.unserializeJSON("[]", { parse_empty_array = false })) + :ne(textutils.empty_json_array) + :same({}) end) it("basic objects", function() diff --git a/src/testMod/java/dan200/computercraft/export/Exporter.java b/src/testMod/java/dan200/computercraft/export/Exporter.java new file mode 100644 index 000000000..9b5cc515c --- /dev/null +++ b/src/testMod/java/dan200/computercraft/export/Exporter.java @@ -0,0 +1,153 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.export; + +import com.google.common.io.MoreFiles; +import com.google.common.io.RecursiveDeleteOption; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.mojang.blaze3d.systems.RenderSystem; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.ingame.mod.TestMod; +import net.minecraft.client.Minecraft; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.*; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.StringTextComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.ClientChatEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.ForgeRegistries; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +/** + * Provides a {@literal /ccexport } command which exports icons and recipes for all ComputerCraft items. + */ +@Mod.EventBusSubscriber( modid = TestMod.MOD_ID, value = Dist.CLIENT ) +public class Exporter +{ + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + @SubscribeEvent + public static void onClientCommands( ClientChatEvent event ) + { + String prefix = "/ccexport"; + if( !event.getMessage().startsWith( prefix ) ) return; + event.setCanceled( true ); + + Path output = new File( event.getMessage().substring( prefix.length() ).trim() ).getAbsoluteFile().toPath(); + if( !Files.isDirectory( output ) ) + { + Minecraft.getInstance().gui.getChat().addMessage( new StringTextComponent( "Output path does not exist" ) ); + return; + } + + RenderSystem.assertThread( RenderSystem::isOnRenderThread ); + try( ImageRenderer renderer = new ImageRenderer() ) + { + export( output, renderer ); + } + catch( IOException e ) + { + throw new UncheckedIOException( e ); + } + + Minecraft.getInstance().gui.getChat().addMessage( new StringTextComponent( "Export finished!" ) ); + } + + private static void export( Path root, ImageRenderer renderer ) throws IOException + { + JsonDump dump = new JsonDump(); + + Set items = new HashSet<>(); + + // First find all CC items + for( Item item : ForgeRegistries.ITEMS.getValues() ) + { + if( item.getRegistryName().getNamespace().equals( ComputerCraft.MOD_ID ) ) items.add( item ); + } + + // Now find all CC recipes. + for( ICraftingRecipe recipe : Minecraft.getInstance().level.getRecipeManager().getAllRecipesFor( IRecipeType.CRAFTING ) ) + { + ItemStack result = recipe.getResultItem(); + if( !result.getItem().getRegistryName().getNamespace().equals( ComputerCraft.MOD_ID ) ) continue; + if( result.hasTag() ) + { + ComputerCraft.log.warn( "Skipping recipe {} as it has NBT", recipe.getId() ); + continue; + } + + if( recipe instanceof ShapedRecipe ) + { + JsonDump.Recipe converted = new JsonDump.Recipe( result ); + + ShapedRecipe shaped = (ShapedRecipe) recipe; + for( int x = 0; x < shaped.getWidth(); x++ ) + { + for( int y = 0; y < shaped.getHeight(); y++ ) + { + Ingredient ingredient = shaped.getIngredients().get( x + y * shaped.getWidth() ); + if( ingredient.isEmpty() ) continue; + + converted.setInput( x + y * 3, ingredient, items ); + } + } + + dump.recipes.put( recipe.getId().toString(), converted ); + } + else if( recipe instanceof ShapelessRecipe ) + { + JsonDump.Recipe converted = new JsonDump.Recipe( result ); + + ShapelessRecipe shapeless = (ShapelessRecipe) recipe; + NonNullList ingredients = shapeless.getIngredients(); + for( int i = 0; i < ingredients.size(); i++ ) + { + converted.setInput( i, ingredients.get( i ), items ); + } + + dump.recipes.put( recipe.getId().toString(), converted ); + } + else + { + ComputerCraft.log.info( "Don't know how to handle recipe {}", recipe ); + } + } + + Path itemDir = root.resolve( "items" ); + if( Files.exists( itemDir ) ) MoreFiles.deleteRecursively( itemDir, RecursiveDeleteOption.ALLOW_INSECURE ); + + renderer.setupState(); + for( Item item : items ) + { + ItemStack stack = new ItemStack( item ); + dump.itemNames.put( item.getRegistryName().toString(), stack.getHoverName().getString() ); + + ResourceLocation location = item.getRegistryName(); + renderer.captureRender( itemDir.resolve( location.getNamespace() ).resolve( location.getPath() + ".png" ), + () -> Minecraft.getInstance().getItemRenderer().renderAndDecorateFakeItem( stack, 0, 0 ) + ); + } + renderer.clearState(); + + try( Writer writer = Files.newBufferedWriter( root.resolve( "index.json" ) ) ) + { + GSON.toJson( dump, writer ); + } + } +} diff --git a/src/testMod/java/dan200/computercraft/export/ImageRenderer.java b/src/testMod/java/dan200/computercraft/export/ImageRenderer.java new file mode 100644 index 000000000..cbc7eb735 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/export/ImageRenderer.java @@ -0,0 +1,84 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.export; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.texture.NativeImage; +import net.minecraft.client.shader.Framebuffer; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Utilities for saving OpenGL output to an image rather than displaying it on the screen. + */ +public class ImageRenderer implements AutoCloseable +{ + public static final int WIDTH = 64; + public static final int HEIGHT = 64; + + private final Framebuffer framebuffer = new Framebuffer( WIDTH, HEIGHT, true, Minecraft.ON_OSX ); + private final NativeImage image = new NativeImage( WIDTH, HEIGHT, Minecraft.ON_OSX ); + + public ImageRenderer() + { + framebuffer.setClearColor( 0, 0, 0, 0 ); + framebuffer.clear( Minecraft.ON_OSX ); + } + + public void setupState() + { + RenderSystem.matrixMode( GL11.GL_PROJECTION ); + RenderSystem.pushMatrix(); + RenderSystem.loadIdentity(); + RenderSystem.ortho( 0, 16, 16, 0, 1000, 3000 ); + + RenderSystem.matrixMode( GL11.GL_MODELVIEW ); + RenderSystem.pushMatrix(); + RenderSystem.loadIdentity(); + RenderSystem.translatef( 0, 0, -2000f ); + + FogRenderer.setupNoFog(); + } + + public void clearState() + { + RenderSystem.matrixMode( GL11.GL_PROJECTION ); + RenderSystem.popMatrix(); + + RenderSystem.matrixMode( GL11.GL_MODELVIEW ); + RenderSystem.popMatrix(); + } + + public void captureRender( Path output, Runnable render ) throws IOException + { + Files.createDirectories( output.getParent() ); + + framebuffer.bindWrite( true ); + RenderSystem.clear( GL12.GL_COLOR_BUFFER_BIT | GL12.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX ); + render.run(); + framebuffer.unbindWrite(); + + framebuffer.bindRead(); + image.downloadTexture( 0, false ); + image.flipY(); + framebuffer.unbindRead(); + + image.writeToFile( output ); + } + + @Override + public void close() + { + image.close(); + framebuffer.destroyBuffers(); + } +} diff --git a/src/testMod/java/dan200/computercraft/export/JsonDump.java b/src/testMod/java/dan200/computercraft/export/JsonDump.java new file mode 100644 index 000000000..8b2431fb2 --- /dev/null +++ b/src/testMod/java/dan200/computercraft/export/JsonDump.java @@ -0,0 +1,65 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.export; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.item.crafting.Ingredient; + +import java.util.*; + +public class JsonDump +{ + public Map itemNames = new TreeMap<>(); + public Map recipes = new TreeMap<>(); + + public static class Recipe + { + public final String[][] inputs = new String[9][]; + public String output; + public int count; + + public Recipe( ItemStack output ) + { + this.output = output.getItem().getRegistryName().toString(); + count = output.getCount(); + } + + public void setInput( int pos, Ingredient ingredient, Set trackedItems ) + { + if( ingredient.isEmpty() ) return; + + ItemStack[] items = ingredient.getItems(); + + // First try to simplify some tags to something easier. + for( ItemStack stack : items ) + { + Item item = stack.getItem(); + if( !canonicalItem.contains( item ) ) continue; + + trackedItems.add( item ); + inputs[pos] = new String[] { item.getRegistryName().toString() }; + return; + } + + String[] itemIds = new String[items.length]; + for( int i = 0; i < items.length; i++ ) + { + Item item = items[i].getItem(); + trackedItems.add( item ); + itemIds[i] = item.getRegistryName().toString(); + } + Arrays.sort( itemIds ); + + inputs[pos] = itemIds; + } + + private static final Set canonicalItem = new HashSet<>( Arrays.asList( + Items.GLASS_PANE, Items.STONE, Items.CHEST + ) ); + } +} diff --git a/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt b/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt index f37337b25..4f59fcaae 100644 --- a/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt +++ b/src/testMod/java/dan200/computercraft/ingame/TurtleTest.kt @@ -1,10 +1,11 @@ package dan200.computercraft.ingame -import dan200.computercraft.ingame.api.GameTest -import dan200.computercraft.ingame.api.GameTestHelper +import dan200.computercraft.api.ComputerCraftAPI +import dan200.computercraft.api.detail.BasicItemDetailProvider +import dan200.computercraft.ingame.api.* import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT -import dan200.computercraft.ingame.api.sequence -import dan200.computercraft.ingame.api.thenComputerOk +import dan200.computercraft.shared.media.items.ItemPrintout +import net.minecraft.item.ItemStack class Turtle_Test { @GameTest(timeoutTicks = COMPUTER_TIMEOUT) @@ -72,4 +73,25 @@ fun Use_compostors(helper: GameTestHelper) = helper.sequence { thenComputerOk() */ @GameTest fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { thenComputerOk() } + + /** + * Checks turtles can use IDetailProviders by getting details for a printed page. + */ + @GameTest(timeoutTicks = COMPUTER_TIMEOUT) + fun Item_detail_provider(helper: GameTestHelper) = helper.sequence { + this + .thenComputerOk(marker = "initial") + .thenExecute { + // Register a dummy provider for printout items + ComputerCraftAPI.registerDetailProvider( + ItemStack::class.java, + object : BasicItemDetailProvider("printout", ItemPrintout::class.java) { + override fun provideDetails(data: MutableMap, stack: ItemStack, item: ItemPrintout) { + data["type"] = item.type.toString(); + } + } + ) + } + .thenComputerOk() + } } diff --git a/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt b/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt index 360b933ae..9f34b99c1 100644 --- a/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt +++ b/src/testMod/java/dan200/computercraft/ingame/api/TestExtensions.kt @@ -23,9 +23,11 @@ * Custom timeouts for various test types. */ object Timeouts { - const val COMPUTER_TIMEOUT: Int = 200 + private const val SECOND: Int = 20 - const val CLIENT_TIMEOUT: Int = 400 + const val COMPUTER_TIMEOUT: Int = SECOND * 15 + + const val CLIENT_TIMEOUT: Int = SECOND * 20 } /** diff --git a/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java b/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java index ba5709fe8..063a142f0 100644 --- a/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java +++ b/src/testMod/java/dan200/computercraft/ingame/mod/Copier.java @@ -6,6 +6,7 @@ package dan200.computercraft.ingame.mod; import com.google.common.io.MoreFiles; +import com.google.common.io.RecursiveDeleteOption; import java.io.IOException; import java.nio.file.FileVisitResult; @@ -55,7 +56,7 @@ public static void replicate( Path from, Path to ) throws IOException public static void replicate( Path from, Path to, Predicate check ) throws IOException { - if( Files.exists( to ) ) MoreFiles.deleteRecursively( to ); + if( Files.exists( to ) ) MoreFiles.deleteRecursively( to, RecursiveDeleteOption.ALLOW_INSECURE ); Files.walkFileTree( from, new Copier( from, to, check ) ); } } diff --git a/src/testMod/server-files/computers/computer/0/startup.lua b/src/testMod/server-files/computers/computer/0/startup.lua index b0b815745..b67ad6af7 100644 --- a/src/testMod/server-files/computers/computer/0/startup.lua +++ b/src/testMod/server-files/computers/computer/0/startup.lua @@ -4,6 +4,14 @@ if label == nil then return test.fail("Label a computer to use it.") end local fn, err = loadfile("tests/" .. label .. ".lua", nil, _ENV) if not fn then return test.fail(err) end +local source = "@" .. label .. ".lua" +debug.sethook(function() + local i = debug.getinfo(2, "lS") + if i.source == source and i.currentline then + test.log("At line " .. i.currentline) + end +end, "l") + local ok, err = pcall(fn) if not ok then return test.fail(err) end diff --git a/src/testMod/server-files/computers/computer/0/tests/turtle_test.item_detail_provider.lua b/src/testMod/server-files/computers/computer/0/tests/turtle_test.item_detail_provider.lua new file mode 100644 index 000000000..76f4749f1 --- /dev/null +++ b/src/testMod/server-files/computers/computer/0/tests/turtle_test.item_detail_provider.lua @@ -0,0 +1,7 @@ +test.ok("initial") + +local details = turtle.getItemDetail(1, true) + +test.assert(details, "Has details") +test.assert(details.printout, "Has printout meta") +test.eq("PAGE", details.printout.type) diff --git a/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt b/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt new file mode 100644 index 000000000..986ef80ac --- /dev/null +++ b/src/testMod/server-files/structures/turtle_test.item_detail_provider.snbt @@ -0,0 +1,196 @@ +{ + size: [3, 3, 3], + entities: [], + blocks: [ + { + pos: [0, 0, 0], + state: 0 + }, + { + pos: [0, 0, 1], + state: 0 + }, + { + pos: [0, 0, 2], + state: 0 + }, + { + pos: [1, 0, 0], + state: 0 + }, + { + pos: [1, 0, 1], + state: 0 + }, + { + pos: [1, 0, 2], + state: 0 + }, + { + pos: [2, 0, 0], + state: 0 + }, + { + pos: [2, 0, 1], + state: 0 + }, + { + pos: [2, 0, 2], + state: 0 + }, + { + pos: [0, 1, 0], + state: 1 + }, + { + pos: [0, 1, 1], + state: 1 + }, + { + pos: [0, 1, 2], + state: 1 + }, + { + pos: [1, 1, 1], + state: 1 + }, + { + pos: [1, 1, 2], + state: 1 + }, + { + pos: [2, 1, 0], + state: 1 + }, + { + pos: [2, 1, 1], + state: 1 + }, + { + pos: [2, 1, 2], + state: 1 + }, + { + pos: [0, 2, 0], + state: 1 + }, + { + pos: [0, 2, 1], + state: 1 + }, + { + pos: [0, 2, 2], + state: 1 + }, + { + pos: [1, 2, 0], + state: 1 + }, + { + pos: [1, 2, 1], + state: 1 + }, + { + pos: [1, 2, 2], + state: 1 + }, + { + pos: [2, 2, 0], + state: 1 + }, + { + pos: [2, 2, 1], + state: 1 + }, + { + pos: [2, 2, 2], + state: 1 + }, + { + nbt: { + Owner: { + UpperId: 4039158846114182220L, + LowerId: -6876936588741668278L, + Name: "Dev" + }, + Fuel: 0, + Label: "turtle_test.item_detail_provider", + Slot: 0, + Items: [ + { + Slot: 0b, + id: "computercraft:printed_page", + Count: 1b, + tag: { + Text9: " ", + Text8: " ", + Text7: " ", + Text6: " ", + Text5: " ", + Text4: " ", + Text3: " ", + Text2: " ", + Text1: " ", + Text0: "Example ", + Color20: "fffffffffffffffffffffffff", + Text20: " ", + Title: "Example page", + Text18: " ", + Text19: " ", + Text16: " ", + Color2: "fffffffffffffffffffffffff", + Text17: " ", + Color1: "fffffffffffffffffffffffff", + Text14: " ", + Color0: "fffffffffffffffffffffffff", + Text15: " ", + Pages: 1, + Text12: " ", + Color6: "fffffffffffffffffffffffff", + Text13: " ", + Color5: "fffffffffffffffffffffffff", + Text10: " ", + Color4: "fffffffffffffffffffffffff", + Color11: "fffffffffffffffffffffffff", + Text11: " ", + Color3: "fffffffffffffffffffffffff", + Color10: "fffffffffffffffffffffffff", + Color13: "fffffffffffffffffffffffff", + Color9: "fffffffffffffffffffffffff", + Color12: "fffffffffffffffffffffffff", + Color8: "fffffffffffffffffffffffff", + Color15: "fffffffffffffffffffffffff", + Color7: "fffffffffffffffffffffffff", + Color14: "fffffffffffffffffffffffff", + Color17: "fffffffffffffffffffffffff", + Color16: "fffffffffffffffffffffffff", + Color19: "fffffffffffffffffffffffff", + Color18: "fffffffffffffffffffffffff" + } + } + ], + id: "computercraft:turtle_normal", + ComputerId: 0, + On: 1b + }, + pos: [1, 1, 0], + state: 2 + } + ], + palette: [ + { + Name: "minecraft:polished_andesite" + }, + { + Name: "minecraft:air" + }, + { + Properties: { + waterlogged: "false", + facing: "south" + }, + Name: "computercraft:turtle_normal" + } + ], + DataVersion: 2586 +} diff --git a/src/web/components/Recipe.tsx b/src/web/components/Recipe.tsx new file mode 100644 index 000000000..1dbf34c4f --- /dev/null +++ b/src/web/components/Recipe.tsx @@ -0,0 +1,44 @@ +import type { FunctionComponent } from "react"; +import { createElement as h } from "react"; +import useExport from "./WithExport.js"; + +const Item: FunctionComponent<{ item: string }> = ({ item }) => { + const data = useExport(); + const itemName = data.itemNames[item]; + + return {itemName} +}; + +const Arrow: FunctionComponent = (props) => + + + +; + +const Recipe: FunctionComponent<{ recipe: string }> = ({ recipe }) => { + const data = useExport(); + const recipeInfo = data.recipes[recipe]; + if (!recipeInfo) throw Error("Cannot find recipe for " + recipe); + + return
    +
    + {data.itemNames[recipeInfo.output]} +
    + {recipeInfo.inputs.map((items, i) =>
    {items && }
    )} +
    + +
    + + {recipeInfo.count > 1 && {recipeInfo.count}} +
    +
    +
    ; +} +export default Recipe; diff --git a/src/web/components/WithExport.tsx b/src/web/components/WithExport.tsx new file mode 100644 index 000000000..10cfcfb37 --- /dev/null +++ b/src/web/components/WithExport.tsx @@ -0,0 +1,23 @@ +import { createElement as h, useContext, createContext, FunctionComponent, ReactNode } from "react"; + +export type DataExport = { + readonly itemNames: Record, + readonly recipes: Record, +}; + +export type Recipe = { + readonly inputs: Array>, + readonly output: string, + readonly count: number, +}; + +const DataExport = createContext({ + itemNames: {}, + recipes: {}, +}); + +export const useExport = () => useContext(DataExport); +export default useExport; + +export const WithExport: FunctionComponent<{ data: DataExport, children: ReactNode }> = + ({ data, children }) => {children}; diff --git a/src/web/components/support.tsx b/src/web/components/support.tsx new file mode 100644 index 000000000..9cea4b321 --- /dev/null +++ b/src/web/components/support.tsx @@ -0,0 +1,25 @@ +import type { FunctionComponent } from "react"; + +/** + * Wrap a component and ensure that no children are passed to it. + * + * Our custom tags *must* be explicitly closed, as will be parsed as + * (rest of the document). This ensures you've not forgotten to do + * that. + * + * @param component The component to wrap + * @returns A new functional component identical to the previous one + */ +export const noChildren = function (component: FunctionComponent): FunctionComponent { + // I hope that our few remaining friends + // Give up on trying to save us + + const name = component.displayName ?? component.name; + const wrapped: FunctionComponent = props => { + if ((props as any).children) throw Error("Unexpected children in " + name); + + return component(props); + }; + wrapped.displayName = name; + return wrapped; +} diff --git a/src/web/index.tsx b/src/web/index.tsx index e22f7304e..858d3658e 100644 --- a/src/web/index.tsx +++ b/src/web/index.tsx @@ -76,7 +76,9 @@ class Window extends Component { const snippet = element.getAttribute("data-snippet"); if (snippet) this.snippets[snippet] = example; - if (element.getAttribute("data-lua-kind") == "expr") { + // We attempt to pretty-print the result of a function _except_ when the function + // is print. This is pretty ugly, but prevents the confusing trailing "1". + if (element.getAttribute("data-lua-kind") == "expr" && !example.startsWith("print(")) { example = exprTemplate.replace("__expr__", example); } @@ -98,7 +100,7 @@ class Window extends Component {
    :
    ; diff --git a/src/web/styles.css b/src/web/styles.css index a05a697f9..d72373384 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -7,24 +7,23 @@ } /* Pretty tables, mostly inherited from table.definition-list */ -table.pretty-table { +table { border-collapse: collapse; width: 100%; } -table.pretty-table td, table.pretty-table th { +table td, +table th { border: 1px solid #cccccc; padding: 2px 4px; } -table.pretty-table th { +table th { background-color: var(--background-2); } -pre.highlight.highlight-lua { +pre.highlight { position: relative; - background: var(--background-2); - padding: 2px; } .example-run { @@ -40,7 +39,8 @@ pre.highlight.highlight-lua { position: fixed; z-index: 200; top: 0px; - top: 0px;; + top: 0px; + ; } /* Behold, the most cursed CSS! copy-cat's resizing algorithm is a weird, in @@ -80,7 +80,9 @@ pre.highlight.highlight-lua { font-size: 15px; } -.titlebar-close:hover { background: #cc4c4c; } +.titlebar-close:hover { + background: #cc4c4c; +} @media (max-width: 700px) { .computer-container { @@ -88,6 +90,79 @@ pre.highlight.highlight-lua { height: calc(179px + 40px); } - .titlebar { height: 20px; } - .titlebar-close { font-size: 7px; } + .titlebar { + height: 20px; + } + + .titlebar-close { + font-size: 7px; + } +} + +:root { + --recipe-bg: #ddd; + --recipe-fg: #bbb; + --recipe-hover: #aaa; + + --recipe-padding: 4px; + --recipe-size: 32px; +} + + +.recipe-container { + display: flex; + justify-content: center; + margin: 1em 0; +} + +.recipe { + display: inline-grid; + padding: var(--recipe-padding); + + background: var(--recipe-bg); + column-gap: var(--recipe-padding); + row-gap: var(--recipe-padding); + grid-template-rows: auto auto; + grid-template-columns: max-content 1fr max-content; +} + +.recipe-title { + grid-column-start: span 3; +} + +.recipe-inputs { + display: inline-grid; + column-gap: var(--recipe-padding); + row-gap: var(--recipe-padding); + align-items: center; + grid-template-rows: 1fr 1fr 1fr; + grid-template-columns: 1fr 1fr 1fr; +} + +.recipe-item { + padding: var(--recipe-padding); + background-color: var(--recipe-fg); +} + +.recipe-item:hover { + background-color: var(--recipe-hover); +} + +.recipe-item > img { + display: block; + width: var(--recipe-size); + height: var(--recipe-size); + max-width: 10vw; + max-height: 10vw; +} + +.recipe-arrow { + width: var(--recipe-size); + max-width: 10vw; + align-self: center; +} + +.recipe-output { + /* Hrm! */ + align-self: center; } diff --git a/src/web/transform.tsx b/src/web/transform.tsx new file mode 100644 index 000000000..521ee56b8 --- /dev/null +++ b/src/web/transform.tsx @@ -0,0 +1,53 @@ +/** + * Find all HTML files generated by illuaminate and pipe them through a remark. + * + * This performs compile-time syntax highlighting and expands our custom + * components using React SSR. + * + * Yes, this would be so much nicer with next.js. + */ +import * as fs from "fs/promises"; +import globModule from "glob"; +import * as path from "path"; +import { createElement, createElement as h, Fragment } from 'react'; +import { renderToStaticMarkup } from "react-dom/server"; +import rehypeHighlight from "rehype-highlight"; +import rehypeParse from 'rehype-parse'; +import rehypeReact from 'rehype-react'; +import { unified } from 'unified'; +import { promisify } from "util"; +// Our components +import Recipe from "./components/Recipe.js"; +import { noChildren } from "./components/support.js"; +import { DataExport, WithExport } from "./components/WithExport.js"; + +const glob = promisify(globModule); + +(async () => { + const base = "build/docs/lua"; + + const processor = unified() + .use(rehypeParse, { emitParseErrors: true }) + .use(rehypeHighlight, { prefix: "" }) + .use(rehypeReact, { + createElement, + Fragment, + passNode: false, + components: { + ['mc-recipe']: noChildren(Recipe), + ['mcrecipe']: noChildren(Recipe), + } as any + }); + + const dataExport = JSON.parse(await fs.readFile("src/generated/export/index.json", "utf-8")) as DataExport; + + for (const file of await glob(base + "/**/*.html")) { + const contents = await fs.readFile(file, "utf-8"); + + const { result } = await processor.process(contents); + + const outputPath = path.resolve("build/docs/site", path.relative(base, file)); + await fs.mkdir(path.dirname(outputPath), { recursive: true }); + await fs.writeFile(outputPath, "" + renderToStaticMarkup({result})); + } +})(); diff --git a/tsconfig.json b/tsconfig.json index a1a8a0ed1..2e161c2c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,9 @@ "noFallthroughCasesInSwitch": true, "importsNotUsedAsValues": "error", "forceConsistentCasingInFileNames": true, + + // Needed for some of our internal stuff. + "allowSyntheticDefaultImports": true, }, "include": [ "src/web",
    Default Colors