1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Merge branch 'mc-1.16.x' into mc-1.18.x

This commit is contained in:
Jonathan Coates 2022-10-01 12:36:09 +01:00
commit 08895cdecc
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
32 changed files with 342 additions and 150 deletions

View File

@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: ComputerCraft Discord
url: https://discord.computercraft.cc
about: Get help on the ComputerCraft Discord.
- name: GitHub Discussions
url: https://github.com/cc-tweaked/CC-Tweaked/discussions
about: Or ask questions on GitHub Discussions.
about: Ask questions on GitHub Discussions.

View File

@ -15,7 +15,7 @@ jobs:
with:
java-version: 8
- name: Cache gradle dependencies
- name: Cache Gradle dependencies
uses: actions/cache@v2
with:
path: ~/.gradle/caches
@ -32,7 +32,7 @@ jobs:
run: |
./gradlew assemble || ./gradlew assemble
./gradlew downloadAssets || ./gradlew downloadAssets
xvfb-run ./gradlew build
./gradlew build
- name: Upload Jar
uses: actions/upload-artifact@v2
@ -40,31 +40,12 @@ jobs:
name: CC-Tweaked
path: build/libs
- name: Upload Screnshots
uses: actions/upload-artifact@v2
with:
name: Screenshots
path: test-files/client/screenshots
if-no-files-found: ignore
retention-days: 5
if: failure()
- name: Upload Coverage
- name: Upload coverage
uses: codecov/codecov-action@v2
- name: Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
- name: Cache pre-commit
uses: actions/cache@v2
with:
path: ~/.cache/pre-commit
key: ${{ runner.os }}-pre-commit-${{ hashFiles('config/pre-commit/config.yml') }}
restore-keys: |
${{ runner.os }}-pre-commit-
- name: Run linters
run: |
pip install pre-commit
pre-commit run --config config/pre-commit/config.yml --show-diff-on-failure --all --color=always
uses: pre-commit/action@v3.0.0

View File

@ -26,12 +26,6 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Setup illuaminate
run: |
test -d bin || mkdir bin
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
chmod +x bin/illuaminate
- name: Setup node
run: npm ci

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
/classes
/logs
/build
/buildSrc/build
/out
/doc/out/
/node_modules

View File

@ -17,6 +17,6 @@ vscode:
tasks:
- name: Setup pre-commit hool
init: pre-commit install --config config/pre-commit/config.yml --allow-missing-config
init: pre-commit install --allow-missing-config
- name: Install npm packages
init: npm ci

View File

@ -41,8 +41,8 @@ repos:
- id: illuaminate
name: Check Lua code
files: ".*\\.(lua|java|md)"
language: script
entry: config/pre-commit/illuaminate-lint.sh
language: system
entry: ./gradlew lintLua -i
pass_filenames: false
require_serial: true

View File

@ -39,40 +39,30 @@ ### Code linters
- **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or
`./gradle check`.
- **[illuaminate]:** Checks Lua code for semantic and styleistic issues. See [the usage section][illuaminate-usage] for
how to download and run it. You may need to generate the Java documentation stubs (see "Documentation" below) for all
lints to pass.
- **[illuaminate]:** Checks Lua code for semantic and styleistic issues. This can be run with `./gradlew lintLua`.
### Documentation
When writing documentation for [CC: Tweaked's documentation website][docs], it may be useful to build the documentation
and preview it yourself before submitting a PR.
Building all documentation is, sadly, a multi-stage process (though this is largely hidden by Gradle). First we need to
convert Java doc-comments into Lua ones, we also generate some Javascript to embed. All of this is then finally fed into
illuaminate, which spits out our HTML.
Our documentation generation pipeline is rather complex, and involves invoking several external tools. Most of this
complexity is hidden by Gradle, but you will need to perform some initial setup:
#### Setting up the tooling
For various reasons, getting the environment set up to build documentation can be pretty complex. I'd quite like to
automate this via Docker and/or nix in the future, but this needs to be done manually for now.
- Install [Node/npm][node].
- Run `npm ci` to install our Node dependencies.
This tooling is only needed if you need to build the whole website. If you just want to generate the Lua stubs, you can
skp this section.
- Install Node/npm and install our Node packages with `npm ci`.
- Install [illuaminate][illuaminate-usage] as described above.
#### Building documentation
Gradle should be your entrypoint to building most documentation. There's two tasks which are of interest:
- `./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/site/`.
You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting
HTML into `./build/docs/site`.
#### Writing documentation
illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as
[ldoc][ldoc]. Documentation comments are written in Markdown,
Our markdown engine does _not_ support GitHub flavoured markdown, and so does not support all the features one might
expect (such as tables). It is very much recommended that you build and preview the docs locally first.
expect. It is recommended that you build and preview the docs locally first.
When iterating on documentation, you can get Gradle to rebuild the website every time a file changes by running
`./gradlew docWebsite -t`. This will take a couple of seconds to run, but definitely beats running it manually!
### Testing
Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the
@ -90,11 +80,10 @@ ### Testing
These tests are run by the '"Core" Java' test suite, and so are also run with `./gradlew test`.
- In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server and client,
using [the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
- In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server, using
the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
These are run by `./gradlew testClient` and `./gradlew testServer`. You may want to run the client under `xvfb-run`
or similar when running in a headless environment.
These tests are run with `./gradlew testServer`.
## CraftOS tests
CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of
@ -107,9 +96,9 @@ ## CraftOS tests
[community]: README.md#Community "Get in touch with the community."
[checkstyle]: https://checkstyle.org/
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
[illuaminate-usage]: https://github.com/SquidDev/illuaminate/blob/master/README.md#usage "Installing Illuaminate"
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
[ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator."
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
[node]: https://nodejs.org/en/ "Node.js"

View File

@ -13,9 +13,8 @@ ## Contributing
## Community
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.computercraft.cc)!
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=computercraft), if that's
more your cup of tea.
ComputerCraft, do check out our [forum] and [GitHub discussions page][GitHub discussions]! There's also a fairly
populated, albeit quiet [IRC channel][irc], if that's more your cup of tea.
We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website").
@ -52,3 +51,6 @@ ## Using
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
[forum]: https://forums.computercraft.cc/
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

@ -11,9 +11,12 @@
id "org.spongepowered.mixin" version "0.7.+"
id "org.parchmentmc.librarian.forgegradle" version "1.+"
id "com.github.johnrengelman.shadow" version "7.1.2"
id "cc-tweaked.illuaminate"
}
import org.apache.tools.ant.taskdefs.condition.Os
import cc.tweaked.gradle.IlluaminateExec
import cc.tweaked.gradle.IlluaminateExecToDir
version = mod_version
@ -169,6 +172,10 @@ accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg')
cctJavadoc 'cc.tweaked:cct-javadoc:1.4.7'
}
illuaminate {
version.set("0.1.0-3-g0f40379")
}
// Compile tasks
javadoc {
@ -314,32 +321,36 @@ List<String> mkCommand(String command) {
commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js')
}
def illuaminateDocs = tasks.register("illuaminateDocs", Exec.class) {
def illuaminateDocs = tasks.register("illuaminateDocs", IlluaminateExecToDir.class) {
group = "documentation"
description = "Generates docs using Illuaminate"
dependsOn(rollup, luaJavadoc)
dependsOn(rollup)
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom")
// Config files
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
inputs.dir("$buildDir/docs/luaJavadoc")
// Sources
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
// Additional assets
inputs.file("$buildDir/rollup/index.js").withPropertyName("scripts")
inputs.file("src/web/styles.css").withPropertyName("styles")
outputs.dir("$buildDir/docs/lua")
commandLine mkCommand('"bin/illuaminate" doc-gen')
// Output directory. Also defined in illuaminate.sexp and transform.tsx
output.set(new File(buildDir, "docs/lua"))
args = ["doc-gen"]
}
def jsxDocs = tasks.register("jsxDocs", Exec) {
group = "documentation"
description = "Post-processes documentation to statically render some dynamic content."
dependsOn(illuaminateDocs)
inputs.files(fileTree("src/web")).withPropertyName("sources")
inputs.file("src/generated/export/index.json").withPropertyName("export")
inputs.file("package-lock.json").withPropertyName("package-lock.json")
inputs.file("tsconfig.json").withPropertyName("Typescript config")
inputs.files(fileTree("$buildDir/docs/lua"))
inputs.files(illuaminateDocs)
outputs.dir("$buildDir/docs/site")
commandLine mkCommand('"node_modules/.bin/ts-node" -T --esm src/web/transform.tsx')
@ -403,6 +414,23 @@ commandLine mkCommand('"node_modules/.bin/ts-node" -T --esm src/web/transform.ts
check.dependsOn("licenseCheck")
def lintLua = tasks.register("lintLua", IlluaminateExec.class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
// Sources
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = ["lint"]
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
def testServerClassDumpDir = new File(buildDir, "jacocoClassDump/runTestServer")
def testServer = tasks.register("testServer", JavaExec.class) {

18
buildSrc/build.gradle.kts Normal file
View File

@ -0,0 +1,18 @@
plugins {
`java-gradle-plugin`
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
gradlePlugin {
plugins {
register("cc-tweaked.illuaminate") {
id = "cc-tweaked.illuaminate"
implementationClass = "cc.tweaked.gradle.IlluaminatePlugin"
}
}
}

View File

@ -0,0 +1,11 @@
package cc.tweaked.gradle
import org.gradle.api.provider.Property
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.OutputDirectory
import java.io.File
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
@get:OutputDirectory
abstract val output: Property<File>
}

View File

@ -0,0 +1,121 @@
package cc.tweaked.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import java.io.File
abstract class IlluaminateExtension {
/** The version of illuaminate to use. */
abstract val version: Property<String>
/** The path to illuaminate. If not given, illuaminate will be downloaded automatically. */
abstract val file: Property<File>
}
class IlluaminatePlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create("illuaminate", IlluaminateExtension::class.java)
extension.file.convention(setupDependency(project, extension.version))
project.tasks.register(SetupIlluaminate.NAME, SetupIlluaminate::class.java) {
file.set(extension.file.map { it.absolutePath })
}
}
/** Set up a repository for illuaminate and download our binary from it. */
private fun setupDependency(project: Project, version: Provider<String>): Provider<File> {
project.repositories.ivy {
name = "Illuaminate"
setUrl("https://squiddev.cc/illuaminate/bin/")
patternLayout {
artifact("[revision]/[artifact]-[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("cc.squiddev", "illuaminate")
}
}
return version.map {
val dep = illuaminateArtifact(project, it)
val configuration = project.configurations.detachedConfiguration(dep)
configuration.isTransitive = false
configuration.resolve().single()
}
}
/** Define a dependency for illuaminate from a version number and the current operating system. */
private fun illuaminateArtifact(project: Project, version: String): Dependency {
val osName = System.getProperty("os.name").toLowerCase()
val (os, suffix) = when {
osName.contains("windows") -> Pair("windows", ".exe")
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
osName.contains("linux") -> Pair("linux", "")
else -> error("Unsupported OS $osName for illuaminate")
}
val osArch = System.getProperty("os.arch").toLowerCase()
val arch = when {
osArch == "arm" || osArch.startsWith("aarch") -> error("Unsupported architecture '$osArch' for illuaminate")
osArch.contains("64") -> "x86_64"
else -> error("Unsupported architecture $osArch for illuaminate")
}
return project.dependencies.create(
mapOf(
"group" to "cc.squiddev",
"name" to "illuaminate",
"version" to version,
"ext" to "$os-$arch$suffix",
),
)
}
}
private val Task.illuaminatePath: String? // "?" needed to avoid overload ambiguity in setExecutable below.
get() = project.extensions.getByType(IlluaminateExtension::class.java).file.get().absolutePath
/** Prepares illuaminate for being run. This simply requests the dependency and then marks it as executable. */
abstract class SetupIlluaminate : DefaultTask() {
@get:Input
abstract val file: Property<String>
@TaskAction
fun setExecutable() {
val file = File(this.file.get())
if (file.canExecute()) {
didWork = false
return
}
file.setExecutable(true)
}
companion object {
const val NAME: String = "setupIlluaminate"
}
}
abstract class IlluaminateExec : AbstractExecTask<IlluaminateExec>(IlluaminateExec::class.java) {
init {
dependsOn(SetupIlluaminate.NAME)
executable = illuaminatePath
}
}
abstract class IlluaminateExecToDir : ExecToDir() {
init {
dependsOn(SetupIlluaminate.NAME)
executable = illuaminatePath
}
}

View File

@ -1,16 +0,0 @@
#!/usr/bin/env sh
set -e
test -d bin || mkdir bin
test -f bin/illuaminate || curl -s -obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
chmod +x bin/illuaminate
if [ -n ${GITHUB_ACTIONS+x} ]; then
# Register a problem matcher (see https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md)
# for illuaminate.
echo "::add-matcher::.github/matchers/illuaminate.json"
trap 'echo "::remove-matcher owner=illuaminate::"' EXIT
fi
./gradlew luaJavadoc
bin/illuaminate lint

View File

@ -185,7 +185,7 @@ ## Processing audio
:::note Confused?
Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
cover. That said, don't be afraid to ask on [Discord] or [IRC] either!
cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either!
:::
It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of
@ -200,6 +200,5 @@ ## Processing audio
[PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia"
[Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia"
[Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia"
[Discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord"
[IRC]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

@ -37,8 +37,7 @@ ## Getting Started
Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the
various APIs and peripherals provided by the mod.
If you get stuck, do pop in to the [Minecraft Computer Mod Discord guild][discord] or ComputerCraft's
[IRC channel][irc].
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
## Get Involved
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
@ -51,5 +50,5 @@ ## Get Involved
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
[lua]: https://www.lua.org/ "Lua's main website"
[discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord"
[irc]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

@ -14,7 +14,7 @@ thread, not the whole program.
:::tip
Because sleep internally uses timers, it is a function that yields. This means
that you can use it to prevent "Too long without yielding" errors, however, as
that you can use it to prevent "Too long without yielding" errors. However, as
the minimum sleep time is 0.05 seconds, it will slow your program down.
:::

View File

@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx3G
kotlin.stdlib.default.dependency=false
# Mod properties
mod_version=1.100.9
mod_version=1.100.10
# Minecraft properties (update mods.toml when changing)
mc_version=1.18.2

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -24,7 +24,7 @@ private LuaDateTime()
{
}
static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffset offset ) throws LuaException
static void format( DateTimeFormatterBuilder formatter, String format ) throws LuaException
{
for( int i = 0; i < format.length(); )
{
@ -61,7 +61,7 @@ static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffse
formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.FULL );
break;
case 'c':
format( formatter, "%a %b %e %H:%M:%S %Y", offset );
format( formatter, "%a %b %e %H:%M:%S %Y" );
break;
case 'C':
formatter.appendValueReduced( CENTURY, 2, 2, 0 );
@ -71,13 +71,13 @@ static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffse
break;
case 'D':
case 'x':
format( formatter, "%m/%d/%y", offset );
format( formatter, "%m/%d/%y" );
break;
case 'e':
formatter.padNext( 2 ).appendValue( ChronoField.DAY_OF_MONTH );
break;
case 'F':
format( formatter, "%Y-%m-%d", offset );
format( formatter, "%Y-%m-%d" );
break;
case 'g':
formatter.appendValueReduced( IsoFields.WEEK_BASED_YEAR, 2, 2, 0 );
@ -107,10 +107,10 @@ static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffse
formatter.appendText( ChronoField.AMPM_OF_DAY );
break;
case 'r':
format( formatter, "%I:%M:%S %p", offset );
format( formatter, "%I:%M:%S %p" );
break;
case 'R':
format( formatter, "%H:%M", offset );
format( formatter, "%H:%M" );
break;
case 'S':
formatter.appendValue( ChronoField.SECOND_OF_MINUTE, 2 );
@ -120,7 +120,7 @@ static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffse
break;
case 'T':
case 'X':
format( formatter, "%H:%M:%S", offset );
format( formatter, "%H:%M:%S" );
break;
case 'u':
formatter.appendValue( ChronoField.DAY_OF_WEEK );
@ -212,15 +212,13 @@ private static Boolean getBoolField( Map<?, ?> table, String field ) throws LuaE
throw new LuaException( "field \"" + field + "\" missing in date table" );
}
private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 6 ), x -> (x / 100) % 100 );
private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 99 ), x -> (x / 100) % 100 );
private static final TemporalField ZERO_WEEK = map( WeekFields.SUNDAY_START.dayOfWeek(), ValueRange.of( 0, 6 ), x -> x - 1 );
private static TemporalField map( TemporalField field, ValueRange range, LongUnaryOperator convert )
{
return new TemporalField()
{
private final ValueRange range = ValueRange.of( 0, 99 );
@Override
public TemporalUnit getBaseUnit()
{

View File

@ -493,7 +493,7 @@ public final Object date( Optional<String> formatA, Optional<Long> timeA ) throw
if( format.equals( "*t" ) ) return LuaDateTime.toTable( date, offset, instant );
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
LuaDateTime.format( formatter, format, offset );
LuaDateTime.format( formatter, format );
return formatter.toFormatter( Locale.ROOT ).format( date );
}

View File

@ -323,8 +323,9 @@ public boolean teleportTo( @Nonnull Level world, @Nonnull BlockPos pos )
try
{
// Create a new turtle
if( world.setBlock( pos, newState, 0 ) )
// We use Block.UPDATE_CLIENTS here to ensure that neighbour updates caused in Block.updateNeighbourShapes
// are sent to the client. We want to avoid doing a full block update until the turtle state is copied over.
if( world.setBlock( pos, newState, 2 ) )
{
Block block = world.getBlockState( pos ).getBlock();
if( block == oldBlock.getBlock() )

View File

@ -259,7 +259,7 @@ input should the whole output not fit on the display.
local rows = {}
for i = 1, 30 do rows[i] = {("Row #%d"):format(i), math.random(1, 400)} end
textutils.tabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows))
textutils.pagedTabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows))
]]
function pagedTabulate(...)
return tabulateCommon(true, ...)
@ -749,9 +749,9 @@ suitable for pretty printing.
@usage Demonstrates some of the other options
local tbl = { 1, 2, 3 }
print(textutils.serialize({ tbl, tbl }, { allow_repetitions = true }))
print(textutils.serialise({ tbl, tbl }, { allow_repetitions = true }))
print(textutils.serialize(tbl, { compact = true }))
print(textutils.serialise(tbl, { compact = true }))
]]
function serialize(t, opts)
local tTracking = {}
@ -770,7 +770,7 @@ serialise = serialize -- GB version
--- Converts a serialised string back into a reassembled Lua object.
--
-- This is mainly used together with @{textutils.serialize}.
-- This is mainly used together with @{textutils.serialise}.
--
-- @tparam string s The serialised string to deserialise.
-- @return[1] The deserialised object
@ -807,10 +807,10 @@ unserialise = unserialize -- GB version
-- @throws If the object contains a value which cannot be
-- serialised. This includes functions and tables which appear multiple
-- times.
-- @usage textutils.serializeJSON({ values = { 1, "2", true } })
-- @usage textutils.serialiseJSON({ 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.
-- @see textutils.json_null Use to serialise a JSON `null` value.
-- @see textutils.empty_json_array Use to serialise a JSON empty array.
function serializeJSON(t, bNBTStyle)
expect(1, t, "table", "string", "number", "boolean")
expect(2, bNBTStyle, "boolean", "nil")

View File

@ -1,3 +1,14 @@
# New features in CC: Tweaked 1.100.10
* Mention WAV support in speaker help (MCJack123).
* Add http programs to the path, even when http is not enabled.
Several bug fixes:
* Fix example in textutils.pagedTabulate docs (IvoLeal72).
* Fix help program treating the terminal one line longer than it was.
* Send block updates to client when turtle moves (roland-a).
* Resolve several monitor issues when running Occulus shaders.
# New features in CC: Tweaked 1.100.9
* Add documentation for setting up GPS (Lupus590).

View File

@ -1,5 +1,9 @@
The speaker program plays audio files using speakers attached to this computer.
It supports audio files in a limited number of formats:
* DFPWM: You can convert music to DFPWM with external tools like https://music.madefor.cc.
* WAV: WAV files must be 8-bit PCM or DFPWM, with exactly one channel and a sample rate of 48kHz.
## Examples:
- `speaker play example.dfpwm left` plays the "example.dfpwm" audio file using the speaker on the left of the computer.
- `speaker stop` stops any currently playing audio.
* `speaker play example.dfpwm left` plays the "example.dfpwm" audio file using the speaker on the left of the computer.
* `speaker stop` stops any currently playing audio.

View File

@ -1,15 +1,12 @@
New features in CC: Tweaked 1.100.9
New features in CC: Tweaked 1.100.10
* Add documentation for setting up GPS (Lupus590).
* Add WAV support to the `speaker` program (MCJack123).
* Expose item groups in `getItemDetail` (itisluiz).
* Other fixes to documentation (Erb3, JohnnyIrvin).
* Add Norwegian translation (Erb3).
* Mention WAV support in speaker help (MCJack123).
* Add http programs to the path, even when http is not enabled.
Several bug fixes:
* Fix z-fighting on bold printout borders (toad-dev).
* Fix `term.blit` failing on certain strings.
* Fix `getItemLimit()` using the wrong slot (heap-underflow).
* Increase size of monitor depth blocker.
* Fix example in textutils.pagedTabulate docs (IvoLeal72).
* Fix help program treating the terminal one line longer than it was.
* Send block updates to client when turtle moves (roland-a).
* Resolve several monitor issues when running Occulus shaders.
Type "help changelog" to see the full version history.

View File

@ -146,14 +146,17 @@ end
local contents = file:read("*a")
file:close()
-- Trim trailing newlines from the file to avoid displaying a blank line.
if contents:sub(-1) == "\n" then contents:sub(1, -2) end
local word_wrap = sFile:sub(-3) == ".md" and word_wrap_markdown or word_wrap_basic
local width, height = term.getSize()
local content_height = height - 1 -- Height of the content box.
local lines, fg, bg, sections = word_wrap(contents, width)
local print_height = #lines
-- If we fit within the screen, just display without pagination.
if print_height <= height then
if print_height <= content_height then
local _, y = term.getCursorPos()
for i = 1, print_height do
if y + i - 1 > height then
@ -201,7 +204,7 @@ end
local function draw()
for y = 1, height - 1 do
for y = 1, content_height do
term.setCursorPos(1, y)
if y + offset > print_height then
-- Should only happen if we resize the terminal to a larger one
@ -228,14 +231,14 @@ while true do
if param == keys.up and offset > 0 then
offset = offset - 1
draw()
elseif param == keys.down and offset < print_height - height then
elseif param == keys.down and offset < print_height - content_height then
offset = offset + 1
draw()
elseif param == keys.pageUp and offset > 0 then
offset = math.max(offset - height + 2, 0)
offset = math.max(offset - content_height + 1, 0)
draw()
elseif param == keys.pageDown and offset < print_height - height then
offset = math.min(offset + height - 2, print_height - height)
elseif param == keys.pageDown and offset < print_height - content_height then
offset = math.min(offset + content_height - 1, print_height - content_height)
draw()
elseif param == keys.home then
offset = 0
@ -247,7 +250,7 @@ while true do
offset = sections[current_section + 1].offset
draw()
elseif param == keys["end"] then
offset = print_height - height
offset = print_height - content_height
draw()
elseif param == keys.q then
sleep(0) -- Super janky, but consumes stray "char" events.
@ -257,7 +260,7 @@ while true do
if param < 0 and offset > 0 then
offset = offset - 1
draw()
elseif param > 0 and offset < print_height - height then
elseif param > 0 and offset <= print_height - content_height then
offset = offset + 1
draw()
end
@ -270,7 +273,8 @@ while true do
end
width, height = new_width, new_height
offset = math.max(math.min(offset, print_height - height), 0)
content_height = height - 1
offset = math.max(math.min(offset, print_height - content_height), 0)
draw()
draw_menu()
elseif event == "terminate" then

View File

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

View File

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

View File

@ -1,7 +1,7 @@
local completion = require "cc.shell.completion"
-- Setup paths
local sPath = ".:/rom/programs"
local sPath = ".:/rom/programs:/rom/programs/http"
if term.isColor() then
sPath = sPath .. ":/rom/programs/advanced"
end
@ -19,9 +19,6 @@ end
if commands then
sPath = sPath .. ":/rom/programs/command"
end
if http then
sPath = sPath .. ":/rom/programs/http"
end
shell.setPath(sPath)
help.setPath("/rom/help")

View File

@ -1,8 +1,46 @@
local capture = require "test_helpers".capture_program
local with_window_lines = require "test_helpers".with_window_lines
describe("The help program", function()
local function stub_help(content)
local name = "/help_file.txt"
io.open(name, "wb"):write(content):close()
stub(help, "lookup", function() return name end)
end
local function capture_help(width, height, content)
stub_help(content)
local co = coroutine.create(shell.run)
local window = with_window_lines(width, height, function()
local ok, err = coroutine.resume(co, "help topic")
if not ok then error(err, 0) end
end)
return coroutine.status(co) == "dead", window
end
it("errors when there is no such help file", function()
expect(capture(stub, "help nothing"))
:matches { ok = true, error = "No help available\n", output = "" }
end)
it("prints a short file directly", function()
local dead, output = capture_help(10, 3, "a short\nfile")
expect(dead):eq(true)
expect(output):same {
"a short ",
"file ",
" ",
}
end)
it("launches the viewer for a longer file", function()
local dead, output = capture_help(10, 3, "a longer\nfile\nwith content")
expect(dead):eq(false)
expect(output):same {
"a longer ",
"file ",
"Help: topi",
}
end)
end)

View File

@ -56,7 +56,22 @@ local function with_window(width, height, fn)
return redirect
end
--- Run a function redirecting to a new window with the given dimensions,
-- returning the content of the window.
--
-- @tparam number width The window's width
-- @tparam number height The window's height
-- @tparam function() fn The action to run
-- @treturn {string...} The content of the window.
local function with_window_lines(width, height, fn)
local window = with_window(width, height, fn)
local out = {}
for i = 1, height do out[i] = window.getLine(i) end
return out
end
return {
capture_program = capture_program,
with_window = with_window,
with_window_lines = with_window_lines,
}

View File

@ -1,3 +1,6 @@
:root {
--nav-width: 250px;
}
/* Some misc styles */
.big-image {