diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14f8b7e3e..38729d846 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,7 @@ If you've any other questions, [just ask the community][community] or [open an i ## Table of Contents - [Reporting issues](#reporting-issues) + - [Translations](#translations) - [Setting up a development environment](#setting-up-a-development-environment) - [Developing CC: Tweaked](#developing-cc-tweaked) - [Writing documentation](#writing-documentation) @@ -20,6 +21,10 @@ If you've any other questions, [just ask the community][community] or [open an i If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do use the issue templates - they provide a useful hint on what information to provide. +## Translations +Translations are managed through [CrowdIn], an online interface for managing language strings. Translations may either +be contributed there, or directly via a pull request. + ## Setting up a development environment In order to develop CC: Tweaked, you'll need to download the source code and then run it. @@ -102,3 +107,4 @@ about how you can build on that until you've covered everything! [busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing." [node]: https://nodejs.org/en/ "Node.js" [architecture]: projects/ARCHITECTURE.md +[Crowdin]: https://crowdin.com/project/cc-tweaked/ diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..1f86638a3 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers +# +# SPDX-License-Identifier: MPL-2.0 +files: + - source: projects/common/src/generated/resources/assets/computercraft/lang/en_us.json + translation: /projects/common/src/main/resources/assets/computercraft/lang/%locale_with_underscore%.json + languages_mapping: + locale_with_underscore: + cs: cs_cs # Czech + da: da_dk # Danish + de: de_de # German + es-ES: es_es # Spanish + fr: fr_fr # French + it: it_it # Italian + ja: ja_jp # Japanese + ko: ko_kr # Korean + nb: nb_no # Norwegian Bokmal + nl: nl_nl # Dutch + pl: pl_pl # Polish + pt-BR: pt_br # Portuguese, Brazilian + ru: ru_ru # Russian + sv-SE: sv_se # Sweedish + tok: tok # Toki Pona + uk: uk_ua # Ukraine + vi: vi_vn # Vietnamese + zh-CN: zh_cn # Chinese Simplified diff --git a/gradle.properties b/gradle.properties index 56fe0595a..f7f78fa35 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false # Mod properties isUnstable=true -modVersion=1.113.0 +modVersion=1.113.1 # Minecraft properties: We want to configure this here so we can read it in settings.gradle mcVersion=1.21.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b0641e699..4b785bba8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,6 +71,7 @@ lwjgl = "3.3.3" minotaur = "2.8.7" neoGradle = "7.0.152" nullAway = "0.10.25" +shadow = "8.3.1" spotless = "6.23.3" taskTree = "2.1.1" teavm = "0.11.0-SQUID.1" @@ -94,9 +95,10 @@ jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +netty-codec = { module = "io.netty:netty-codec", version.ref = "netty" } netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" } -netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" } netty-proxy = { module = "io.netty:netty-handler-proxy", version.ref = "netty" } +netty-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" } nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" } nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" } slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } @@ -173,6 +175,7 @@ yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" } githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" } gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" } versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" } diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index 0f947596d..952639cbf 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -38,9 +38,9 @@ repositories { dependencies { // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness. - implementation(project(":core")) - implementation(commonClasses(project(":common-api"))) - clientImplementation(clientClasses(project(":common-api"))) + api(project(":core")) + api(commonClasses(project(":common-api"))) + clientApi(clientClasses(project(":common-api"))) compileOnly(libs.mixin) compileOnly(libs.mixinExtra) diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java index 9a22513c7..60eaa1bcd 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/PocketItemRenderer.java @@ -14,6 +14,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.util.FastColor; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.DyedItemColor; import org.joml.Matrix4f; @@ -93,16 +94,11 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer { } private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) { - var r = (byte) ((colour >>> 16) & 0xFF); - var g = (byte) ((colour >>> 8) & 0xFF); - var b = (byte) (colour & 0xFF); - var c = new byte[]{ r, g, b, (byte) 255 }; - var buffer = render.getBuffer(RenderTypes.TERMINAL); FixedWidthFontRenderer.drawQuad( FixedWidthFontRenderer.toVertexConsumer(transform, buffer), width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT, - c, RenderTypes.FULL_BRIGHT_LIGHTMAP + FastColor.ARGB32.opaque(colour), RenderTypes.FULL_BRIGHT_LIGHTMAP ); } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java index 6dccfcb14..59c12f61b 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/text/DirectFixedWidthFontRenderer.java @@ -12,9 +12,11 @@ import dan200.computercraft.core.terminal.Palette; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.util.Colour; +import net.minecraft.util.FastColor; import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*; import static org.lwjgl.system.MemoryUtil.*; @@ -37,10 +39,12 @@ import static org.lwjgl.system.MemoryUtil.*; * {@link FixedWidthFontRenderer}. */ public final class DirectFixedWidthFontRenderer { + private static final boolean IS_LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN; + private DirectFixedWidthFontRenderer() { } - private static void drawChar(QuadEmitter emitter, float x, float y, int index, byte[] colour) { + private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour) { // Short circuit to avoid the common case - the texture should be blank here after all. if (index == '\0' || index == ' ') return; @@ -157,8 +161,8 @@ public final class DirectFixedWidthFontRenderer { return (terminal.getHeight() + 2) * (terminal.getWidth() + 2) * 2; } - private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - buffer.quad(x1, y1, x2, y2, z, rgba, u1, v1, u2, v2); + private static void quad(QuadEmitter buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + buffer.quad(x1, y1, x2, y2, z, colour, u1, v1, u2, v2); } public interface QuadEmitter { @@ -166,7 +170,7 @@ public final class DirectFixedWidthFontRenderer { ByteBuffer buffer(); - void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2); + void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2); } public record ByteBufferEmitter(ByteBuffer buffer) implements QuadEmitter { @@ -176,12 +180,12 @@ public final class DirectFixedWidthFontRenderer { } @Override - public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, rgba, u1, v1, u2, v2); + public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, colour, u1, v1, u2, v2); } } - static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { + static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, int colour, 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. @@ -195,16 +199,15 @@ public final class DirectFixedWidthFontRenderer { if (position < 0 || 112 > 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(); + + // Pack colour so it is equivalent to rgba:BBBB. This matches the logic in BufferBuilder. + var colourAbgr = FastColor.ABGR32.fromArgb32(colour); + var nativeColour = IS_LITTLE_ENDIAN ? colourAbgr : Integer.reverseBytes(colourAbgr); memPutFloat(addr + 0, x1); memPutFloat(addr + 4, y1); memPutFloat(addr + 8, z); - memPutByte(addr + 12, rgba[0]); - memPutByte(addr + 13, rgba[1]); - memPutByte(addr + 14, rgba[2]); - memPutByte(addr + 15, (byte) 255); + memPutInt(addr + 12, nativeColour); memPutFloat(addr + 16, u1); memPutFloat(addr + 20, v1); memPutShort(addr + 24, (short) 0xF0); @@ -213,10 +216,7 @@ public final class DirectFixedWidthFontRenderer { memPutFloat(addr + 28, x1); memPutFloat(addr + 32, y2); memPutFloat(addr + 36, z); - memPutByte(addr + 40, rgba[0]); - memPutByte(addr + 41, rgba[1]); - memPutByte(addr + 42, rgba[2]); - memPutByte(addr + 43, (byte) 255); + memPutInt(addr + 40, nativeColour); memPutFloat(addr + 44, u1); memPutFloat(addr + 48, v2); memPutShort(addr + 52, (short) 0xF0); @@ -225,10 +225,7 @@ public final class DirectFixedWidthFontRenderer { memPutFloat(addr + 56, x2); memPutFloat(addr + 60, y2); memPutFloat(addr + 64, z); - memPutByte(addr + 68, rgba[0]); - memPutByte(addr + 69, rgba[1]); - memPutByte(addr + 70, rgba[2]); - memPutByte(addr + 71, (byte) 255); + memPutInt(addr + 68, nativeColour); memPutFloat(addr + 72, u2); memPutFloat(addr + 76, v2); memPutShort(addr + 80, (short) 0xF0); @@ -237,10 +234,7 @@ public final class DirectFixedWidthFontRenderer { memPutFloat(addr + 84, x2); memPutFloat(addr + 88, y1); memPutFloat(addr + 92, z); - memPutByte(addr + 96, rgba[0]); - memPutByte(addr + 97, rgba[1]); - memPutByte(addr + 98, rgba[2]); - memPutByte(addr + 99, (byte) 255); + memPutInt(addr + 96, nativeColour); memPutFloat(addr + 100, u2); memPutFloat(addr + 104, v1); memPutShort(addr + 108, (short) 0xF0); diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java index b2d495ba4..7c6e1ed21 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/text/FixedWidthFontRenderer.java @@ -12,6 +12,7 @@ import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.util.Colour; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -41,7 +42,7 @@ public final class FixedWidthFontRenderer { 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 static final int BLACK = FastColor.ARGB32.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR())); private static final float Z_OFFSET = 1e-3f; private FixedWidthFontRenderer() { @@ -59,7 +60,7 @@ public final class FixedWidthFontRenderer { return 15 - Terminal.getColour(c, def); } - private static void drawChar(QuadEmitter emitter, float x, float y, int index, byte[] colour, int light) { + private static void drawChar(QuadEmitter emitter, float x, float y, int index, int colour, int light) { // Short circuit to avoid the common case - the texture should be blank here after all. if (index == '\0' || index == ' ') return; @@ -75,7 +76,7 @@ public final class FixedWidthFontRenderer { ); } - public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, byte[] colour, int light) { + public static void drawQuad(QuadEmitter emitter, float x, float y, float z, float width, float height, int colour, int light) { quad(emitter, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light); } @@ -216,10 +217,10 @@ public final class FixedWidthFontRenderer { return new QuadEmitter(transform.last().pose(), consumer); } - private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2, int light) { + private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2, int light) { var poseMatrix = c.poseMatrix(); var consumer = c.consumer(); - byte r = rgba[0], g = rgba[1], b = rgba[2], a = rgba[3]; + int r = FastColor.ARGB32.red(colour), g = FastColor.ARGB32.green(colour), b = FastColor.ARGB32.blue(colour), a = FastColor.ARGB32.alpha(colour); consumer.addVertex(poseMatrix, x1, y1, z).setColor(r, g, b, a).setUv(u1, v1).setLight(light); consumer.addVertex(poseMatrix, x1, y2, z).setColor(r, g, b, a).setUv(u1, v2).setLight(light); diff --git a/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json b/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json index e85c7816e..eaf63c94d 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/ja_jp.json @@ -1,8 +1,14 @@ { "argument.computercraft.argument_expected": "引数が期待される", + "argument.computercraft.computer.distance": "エンティティまでの距離", + "argument.computercraft.computer.family": "コンピューターファミリー", + "argument.computercraft.computer.id": "コンピューターID", + "argument.computercraft.computer.instance": "固有インスタンスID", + "argument.computercraft.computer.label": "コンピューターラベル", "argument.computercraft.computer.many_matching": "'%s'に一致する複数のコンピューター (インスタンス %s)", - "argument.computercraft.computer.no_matching": "'%s'に一致するコンピュータはありません", + "argument.computercraft.computer.no_matching": "'%s'に一致するコンピューターはありません", "argument.computercraft.tracking_field.no_field": "'%s'は未知のフィールドです", + "argument.computercraft.unknown_computer_family": "'%s'は未知のコンピューターファミリーです", "block.computercraft.cable": "ネットワークケーブル", "block.computercraft.computer_advanced": "高度なコンピューター", "block.computercraft.computer_command": "コマンドコンピューター", @@ -12,9 +18,9 @@ "block.computercraft.monitor_normal": "モニター", "block.computercraft.printer": "プリンター", "block.computercraft.speaker": "スピーカー", - "block.computercraft.turtle_advanced": "高度なタートル", - "block.computercraft.turtle_advanced.upgraded": "高度な%sタートル", - "block.computercraft.turtle_advanced.upgraded_twice": "高度な%s%sタートル", + "block.computercraft.turtle_advanced": "アドバンスドタートル", + "block.computercraft.turtle_advanced.upgraded": "アドバンスド%sタートル", + "block.computercraft.turtle_advanced.upgraded_twice": "アドバンスド%s%sタートル", "block.computercraft.turtle_normal": "タートル", "block.computercraft.turtle_normal.upgraded": "%sタートル", "block.computercraft.turtle_normal.upgraded_twice": "%s%sタートル", @@ -24,11 +30,11 @@ "block.computercraft.wireless_modem_normal": "無線モデム", "chat.computercraft.wired_modem.peripheral_connected": "周辺の\"%s\"のネットワークに接続されました", "chat.computercraft.wired_modem.peripheral_disconnected": "周辺の\"%s\"のネットワークから切断されました", - "commands.computercraft.desc": "/computercraft コマンドは、コンピュータとの制御および対話するためのさまざまなデバッグツールと管理者ツールを提供します。", - "commands.computercraft.dump.action": "このコンピュータの詳細を表示します", - "commands.computercraft.dump.desc": "すべてのコンピューターの状態、または一台のコンピューターの特定の情報を表示する。 コンピュータのインスタンスID (例えば 123), コンピュータID (例えば #123) またはラベル (例えば \\\"@My Computer\\\") を指定することができます。", - "commands.computercraft.dump.open_path": "このコンピュータのファイルを表示します", - "commands.computercraft.dump.synopsis": "コンピュータの状態を表示します。", + "commands.computercraft.desc": "/computercraft コマンドは、コンピューターとの制御および対話するためのさまざまなデバッグツールと管理者ツールを提供します。", + "commands.computercraft.dump.action": "このコンピューターの詳細を表示します", + "commands.computercraft.dump.desc": "すべてのコンピューターの状態、または一台のコンピューターの特定の情報を表示する。 コンピューターのインスタンスID (例えば 123), コンピューターID (例えば #123) またはラベル (例えば \\\"@My Computer\\\") を指定することができます。", + "commands.computercraft.dump.open_path": "このコンピューターのファイルを表示します", + "commands.computercraft.dump.synopsis": "コンピューターの状態を表示します。", "commands.computercraft.generic.additional_rows": "%d行を追加…", "commands.computercraft.generic.exception": "未処理の例外 (%s)", "commands.computercraft.generic.no": "N", @@ -39,65 +45,182 @@ "commands.computercraft.help.no_children": "%s にサブコマンドはありません", "commands.computercraft.help.no_command": "%s というコマンドはありません", "commands.computercraft.help.synopsis": "特定のコマンドのヘルプを提供します", - "commands.computercraft.queue.desc": "追加の引数を通過する computer_command インベントをコマンドコンピューターに送信します。これは主にマップメーカーのために設計されており、よりコンピュータフレンドリーバージョンの /trigger として機能します。 どのプレイヤーでもコマンドを実行できます。これは、テキストコンポーネントのクリックイベントを介して行われる可能性があります。", + "commands.computercraft.queue.desc": "追加の引数を通過する computer_command インベントをコマンドコンピューターに送信します。これは主にマップメーカーのために設計されており、よりコンピューターフレンドリーバージョンの /trigger として機能します。 どのプレイヤーでもコマンドを実行できます。これは、テキストコンポーネントのクリックイベントを介して行われる可能性があります。", "commands.computercraft.queue.synopsis": "computer_command インベントをコマンドコンピューターに送信します", - "commands.computercraft.shutdown.desc": "指定されたコンピュータ、指定されていない場合はすべてのコンピュータをシャットダウンします。 コンピュータのインスタンスID (例えば 123), コンピュータID (例えば #123) またはラベル (例えば \\\"@My Computer\\\") を指定することができます。", + "commands.computercraft.shutdown.desc": "指定されたコンピューター、指定されていない場合はすべてのコンピューターをシャットダウンします。 コンピューターのインスタンスID (例えば 123), コンピューターID (例えば #123) またはラベル (例えば \\\"@My Computer\\\") を指定することができます。", "commands.computercraft.shutdown.done": "%s/%s コンピューターをシャットダウンしました", - "commands.computercraft.shutdown.synopsis": "コンピュータをリモートでシャットダウンする。", - "commands.computercraft.synopsis": "コンピュータを制御するためのさまざまなコマンド。", + "commands.computercraft.shutdown.synopsis": "コンピューターをリモートでシャットダウンする。", + "commands.computercraft.synopsis": "コンピューターを制御するためのさまざまなコマンド。", "commands.computercraft.tp.action": "このコンピューターへテレポートします", - "commands.computercraft.tp.desc": "コンピュータの場所にテレポート.コンピュータのインスタンスID(例えば 123)またはコンピュータID(例えば #123)を指定することができます。", - "commands.computercraft.tp.synopsis": "特定のコンピュータにテレポート。", - "commands.computercraft.track.desc": "コンピュータの実行時間を追跡するだけでなく、イベントを確認することができます。 これは /forge と同様の方法で情報を提示し、遅れを診断するのに役立ちます。", + "commands.computercraft.tp.desc": "コンピューターの場所にテレポート.コンピューターのインスタンスID(例えば 123)またはコンピューターID(例えば #123)を指定することができます。", + "commands.computercraft.tp.synopsis": "特定のコンピューターにテレポート。", + "commands.computercraft.track.desc": "コンピューターの実行時間を追跡するだけでなく、イベントを確認することができます。 これは /forge と同様の方法で情報を提示し、遅れを診断するのに役立ちます。", "commands.computercraft.track.dump.computer": "コンピューター", - "commands.computercraft.track.dump.desc": "コンピュータの最新の追跡結果をダンプしてください。", + "commands.computercraft.track.dump.desc": "コンピューターの最新の追跡結果をダンプしてください。", "commands.computercraft.track.dump.no_timings": "利用可能なタイミングはありません", "commands.computercraft.track.dump.synopsis": "最新の追跡結果をダンプしてください", - "commands.computercraft.track.start.desc": "すべてのコンピュータの実行時間とイベント数の追跡を開始します。 これにより、以前の実行結果が破棄されます。", + "commands.computercraft.track.start.desc": "すべてのコンピューターの実行時間とイベント数の追跡を開始します。 これにより、以前の実行結果が破棄されます。", "commands.computercraft.track.start.stop": "トラッキングを停止して結果を表示するには %s を実行してください", - "commands.computercraft.track.start.synopsis": "すべてのコンピュータの追跡を開始します", + "commands.computercraft.track.start.synopsis": "すべてのコンピューターの追跡を開始します。", "commands.computercraft.track.stop.action": "追跡を中止するためにクリックしてください", - "commands.computercraft.track.stop.desc": "すべてのコンピュータのイベントと実行時間の追跡を停止します", - "commands.computercraft.track.stop.not_enabled": "現在コンピュータを追跡していません", - "commands.computercraft.track.stop.synopsis": "すべてのコンピュータの追跡を停止します", - "commands.computercraft.track.synopsis": "コンピュータの実行時間を追跡します。", - "commands.computercraft.turn_on.desc": "指定されているコンピュータを起動します。 コンピュータのインスタンスID (例えば 123), コンピュータID (例えば #123) またはラベル (例えば \\\"@My Computer\\\") を指定することができます。", + "commands.computercraft.track.stop.desc": "すべてのコンピューターのイベントと実行時間の追跡を停止します", + "commands.computercraft.track.stop.not_enabled": "現在コンピューターを追跡していません", + "commands.computercraft.track.stop.synopsis": "すべてのコンピューターの追跡を停止します。", + "commands.computercraft.track.synopsis": "コンピューターの実行時間を追跡します。", + "commands.computercraft.turn_on.desc": "指定されているコンピューターを起動します。 コンピューターのインスタンスID (例えば 123), コンピューターID (例えば #123) またはラベル (例えば \\\"@My Computer\\\") を指定することができます。", "commands.computercraft.turn_on.done": "%s/%s コンピューターを起動しました", "commands.computercraft.turn_on.synopsis": "コンピューターをリモートで起動します。", - "commands.computercraft.view.action": "このコンピュータを見ます", - "commands.computercraft.view.desc": "コンピュータのターミナルを開き、コンピュータのリモートコントロールを可能にします。 これはタートルのインベントリへのアクセスを提供しません。 コンピュータのインスタンスID(例えば 123)またはコンピュータID(例えば #123)を指定することができます。", + "commands.computercraft.view.action": "このコンピューターを見ます", + "commands.computercraft.view.desc": "コンピューターのターミナルを開き、コンピューターのリモートコントロールを可能にします。 これはタートルのインベントリへのアクセスを提供しません。 コンピューターのインスタンスID(例えば 123)またはコンピューターID(例えば #123)を指定することができます。", "commands.computercraft.view.not_player": "非プレイヤー用のターミナルを開くことができません", - "commands.computercraft.view.synopsis": "コンピュータのターミナルを表示します。", - "gui.computercraft.pocket_computer_overlay": "ポケットコンピュータを開いています。 ESCを押して閉じます。", - "gui.computercraft.tooltip.computer_id": "コンピュータID: %s", + "commands.computercraft.view.synopsis": "コンピューターのターミナルを表示します。", + "gui.computercraft.config.command_require_creative": "コマンドコンピューターはクリエイティブモードが必要です。", + "gui.computercraft.config.command_require_creative.tooltip": "コマンドコンピューターと対話するためにはプレイヤーがクリエイティブモードかつOP権限保有者でなければなりません。\nこれはバニラのコマンドブロックのデフォルト挙動です。", + "gui.computercraft.config.computer_space_limit": "コンピューターの限容制限(バイト)", + "gui.computercraft.config.computer_space_limit.tooltip": "コンピューターとタートルのディスク容量制限、バイト単位。", + "gui.computercraft.config.default_computer_settings": "デフォルトのコンピューター設定", + "gui.computercraft.config.default_computer_settings.tooltip": "新しいコンピューターに設定するデフォルトのシステム設定のコンマ区切りのリスト。\n例: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nは全ての自動補完を無効にします。", + "gui.computercraft.config.disabled_generic_methods": "無効化するジェネリックメソッド", + "gui.computercraft.config.disabled_generic_methods.tooltip": "無効にするジェネリックメソッドまたはメソッドソースのリスト。\nジェネリックメソッドは、明示的な周辺プロバイダがない場合にブロック/ブロックエンティティに追加されるメソッドです。\nこれには、インベントリメソッド (inventory.getItemDetail や inventory.pushItems) や、(Forgeであれば)fluid_storage や energy_storage メソッドが含まれます。\nこのリストに含まれるメソッドは、メソッド群全体 (computercraft:inventory) か、単一のメソッド (computercraft:inventory#pushItems) のどちらかになります。", + "gui.computercraft.config.execution": "実行", + "gui.computercraft.config.execution.computer_threads": "コンピューターのスレッド", + "gui.computercraft.config.execution.computer_threads.tooltip": "コンピューターが実行できるスレッド数を設定する。\n数値が高いほどより多くのコンピューターが一度に実行できますが、ラグを誘発する可能性があります。\nスレッド数が1より大きいと動作しないMODもあるので注意してください\n範囲: > 1", + "gui.computercraft.config.execution.max_main_computer_time": "サーバーティックのコンピューター時間上限", + "gui.computercraft.config.execution.max_main_computer_time.tooltip": "コンピューターが1ティックで実行できる理想的な最大時間、ミリ秒単位。\nどれぐらい時間がかかるか不明であるため、上限を超える可能性があることに注意。\nこれは平均時間の上限を目的とする。\n範囲: > 1", + "gui.computercraft.config.execution.max_main_global_time": "サーバーティックのグローバル回数上限", + "gui.computercraft.config.execution.max_main_global_time.tooltip": "1ティックでタスクを実行できる最大時間、ミリ秒単位。\nどれぐらい時間がかかるか不明であるため、上限を超える可能性があることに注意。\nこれは平均回数の上限を時間とする。", + "gui.computercraft.config.execution.tooltip": "コンピュータの実行挙動を制御する。\nこれは主にサーバーを微調整するためのもので、一般的には触る必要はない。", + "gui.computercraft.config.floppy_space_limit": "フロッピーディスクの容量制限(バイト)", + "gui.computercraft.config.floppy_space_limit.tooltip": "フロッピーディスクのディスク容量制限(バイト単位)。", + "gui.computercraft.config.http": "HTTP", + "gui.computercraft.config.http.bandwidth": "帯域幅", + "gui.computercraft.config.http.bandwidth.global_download": "グローバルダウンロード制限", + "gui.computercraft.config.http.bandwidth.global_download.tooltip": "1秒間にダウンロードできるバイト数。これはすべてのコンピュータで共有されます。(byte/s).\n範囲: > 1", + "gui.computercraft.config.http.bandwidth.global_upload": "グローバルアップロード制限", + "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "1秒間にアップロードできるバイト数。これはすべてのコンピュータで共有されます。(byte/s).\n範囲: > 1", + "gui.computercraft.config.http.bandwidth.tooltip": "コンピュータが使用する帯域幅を制限する。", + "gui.computercraft.config.http.enabled": "HTTP APIを有効にする", + "gui.computercraft.config.http.enabled.tooltip": "コンピュータの\"http\" APIを有効にする。これを無効にすると、多くのユーザーが依存している\"pastebin\"と\"wget\"プログラムも無効になる。\nこのオプションはオンのままにしておき、よりきめ細かい制御を行うために\"ルール\"の設定オプションを使用することを推奨する。", + "gui.computercraft.config.http.max_requests": "最大同時リクエスト数", + "gui.computercraft.config.http.max_requests.tooltip": "コンピューターが一度にできるhttpリクエストの数。追加のリクエストはキューに入れられ、実行中のリクエストが終了したときに送信されます。無制限の場合は0に設定します。\n範囲: > 0", + "gui.computercraft.config.http.max_websockets": "最大同時ウェブソケット数", + "gui.computercraft.config.http.max_websockets.tooltip": "コンピュータが一度に開くことのできるウェブソケットの数。\n範囲: > 1", + "gui.computercraft.config.http.proxy": "プロキシ", + "gui.computercraft.config.http.proxy.host": "ホスト名", + "gui.computercraft.config.http.proxy.host.tooltip": "プロキシサーバーのホスト名またはIPアドレス。", + "gui.computercraft.config.http.proxy.port": "ポート", + "gui.computercraft.config.http.proxy.port.tooltip": "プロキシサーバーのポート。\n範囲: 1 ~ 65536", + "gui.computercraft.config.http.proxy.tooltip": "HTTPとウェブソケットリクエストをプロキシサーバ経由でトンネリングする。\"use_proxy\"が\"true\"(デフォルトでは\"off\")に設定されている HTTPルールにのみ影響します。\nプロキシに認証が必要な場合は、\"computercraft-server.toml\"と同じディレクトリに\"myuser:mypassword\"のようにユーザー名とパスワードをコロンで区切って記述した\"computercraft-proxy.pw\"ファイルを作成します。\nSOCKS4プロキシでは、ユーザー名のみが必要です。", + "gui.computercraft.config.http.proxy.type": "プロキシ種類", + "gui.computercraft.config.http.proxy.type.tooltip": "使用するプロキシの種類。\n許可された値: HTTP, HTTPS, SOCKS4, SOCKS5", + "gui.computercraft.config.http.rules": "ルールの許可/拒否", + "gui.computercraft.config.http.rules.tooltip": "特定のドメインやIPに対する\"http\" APIの動作を制御するルールのリスト。それぞれのルールはホスト名とオプションのポートに対して対応し、 リクエストに対していくつかのプロパティを設定します。 ルールは順番に評価され、前のルールが後のルールを上書きします。\n\n有効なプロパティ:\n - \"host\" (必須): このルールが対応するドメインまたはIPアドレス。 これはドメイン名(\"pastebin.com\")、ワイルドカード(\"*.pastebin.com\")、あるいはCIDR表記(\"127.0.0.0/8\")となります。\n - \"port\" (オプション): 80や 443など、特定のポートに対するリクエストにのみマッチする。.\n\n - \"action\" (オプション): このリクエストを許可するか拒否するか。\n - \"max_download\" (オプション): このリクエストでコンピューターがダウンロードできる最大サイズ(バイト単位)。\n - \"max_upload\" (オプション): このリクエストでコンピューターがアップロードできる最大サイズ(バイト)。\n - \"max_websocket_message\" (オプション): コンピューターが1つのウェブソケット・パケットで送受信できる最大サイズ(バイト)。\n - \"use_proxy\" (オプション): HTTP/SOCKSプロキシが設定されている場合は、その使用を有効にする。", + "gui.computercraft.config.http.tooltip": "HTTP APIの制御", + "gui.computercraft.config.http.websocket_enabled": "ウェブソケットを有効にする", + "gui.computercraft.config.http.websocket_enabled.tooltip": "httpウェブソケットの使用を有効にする。これには、\"http_enable\"オプションもtrueである必要があります。.", + "gui.computercraft.config.log_computer_errors": "コンピュータのエラーを記録する", + "gui.computercraft.config.log_computer_errors.tooltip": "周辺機器やその他のLuaオブジェクトが発生させた例外を記録します。これにより、MODの作者が問題をデバッグしやすくなりますが、バグを含んだメソッドを使用した場合、ログスパムが発生する可能性があります。", + "gui.computercraft.config.maximum_open_files": "1台のコンピューターで開けるファイルの最大数", + "gui.computercraft.config.maximum_open_files.tooltip": "コンピューターが同時に開くことができるファイルの数を設定します。無制限の場合は0に設定します。\n範囲: > 0", + "gui.computercraft.config.monitor_distance": "モニター距離", + "gui.computercraft.config.monitor_distance.tooltip": "モニターがレンダリングする最大距離。デフォルトは標準的なタイルエンティティの制限値ですが、より大きなモニタを構築したい場合は拡張することができます。\n範囲: 16 ~ 1024", + "gui.computercraft.config.monitor_renderer": "モニターレンダラー", + "gui.computercraft.config.monitor_renderer.tooltip": "モニターに使用するレンダラー。一般的に、この値は\"best\"に保つたれるべきです。 - モニターにパフォーマンス上の問題がある場合は、別のレンダラーを試してみるとよいでしょう。\n許可された値: BEST, TBO, VBO", + "gui.computercraft.config.peripheral": "周辺機器", + "gui.computercraft.config.peripheral.command_block_enabled": "コマンドブロック周辺機器を有効にする", + "gui.computercraft.config.peripheral.command_block_enabled.tooltip": "コマンドブロック周辺機器サポートを有効にする", + "gui.computercraft.config.peripheral.max_notes_per_tick": "コンピューターが一度に演奏できる最大音符数", + "gui.computercraft.config.peripheral.max_notes_per_tick.tooltip": "スピーカーが一度に演奏できる最大音符数。\n範囲: > 1", + "gui.computercraft.config.peripheral.modem_high_altitude_range": "モデム範囲(高高度)", + "gui.computercraft.config.peripheral.modem_high_altitude_range.tooltip": "晴天時の最大高度におけるワイヤレスモデムの通信距離、メートル単位。\n範囲: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm": "モデム範囲(高高度、悪天候)", + "gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "悪天候の最大高度におけるワイヤレスモデムの通信距離、メートル単位。\n範囲: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_range": "モデム範囲(デフォルト)", + "gui.computercraft.config.peripheral.modem_range.tooltip": "晴天時の低高度におけるワイヤレスモデムの通信距離、メートル単位。\n範囲: 0 ~ 100000", + "gui.computercraft.config.peripheral.modem_range_during_storm": "モデム範囲(悪天候)", + "gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "荒天時の低高度におけるワイヤレスモデムの通信距離、メートル単位。\n範囲: 0 ~ 100000", + "gui.computercraft.config.peripheral.monitor_bandwidth": "モニター帯域幅", + "gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "1ティックあたりのモニターデータ送信量の上限。注:\n - 帯域幅は圧縮前に測定されるため、クライアントに送信されるデータはより小さくなります。\n - これは、パケットを送信するプレーヤーの数を無視する。1人のプレーヤーのモニターを更新することは、20人に送信するのと同じ帯域幅の制限を消費する。\n - フルサイズのモニターは~25kbのデータを送信する。そのため、デフォルト(1MB)では、1回のティックで~40個のモニターを更新することができる。\n 無効にするには 0 を設定する。\n範囲: > 0", + "gui.computercraft.config.peripheral.tooltip": "周辺機器に関する各種オプション。", + "gui.computercraft.config.term_sizes": "ターミナルサイズ", + "gui.computercraft.config.term_sizes.computer": "コンピューター", + "gui.computercraft.config.term_sizes.computer.height": "ターミナルの高さ", + "gui.computercraft.config.term_sizes.computer.height.tooltip": "範囲: 1 ~ 255", + "gui.computercraft.config.term_sizes.computer.tooltip": "コンピュータの端末サイズ。", + "gui.computercraft.config.term_sizes.computer.width": "ターミナルの幅", + "gui.computercraft.config.term_sizes.computer.width.tooltip": "範囲: 1 ~ 255", + "gui.computercraft.config.term_sizes.monitor": "モニター", + "gui.computercraft.config.term_sizes.monitor.height": "モニターの最大高さ", + "gui.computercraft.config.term_sizes.monitor.height.tooltip": "範囲: 1 ~ 32", + "gui.computercraft.config.term_sizes.monitor.tooltip": "モニターの最大サイズ(ブロック単位).", + "gui.computercraft.config.term_sizes.monitor.width": "モニターの最大幅", + "gui.computercraft.config.term_sizes.monitor.width.tooltip": "範囲: 1 ~ 32", + "gui.computercraft.config.term_sizes.pocket_computer": "ポケットコンピューター", + "gui.computercraft.config.term_sizes.pocket_computer.height": "ターミナルの高さ", + "gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "範囲: 1 ~ 255", + "gui.computercraft.config.term_sizes.pocket_computer.tooltip": "ポケットコンピュータのターミナルサイズ。", + "gui.computercraft.config.term_sizes.pocket_computer.width": "ターミナルの幅", + "gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "範囲: 1 ~ 255", + "gui.computercraft.config.term_sizes.tooltip": "各種コンピュータのターミナルサイズを設定します。ターミナルサイズが大きくなるとより多くの帯域幅を必要としますので、注意して使用してください。", + "gui.computercraft.config.turtle": "タートル", + "gui.computercraft.config.turtle.advanced_fuel_limit": "アドバンスドタートルの燃料制限", + "gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "アドバンスドタートルの燃料制限。\n範囲: > 0", + "gui.computercraft.config.turtle.can_push": "タートルのによるエンティティ押出し", + "gui.computercraft.config.turtle.can_push.tooltip": "trueに設定すると、タートルは空間がある場合停止する代わりにエンティティを押し出す。", + "gui.computercraft.config.turtle.need_fuel": "燃料を有効にする", + "gui.computercraft.config.turtle.need_fuel.tooltip": "タートルズが移動に燃料を必要とするかどうかを設定する。", + "gui.computercraft.config.turtle.normal_fuel_limit": "タートルの燃料制限", + "gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "タートルの燃料制限。\n範囲: > 0", + "gui.computercraft.config.turtle.tooltip": "タートルに関する様々なオプション。", + "gui.computercraft.config.upload_max_size": "ファイルアップロードサイズ制限(バイト)", + "gui.computercraft.config.upload_max_size.tooltip": "ファイルアップロードサイズの上限をバイト数で指定します。1KiBから16MiBの範囲でなければなりません。 \nアップロードは1ティックで処理されることを覚えておいてください - 大きなファイルやネットワークパフォーマンスの低下はネットワーキングスレッドを停止させる可能性があります。\nディスク容量にも注意してください!\n範囲: 1024 ~ 16777216", + "gui.computercraft.config.upload_nag_delay": "アップロード催促遅延", + "gui.computercraft.config.upload_nag_delay.tooltip": "未処理の入力について通知するまでの遅延時間(秒)。無効にするには0を設定する。\n範囲: 0 ~ 60", + "gui.computercraft.pocket_computer_overlay": "ポケットコンピューターを開いています。 ESCを押して閉じます。", + "gui.computercraft.terminal": "コンピューターターミナル", + "gui.computercraft.tooltip.computer_id": "コンピューターID: %s", "gui.computercraft.tooltip.copy": "クリップボードにコピー", "gui.computercraft.tooltip.disk_id": "ディスクID: %s", "gui.computercraft.tooltip.terminate": "現在実行中のコードを停止する", "gui.computercraft.tooltip.terminate.key": "Ctrl+T 長押し", - "gui.computercraft.tooltip.turn_off": "このコンピュータをオフにする", + "gui.computercraft.tooltip.turn_off": "このコンピューターをオフにする", "gui.computercraft.tooltip.turn_off.key": "Ctrl+S 長押し", - "gui.computercraft.tooltip.turn_on": "このコンピュータをオンにする", + "gui.computercraft.tooltip.turn_on": "このコンピューターをオンにする", "gui.computercraft.upload.failed": "アップロードに失敗しました", - "gui.computercraft.upload.failed.computer_off": "ファイルをアップロードする前にコンピュータを起動する必要があります。", + "gui.computercraft.upload.failed.computer_off": "ファイルをアップロードする前にコンピューターを起動する必要があります。", "gui.computercraft.upload.failed.corrupted": "アップロード時にファイルが破損しました。 もう一度やり直してください。", "gui.computercraft.upload.failed.generic": "ファイルのアップロードに失敗しました(%s)", "gui.computercraft.upload.failed.name_too_long": "ファイル名が長すぎてアップロードできません。", "gui.computercraft.upload.failed.too_many_files": "多くのファイルをアップロードできません。", "gui.computercraft.upload.failed.too_much": "アップロードするにはファイルが大きスギます。", + "gui.computercraft.upload.no_response": "ファイルの転送", + "gui.computercraft.upload.no_response.msg": "コンピュータが転送されたファイルを使用していません。プログラム %s を実行して再試行する必要があります。", "item.computercraft.disk": "フロッピーディスク", - "item.computercraft.pocket_computer_advanced": "高度なポケットコンピュータ", - "item.computercraft.pocket_computer_advanced.upgraded": "高度な%sポケットコンピュータ", - "item.computercraft.pocket_computer_normal": "ポケットコンピュータ", - "item.computercraft.pocket_computer_normal.upgraded": "%sポケットコンピュータ", + "item.computercraft.pocket_computer_advanced": "高度なポケットコンピューター", + "item.computercraft.pocket_computer_advanced.upgraded": "高度な%sポケットコンピューター", + "item.computercraft.pocket_computer_normal": "ポケットコンピューター", + "item.computercraft.pocket_computer_normal.upgraded": "%sポケットコンピューター", "item.computercraft.printed_book": "印刷された本", "item.computercraft.printed_page": "印刷された紙", "item.computercraft.printed_pages": "印刷された紙束", "item.computercraft.treasure_disk": "フロッピーディスク", "itemGroup.computercraft": "ComputerCraft", + "tag.item.computercraft.computer": "コンピューター", + "tag.item.computercraft.monitor": "モニター", + "tag.item.computercraft.turtle": "タートル", + "tag.item.computercraft.wired_modem": "有線モデム", + "tracking_field.computercraft.avg": "%s (平均)", + "tracking_field.computercraft.computer_tasks.name": "タスク", + "tracking_field.computercraft.count": "%s (回)", "tracking_field.computercraft.fs.name": "ファイルシステム演算", "tracking_field.computercraft.http_download.name": "HTTPダウンロード", + "tracking_field.computercraft.http_requests.name": "HTTPリクエスト", "tracking_field.computercraft.http_upload.name": "HTTPアップロード", + "tracking_field.computercraft.java_allocation.name": "Java割当", + "tracking_field.computercraft.max": "%s (最大)", "tracking_field.computercraft.peripheral.name": "実行呼び出し", + "tracking_field.computercraft.server_tasks.name": "サーバータスク", + "tracking_field.computercraft.turtle_ops.name": "タートル操作", "tracking_field.computercraft.websocket_incoming.name": "Websocket 受信", "tracking_field.computercraft.websocket_outgoing.name": "Websocket 送信", "upgrade.computercraft.speaker.adjective": "騒音", diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt index 2d5a4d48a..afbf8d400 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt @@ -160,5 +160,7 @@ class Monitor_Test { } thenScreenshot() + + thenExecute { helper.level.dayTime = Times.NOON } } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt index 1cb547eba..27c605d52 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printout_Test.kt @@ -53,5 +53,7 @@ class Printout_Test { } thenScreenshot() + + thenExecute { helper.level.dayTime = Times.NOON } } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index 9fb96bf9c..191152e74 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -742,7 +742,7 @@ class Turtle_Test { @GameTest fun Breaks_exploding_block(context: GameTestHelper) = context.sequence { thenOnComputer { turtle.dig(Optional.empty()) } - thenIdle(2) + thenWaitUntil { context.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 2)) } thenExecute { context.assertItemEntityCountIs(ModRegistry.Items.TURTLE_NORMAL.get(), 1) context.assertItemEntityCountIs(Items.BONE_BLOCK, 65) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt index 2240934da..d410a1b9c 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt @@ -73,6 +73,7 @@ object ClientTestHooks { minecraft.options.cloudStatus().set(CloudStatus.OFF) minecraft.options.particles().set(ParticleStatus.MINIMAL) minecraft.options.tutorialStep = TutorialSteps.NONE + minecraft.options.pauseOnLostFocus = false minecraft.options.renderDistance().set(6) minecraft.options.gamma().set(1.0) minecraft.options.getSoundSourceOptionInstance(SoundSource.MUSIC).set(0.0) @@ -92,10 +93,7 @@ object ClientTestHooks { LEVEL_NAME, LevelSettings("Test Level", GameType.CREATIVE, false, Difficulty.EASY, true, rules, WorldDataConfiguration.DEFAULT), WorldOptions(WorldOptions.randomSeed(), false, false), - { - it.registryOrThrow(Registries.WORLD_PRESET).getHolderOrThrow(WorldPresets.FLAT).value() - .createWorldDimensions() - }, + { it.registryOrThrow(Registries.WORLD_PRESET).getOrThrow(WorldPresets.FLAT).createWorldDimensions() }, screen, ) } @@ -112,7 +110,20 @@ object ClientTestHooks { val testTracker = when (val tracker = this.testTracker) { null -> { if (server.overworld().players().isEmpty()) return + + // Place our players above where the tests will run, looking down. This at least ensures they're in the + // right area when the tests start running. + for (player in server.overworld().players()) { + player.abilities.flying = true + player.onUpdateAbilities() + player.connection.teleport(0.0, -30.0, 0.0, 0.0f, 90.0f) + player.inventory.clearContent() + } + + // Wait for all chunks to be rendered. if (!Minecraft.getInstance().isRenderingStable()) return + + // Then a little more just in case. if (startupDelay >= 0) { // TODO: Is there a better way? Maybe set a flag when the client starts rendering? startupDelay-- diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.render_turtle_items.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.render_turtle_items.snbt index 5c1c64d75..2faa4fed4 100644 --- a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.render_turtle_items.snbt +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.render_turtle_items.snbt @@ -130,10 +130,10 @@ ], entities: [ {blockPos: [2, 1, 0], pos: [2.5d, 1.0d, 0.5d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CustomName: '{"text":"turtle_test.render_turtle_items"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [125.5d, -58.0d, 53.6501934495752d], Pose: {}, Rotation: [0.14965993f, 4.066999f], ShowArms: 0b, Small: 0b, UUID: [I; -1678989666, 1780632657, -1267321893, 665166246], id: "minecraft:armor_stand"}}, - {blockPos: [3, 1, 3], pos: [3.5d, 1.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [-90.0f, 0.0f], UUID: [I; 671334450, -268547745, -1360971514, -649716242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], display: {Name: '{"text":"Dinnerbone"}'}, On: 1b, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}, - {blockPos: [3, 3, 3], pos: [3.5d, 3.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [-90.0f, 0.0f], UUID: [I; 671334422, -268542345, -1362491514, -649716242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}, - {blockPos: [1, 1, 3], pos: [1.5d, 1.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [90.0f, 0.0f], UUID: [I; 625334450, -268647745, -1360971514, -649724242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, LeftUpgrade: {id: "cctest:netherite_pickaxe", components: {"minecraft:enchantments": {levels: {"minecraft:silk_touch": 1}}}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}, - {blockPos: [1, 3, 3], pos: [1.5d, 3.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [90.0f, 0.0f], UUID: [I; 675334422, -268542245, -1362491514, -649755242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], display: {Name: '{"text":"Dinnerbone"}'}, On: 1b, LeftUpgrade: {id: "cctest:netherite_pickaxe", components: {"minecraft:enchantments": {levels: {"minecraft:silk_touch": 1}}}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}} + {blockPos: [3, 1, 3], pos: [3.5d, 1.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [90.0f, 0.0f], UUID: [I; 671334450, -268547745, -1360971514, -649716242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], display: {Name: '{"text":"Dinnerbone"}'}, On: 1b, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}, + {blockPos: [3, 3, 3], pos: [3.5d, 3.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [90.0f, 0.0f], UUID: [I; 671334422, -268542345, -1362491514, -649716242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, RightUpgrade: "minecraft:diamond_pickaxe", RightUpgradeNbt: {Tag: {Damage: 0}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}, + {blockPos: [1, 1, 3], pos: [1.5d, 1.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [-90.0f, 0.0f], UUID: [I; 625334450, -268647745, -1360971514, -649724242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], On: 1b, LeftUpgrade: {id: "cctest:netherite_pickaxe", components: {"minecraft:enchantments": {levels: {"minecraft:silk_touch": 1}}}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}}, + {blockPos: [1, 3, 3], pos: [1.5d, 3.5d, 3.5d], nbt: {Air: 300s, FallDistance: 0.0f, Fire: 0s, Invulnerable: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [126.5d, -57.5d, 56.5d], Rotation: [-90.0f, 0.0f], UUID: [I; 675334422, -268542245, -1362491514, -649755242], billboard: "fixed", glow_color_override: -1, height: 0.0f, id: "minecraft:item_display", interpolation_duration: 0, item: {Count: 1b, id: "computercraft:turtle_normal", tag: {ComputerId: 8, Fuel: 0, Items: [], display: {Name: '{"text":"Dinnerbone"}'}, On: 1b, LeftUpgrade: {id: "cctest:netherite_pickaxe", components: {"minecraft:enchantments": {levels: {"minecraft:silk_touch": 1}}}}}}, item_display: "none", shadow_radius: 0.0f, shadow_strength: 1.0f, transformation: {left_rotation: [0.0f, 0.0f, 0.0f, 1.0f], right_rotation: [0.0f, 0.0f, 0.0f, 1.0f], scale: [1.0f, 1.0f, 1.0f], translation: [0.0f, 0.0f, 0.0f]}, view_range: 1.0f, width: 0.0f}} ], palette: [ "minecraft:polished_andesite", diff --git a/projects/core/build.gradle.kts b/projects/core/build.gradle.kts index f579159a8..e976ce534 100644 --- a/projects/core/build.gradle.kts +++ b/projects/core/build.gradle.kts @@ -7,6 +7,7 @@ import cc.tweaked.gradle.getAbsolutePath plugins { `java-library` `java-test-fixtures` + alias(libs.plugins.shadow) id("cc-tweaked.kotlin-convention") id("cc-tweaked.java-convention") @@ -57,3 +58,22 @@ val checkChangelog by tasks.registering(cc.tweaked.gradle.CheckChangelog::class) } tasks.check { dependsOn(checkChangelog) } + +// We configure the shadow jar to ship netty-codec and all its dependencies, relocating them under the +// dan200.computercraft.core package. +// This is used as part of the Forge build, so that our version of netty-codec is loaded under the GAME layer, and so +// has access to our jar-in-jar'ed jzlib. +tasks.shadowJar { + minimize() + + dependencies { + include(dependency(libs.netty.codec.get())) + include(dependency(libs.netty.http.get())) + include(dependency(libs.netty.socks.get())) + include(dependency(libs.netty.proxy.get())) + } + + for (pkg in listOf("io.netty.handler.codec", "io.netty.handler.proxy")) { + relocate(pkg, "dan200.computercraft.core.vendor.$pkg") + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index 5d51e55e0..e0f2ccf17 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/projects/core/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -95,11 +95,8 @@ public class CobaltLuaMachine implements ILuaMachine { private void addAPI(LuaState state, LuaTable globals, ILuaAPI api) throws LuaError { // Add the methods of an API to the global table - var table = wrapLuaObject(api); - if (table == null) { - LOG.warn("API {} does not provide any methods", api); - table = new LuaTable(); - } + var table = new LuaTable(); + if (!makeLuaObject(api, table)) LOG.warn("API {} does not provide any methods", api); var names = api.getNames(); for (var name : names) globals.rawset(name, table); @@ -163,13 +160,16 @@ public class CobaltLuaMachine implements ILuaMachine { timeout.removeListener(timeoutListener); } - @Nullable - private LuaTable wrapLuaObject(Object object) { - var table = new LuaTable(); - var found = luaMethods.forEachMethod(object, (target, name, method, info) -> + /** + * Populate a table with methods from an object. + * + * @param object The object to draw methods from. + * @param table The table to fill. + * @return Whether any methods were found. + */ + private boolean makeLuaObject(Object object, LuaTable table) { + return luaMethods.forEachMethod(object, (target, name, method, info) -> table.rawset(name, new ResultInterpreterFunction(this, method, target, context, name))); - - return found ? table : null; } private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap values) throws LuaError { @@ -184,47 +184,35 @@ public class CobaltLuaMachine implements ILuaMachine { return ValueFactory.valueOf(bytes); } - // Don't share singleton values, and instead convert them to a new table. - if (LuaUtil.isSingletonCollection(object)) return new LuaTable(); - + // We have a more complex object, which is possibly recursive. First look up our object in the lookup map, + // and reuse it if present. if (values == null) values = new IdentityHashMap<>(1); var result = values.get(object); if (result != null) return result; - var wrapped = toValueWorker(object, values); - if (wrapped == null) { - LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName()); - return Constants.NIL; - } - - values.put(object, wrapped); - return wrapped; - } - - /** - * Convert a complex Java object (such as a collection or Lua object) to a Lua value. - *

- * This is a worker function for {@link #toValue(Object, IdentityHashMap)}, which handles the actual construction - * of values, without reading/writing from the value map. - * - * @param object The object to convert. - * @param values The map of Java to Lua values. - * @return The converted value, or {@code null} if it could not be converted. - * @throws LuaError If the value could not be converted. - */ - private @Nullable LuaValue toValueWorker(Object object, IdentityHashMap values) throws LuaError { if (object instanceof ILuaFunction) { - return new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString()); + var function = new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString()); + values.put(object, function); + return function; } if (object instanceof IDynamicLuaObject) { - LuaValue wrapped = wrapLuaObject(object); - if (wrapped == null) wrapped = new LuaTable(); - return wrapped; + var table = new LuaTable(); + makeLuaObject(object, table); + values.put(object, table); + return table; } + // The following objects may be recursive. In these instances, we need to be careful to store the value *before* + // recursing, to avoid stack overflows. + if (object instanceof Map map) { + // Don't share singleton values, and instead convert them to a new table. + if (LuaUtil.isSingletonMap(map)) return new LuaTable(); + var table = new LuaTable(); + values.put(object, table); + for (var pair : map.entrySet()) { var key = toValue(pair.getKey(), values); var value = toValue(pair.getValue(), values); @@ -234,7 +222,12 @@ public class CobaltLuaMachine implements ILuaMachine { } if (object instanceof Collection objects) { + // Don't share singleton values, and instead convert them to a new table. + if (LuaUtil.isSingletonCollection(objects)) return new LuaTable(); + var table = new LuaTable(objects.size(), 0); + values.put(object, table); + var i = 0; for (var child : objects) table.rawset(++i, toValue(child, values)); return table; @@ -242,11 +235,20 @@ public class CobaltLuaMachine implements ILuaMachine { if (object instanceof Object[] objects) { var table = new LuaTable(objects.length, 0); + values.put(object, table); + for (var i = 0; i < objects.length; i++) table.rawset(i + 1, toValue(objects[i], values)); return table; } - return wrapLuaObject(object); + var table = new LuaTable(); + if (makeLuaObject(object, table)) { + values.put(object, table); + return table; + } + + LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName()); + return Constants.NIL; } Varargs toValues(@Nullable Object[] objects) throws LuaError { diff --git a/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java b/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java index 0dea4b521..50d75db97 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java +++ b/projects/core/src/main/java/dan200/computercraft/core/terminal/Palette.java @@ -12,15 +12,13 @@ public class Palette { private final boolean colour; private final double[][] colours = new double[PALETTE_SIZE][3]; - private final byte[][] byteColours = new byte[PALETTE_SIZE][4]; + private final int[] byteColours = new int[PALETTE_SIZE]; public static final Palette DEFAULT = new Palette(true); public Palette(boolean colour) { this.colour = colour; resetColours(); - - for (var i = 0; i < PALETTE_SIZE; i++) byteColours[i][3] = (byte) 255; } public void setColour(int i, double r, double g, double b) { @@ -30,15 +28,17 @@ public class Palette { colours[i][2] = b; if (colour) { - byteColours[i][0] = (byte) (int) (r * 255); - byteColours[i][1] = (byte) (int) (g * 255); - byteColours[i][2] = (byte) (int) (b * 255); + byteColours[i] = packColour((int) (r * 255), (int) (g * 255), (int) (b * 255)); } else { - var grey = (byte) (int) ((r + g + b) / 3 * 255); - byteColours[i][0] = byteColours[i][1] = byteColours[i][2] = grey; + var grey = (int) ((r + g + b) / 3 * 255); + byteColours[i] = packColour(grey, grey, grey); } } + private static int packColour(int r, int g, int b) { + return 255 << 24 | (r & 255) << 16 | (g & 255) << 8 | b & 255; + } + public void setColour(int i, Colour colour) { setColour(i, colour.getR(), colour.getG(), colour.getB()); } @@ -48,26 +48,20 @@ public class Palette { } /** - * Get the colour as a set of RGB values suitable for rendering. Colours are automatically converted to greyscale + * Get the colour as a set of ARGB values suitable for rendering. Colours are automatically converted to greyscale * when using a black and white palette. *

- * This returns a byte array, suitable for being used directly by our terminal vertex format. + * This returns a packed 32-bit ARGB colour. * * @param i The colour index. - * @return The number as a tuple of bytes. + * @return The actual RGB colour. */ - public byte[] getRenderColours(int i) { + public int getRenderColours(int i) { return byteColours[i]; } - public void resetColour(int i) { - if (i >= 0 && i < PALETTE_SIZE) setColour(i, Colour.VALUES[i]); - } - public void resetColours() { - for (var i = 0; i < Colour.VALUES.length; i++) { - resetColour(i); - } + for (var i = 0; i < Colour.VALUES.length; i++) setColour(i, Colour.VALUES[i]); } public static int encodeRGB8(double[] rgb) { diff --git a/projects/core/src/main/java/dan200/computercraft/core/util/LuaUtil.java b/projects/core/src/main/java/dan200/computercraft/core/util/LuaUtil.java index 0ba8dd1a0..0dd438e6a 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/util/LuaUtil.java +++ b/projects/core/src/main/java/dan200/computercraft/core/util/LuaUtil.java @@ -36,7 +36,20 @@ public class LuaUtil { * @param value The value to test. * @return Whether this is a singleton collection. */ - public static boolean isSingletonCollection(Object value) { - return value == EMPTY_LIST || value == EMPTY_SET || value == EMPTY_MAP; + public static boolean isSingletonCollection(Collection value) { + return value == EMPTY_LIST || value == EMPTY_SET; + } + + /** + * Determine whether a value is a singleton map, such as one created with {@link Map#of()}. + *

+ * These collections are treated specially by {@link ILuaMachine} implementations: we skip sharing for them, and + * create a new table each time. + * + * @param value The value to test. + * @return Whether this is a singleton map. + */ + public static boolean isSingletonMap(Map value) { + return value == EMPTY_MAP; } } diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index ed9727a69..d9f7304ae 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -944,22 +944,21 @@ unserialiseJSON = unserialise_json -- @since 1.31 function urlEncode(str) expect(1, str, "string") - if str then - str = string.gsub(str, "\n", "\r\n") - str = string.gsub(str, "([^A-Za-z0-9 %-%_%.])", function(c) - local n = string.byte(c) - if n < 128 then - -- ASCII - return string.format("%%%02X", n) - else - -- Non-ASCII (encode as UTF-8) - return - string.format("%%%02X", 192 + bit32.band(bit32.arshift(n, 6), 31)) .. - string.format("%%%02X", 128 + bit32.band(n, 63)) - end - end) - str = string.gsub(str, " ", "+") - end + local gsub, byte, format, band, arshift = string.gsub, string.byte, string.format, bit32.band, bit32.arshift + + str = gsub(str, "\n", "\r\n") + str = gsub(str, "[^A-Za-z0-9%-%_%.]", function(c) + if c == " " then return "+" end + + local n = byte(c) + if n < 128 then + -- ASCII + return format("%%%02X", n) + else + -- Non-ASCII (encode as UTF-8) + return format("%%%02X%%%02X", 192 + band(arshift(n, 6), 31), 128 + band(n, 63)) + end + end) return str end diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md index baa8a9d36..d0176a996 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,12 @@ +# New features in CC: Tweaked 1.113.1 + +* Update Japanese translation (konumatakaki). +* Improve performance of `textutils.urlEncode`. + +Several bug fixes: +* Fix overflow when converting recursive objects from Java to Lua. +* Fix websocket compression not working under Forge. + # New features in CC: Tweaked 1.113.0 * Allow placing printed pages and books in lecterns. diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index 7ce19b2a1..c3684a632 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,13 +1,10 @@ -New features in CC: Tweaked 1.113.0 +New features in CC: Tweaked 1.113.1 -* Allow placing printed pages and books in lecterns. +* Update Japanese translation (konumatakaki). +* Improve performance of `textutils.urlEncode`. Several bug fixes: -* Various documentation fixes (MCJack123) -* Fix computers and turtles not being dropped when exploded with TNT. -* Fix crash when turtles are broken while mining a block. -* Fix pocket computer terminals not updating when in the off-hand. -* Fix disk drives not being exposed as a peripheral. -* Fix item details being non-serialisable due to duplicated tables. +* Fix overflow when converting recursive objects from Java to Lua. +* Fix websocket compression not working under Forge. Type "help changelog" to see the full version history. diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua index 0161c6b09..47034fe42 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua @@ -6,8 +6,7 @@ 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 -in real time. +format compared to raw PCM data, only using 1 bit per sample, but is simple enough to encode and decode in real time. Typically DFPWM audio is read from [the filesystem][`fs.ReadHandle`] or a [a web request][`http.Response`] as a string, and converted a format suitable for [`speaker.playAudio`]. diff --git a/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua b/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua index 95a50b369..9c5980adf 100644 --- a/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua @@ -202,6 +202,14 @@ describe("The os library", function() expect(xs[1]):eq(xs[2]) end) + it("handles recursive tables", function() + local tbl = {} + tbl[1] = tbl + + local xs = roundtrip(tbl) + expect(xs):eq(xs[1]) + end) + it("does not preserve references in separate args", function() -- I'm not sure I like this behaviour, but it is what CC has always done. local tbl = {} diff --git a/projects/core/src/test/resources/test-rom/spec/apis/textutils_spec.lua b/projects/core/src/test/resources/test-rom/spec/apis/textutils_spec.lua index 4811e3b77..8ea92d717 100644 --- a/projects/core/src/test/resources/test-rom/spec/apis/textutils_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/apis/textutils_spec.lua @@ -296,6 +296,22 @@ describe("The textutils library", function() textutils.urlEncode("") expect.error(textutils.urlEncode, nil):eq("bad argument #1 (string expected, got nil)") end) + + it("encodes newlines", function() + expect(textutils.urlEncode("a\nb")):eq("a%0D%0Ab") + end) + + it("leaves normal characters as-is", function() + expect(textutils.urlEncode("abcABC0123")):eq("abcABC0123") + end) + + it("escapes spaces", function() + expect(textutils.urlEncode("a b c")):eq("a+b+c") + end) + + it("escapes special characters", function() + expect(textutils.urlEncode("a%b\0\255")):eq("a%25b%00%C3%BF") + end) end) describe("textutils.complete", function() diff --git a/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java b/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java index 6ac77e078..9f52028b4 100644 --- a/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java +++ b/projects/fabric/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java @@ -11,6 +11,7 @@ import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer; import net.fabricmc.loader.api.FabricLoader; import net.irisshaders.iris.api.v0.IrisApi; import net.irisshaders.iris.api.v0.IrisTextVertexSink; +import net.minecraft.util.FastColor; import java.nio.ByteBuffer; import java.util.Optional; @@ -54,12 +55,8 @@ public class IrisShaderMod implements ShaderMod.Provider { } @Override - public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - sink.quad(x1, y1, x2, y2, z, pack(rgba[0], rgba[1], rgba[2], rgba[3]), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); - } - - private static int pack(int r, int g, int b, int a) { - return (a & 255) << 24 | (b & 255) << 16 | (g & 255) << 8 | r & 255; + public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + sink.quad(x1, y1, x2, y2, z, FastColor.ABGR32.fromArgb32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); } } } diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index cce079683..8b30cc5cc 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -99,9 +99,16 @@ runs { } configurations { + val minecraftEmbed by registering { + isCanBeResolved = false + isCanBeConsumed = false + } + named("jarJar") { extendsFrom(minecraftEmbed.get()) } + val minecraftLibrary by registering { isCanBeResolved = true isCanBeConsumed = false + extendsFrom(minecraftEmbed.get()) } runtimeOnly { extendsFrom(minecraftLibrary.get()) } @@ -129,8 +136,11 @@ dependencies { clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) } implementation(project(":core")) { cct.exclude(this) } - "minecraftLibrary"(libs.cobalt) - "minecraftLibrary"(libs.jzlib) + "minecraftEmbed"(libs.cobalt) + "minecraftEmbed"(libs.jzlib) + + // We don't jar-in-jar our additional netty dependencies (see the tasks.jarJar configuration), but still want them + // on the legacy classpath. "minecraftLibrary"(libs.netty.http) "minecraftLibrary"(libs.netty.socks) "minecraftLibrary"(libs.netty.proxy) @@ -165,9 +175,17 @@ tasks.processResources { tasks.jar { archiveClassifier.set("slim") + duplicatesStrategy = DuplicatesStrategy.FAIL + // Include all classes from other projects except core. + val coreSources = project(":core").sourceSets["main"] for (source in cct.sourceDirectories.get()) { - if (source.classes && source.external) from(source.sourceSet.output) + if (source.classes && source.sourceSet != coreSources) from(source.sourceSet.output) + } + + // Include core separately, along with the relocated netty classes. + from(zipTree(project(":core").tasks.named("shadowJar", AbstractArchiveTask::class).map { it.archiveFile })) { + exclude("META-INF/**") } } @@ -175,15 +193,8 @@ tasks.sourcesJar { for (source in cct.sourceDirectories.get()) from(source.sourceSet.allSource) } -jarJar.enable() - tasks.jarJar { archiveClassifier.set("") - configuration(project.configurations["minecraftLibrary"]) - - for (source in cct.sourceDirectories.get()) { - if (source.classes) from(source.sourceSet.output) - } } tasks.assemble { dependsOn("jarJar") } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java b/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java index 909271c0a..84e90844d 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/integration/IrisShaderMod.java @@ -10,6 +10,7 @@ import dan200.computercraft.client.render.RenderTypes; import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer; import net.irisshaders.iris.api.v0.IrisApi; import net.irisshaders.iris.api.v0.IrisTextVertexSink; +import net.minecraft.util.FastColor; import net.neoforged.fml.ModList; import java.nio.ByteBuffer; @@ -57,12 +58,8 @@ public class IrisShaderMod implements ShaderMod.Provider { } @Override - public void quad(float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2) { - sink.quad(x1, y1, x2, y2, z, pack(rgba[0], rgba[1], rgba[2], rgba[3]), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); - } - - private static int pack(int r, int g, int b, int a) { - return (a & 255) << 24 | (b & 255) << 16 | (g & 255) << 8 | r & 255; + public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) { + sink.quad(x1, y1, x2, y2, z, FastColor.ABGR32.fromArgb32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP); } } }