mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 14:07:38 +00:00
Compare commits
41 Commits
v1.20.1-1.
...
v1.20.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f7a6aac657 | ||
![]() |
782564e6ab | ||
![]() |
6b8ba8b80b | ||
![]() |
52b76d8886 | ||
![]() |
ba36c69583 | ||
![]() |
370e5f92a0 | ||
![]() |
36d05e4774 | ||
![]() |
89d1be17c9 | ||
![]() |
0069591af9 | ||
![]() |
c36c8605bf | ||
![]() |
3c72a00d46 | ||
![]() |
58aefc8df8 | ||
![]() |
97ddfc2794 | ||
![]() |
6e4ec86586 | ||
![]() |
d24984c1d5 | ||
![]() |
8080dcdd9e | ||
![]() |
d7cea55e2a | ||
![]() |
9b2f974a81 | ||
![]() |
43770fa9bd | ||
![]() |
80c7a54ad4 | ||
![]() |
e57b6fede2 | ||
![]() |
34a2fd039f | ||
![]() |
3299d0e72a | ||
![]() |
b89e2615db | ||
![]() |
cdcd82679c | ||
![]() |
cdfa866760 | ||
![]() |
aa8078ddeb | ||
![]() |
7e53c19d74 | ||
![]() |
b7a8432cfb | ||
![]() |
356c8e8aeb | ||
![]() |
ed283155f7 | ||
![]() |
87dfad026e | ||
![]() |
bb97c465d9 | ||
![]() |
9484315d37 | ||
![]() |
be59f1a875 | ||
![]() |
bfb28b4710 | ||
![]() |
216f0adb3c | ||
![]() |
77af4bc213 | ||
![]() |
5abab982c7 | ||
![]() |
764e1aa332 | ||
![]() |
c47718b09d |
@@ -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/
|
||||
|
@@ -100,7 +100,7 @@ SPDX-License-Identifier = "CC0-1.0"
|
||||
path = ".github/**"
|
||||
|
||||
[[annotations]]
|
||||
path = ["gradle/wrapper/**", "gradlew", "gradlew.bat"]
|
||||
path = ["gradle/wrapper/**"]
|
||||
SPDX-FileCopyrightText = "Gradle Inc"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
|
@@ -143,7 +143,7 @@ fun getNextVersion(version: String): String {
|
||||
val lastIndex = mainVersion.lastIndexOf('.')
|
||||
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
|
||||
val lastVersion = try {
|
||||
version.substring(lastIndex + 1).toInt()
|
||||
mainVersion.substring(lastIndex + 1).toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
|
||||
}
|
||||
|
26
crowdin.yml
Normal file
26
crowdin.yml
Normal file
@@ -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
|
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
isUnstable=false
|
||||
modVersion=1.112.0
|
||||
modVersion=1.113.1
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.20.1
|
||||
|
@@ -71,6 +71,7 @@ librarian = "1.+"
|
||||
lwjgl = "3.3.3"
|
||||
minotaur = "2.+"
|
||||
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" }
|
||||
@@ -175,6 +177,7 @@ githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "g
|
||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
|
||||
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" }
|
||||
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
5
gradlew
vendored
5
gradlew
vendored
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -84,7 +86,8 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
@@ -13,7 +13,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
|
||||
* An item detail provider for {@link ItemStack}s whose {@link Item} has a specific type.
|
||||
*
|
||||
* @param <T> The type the stack's item must have.
|
||||
*/
|
||||
@@ -22,7 +22,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
||||
private final @Nullable String namespace;
|
||||
|
||||
/**
|
||||
* Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
|
||||
* Create a new item detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
|
||||
*
|
||||
* @param itemType The type the stack's item must have.
|
||||
* @param namespace The namespace to use for this provider.
|
||||
@@ -34,7 +34,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item detail provider. Meta will be inserted directly into the results.
|
||||
* Create a new item detail provider. Details will be inserted directly into the results.
|
||||
*
|
||||
* @param itemType The type the stack's item must have.
|
||||
*/
|
||||
@@ -53,21 +53,18 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
||||
* @param stack The item stack to provide details for.
|
||||
* @param item The item to provide details for.
|
||||
*/
|
||||
public abstract void provideDetails(
|
||||
Map<? super String, Object> data, ItemStack stack, T item
|
||||
);
|
||||
public abstract void provideDetails(Map<? super String, Object> data, ItemStack stack, T item);
|
||||
|
||||
@Override
|
||||
public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
|
||||
public final void provideDetails(Map<? super String, Object> data, ItemStack stack) {
|
||||
var item = stack.getItem();
|
||||
if (!itemType.isInstance(item)) return;
|
||||
|
||||
// If `namespace` is specified, insert into a new data map instead of the existing one.
|
||||
Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
|
||||
|
||||
provideDetails(child, stack, itemType.cast(item));
|
||||
|
||||
if (namespace != null) {
|
||||
if (namespace == null) {
|
||||
provideDetails(data, stack, itemType.cast(item));
|
||||
} else {
|
||||
Map<? super String, Object> child = new HashMap<>();
|
||||
provideDetails(child, stack, itemType.cast(item));
|
||||
data.put(namespace, child);
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ public interface DetailRegistry<T> {
|
||||
* @param provider The detail provider to register.
|
||||
* @see DetailProvider
|
||||
*/
|
||||
void addProvider(DetailProvider<T> provider);
|
||||
void addProvider(DetailProvider<? super T> provider);
|
||||
|
||||
/**
|
||||
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
|
||||
|
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
/**
|
||||
* An enum representing the two sides of the turtle that a turtle turtle might reside.
|
||||
* An enum representing the two sides of the turtle that a turtle upgrade might reside.
|
||||
*/
|
||||
public enum TurtleSide {
|
||||
/**
|
||||
|
@@ -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.bundles.externalMods.common)
|
||||
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
|
||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
@@ -73,6 +74,7 @@ public final class ClientRegistry {
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.LECTERN.get(), CustomLecternRenderer::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,15 +6,22 @@ package dan200.computercraft.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ContainerListener;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
||||
|
||||
@@ -23,40 +30,75 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
|
||||
*
|
||||
* @see dan200.computercraft.client.render.PrintoutRenderer
|
||||
*/
|
||||
public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
private final boolean book;
|
||||
private final int pages;
|
||||
private final TextBuffer[] text;
|
||||
private final TextBuffer[] colours;
|
||||
private int page;
|
||||
public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu> implements ContainerListener {
|
||||
private PrintoutInfo printout = PrintoutInfo.DEFAULT;
|
||||
private int page = 0;
|
||||
|
||||
public PrintoutScreen(HeldItemMenu container, Inventory player, Component title) {
|
||||
public PrintoutScreen(PrintoutMenu container, Inventory player, Component title) {
|
||||
super(container, player, title);
|
||||
|
||||
imageHeight = Y_SIZE;
|
||||
}
|
||||
|
||||
var text = PrintoutItem.getText(container.getStack());
|
||||
this.text = new TextBuffer[text.length];
|
||||
for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]);
|
||||
private void setPrintout(ItemStack stack) {
|
||||
var text = PrintoutItem.getText(stack);
|
||||
var textBuffers = new TextBuffer[text.length];
|
||||
for (var i = 0; i < textBuffers.length; i++) textBuffers[i] = new TextBuffer(text[i]);
|
||||
|
||||
var colours = PrintoutItem.getColours(container.getStack());
|
||||
this.colours = new TextBuffer[colours.length];
|
||||
for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]);
|
||||
var colours = PrintoutItem.getColours(stack);
|
||||
var colourBuffers = new TextBuffer[colours.length];
|
||||
for (var i = 0; i < colours.length; i++) colourBuffers[i] = new TextBuffer(colours[i]);
|
||||
|
||||
page = 0;
|
||||
pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1);
|
||||
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
|
||||
var pages = Math.max(text.length / PrintoutItem.LINES_PER_PAGE, 1);
|
||||
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
|
||||
|
||||
printout = new PrintoutInfo(pages, book, textBuffers, colourBuffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
menu.addSlotListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed() {
|
||||
menu.removeSlotListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void slotChanged(AbstractContainerMenu menu, int slot, ItemStack stack) {
|
||||
if (slot == 0) setPrintout(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataChanged(AbstractContainerMenu menu, int slot, int data) {
|
||||
if (slot == PrintoutMenu.DATA_CURRENT_PAGE) page = data;
|
||||
}
|
||||
|
||||
private void setPage(int page) {
|
||||
this.page = page;
|
||||
|
||||
var gameMode = Objects.requireNonNull(Objects.requireNonNull(minecraft).gameMode);
|
||||
gameMode.handleInventoryButtonClick(menu.containerId, PrintoutMenu.PAGE_BUTTON_OFFSET + page);
|
||||
}
|
||||
|
||||
private void previousPage() {
|
||||
if (page > 0) setPage(page - 1);
|
||||
}
|
||||
|
||||
private void nextPage() {
|
||||
if (page < printout.pages() - 1) setPage(page + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
if (key == GLFW.GLFW_KEY_RIGHT) {
|
||||
if (page < pages - 1) page++;
|
||||
nextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == GLFW.GLFW_KEY_LEFT) {
|
||||
if (page > 0) page--;
|
||||
previousPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,13 +110,13 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
if (super.mouseScrolled(x, y, delta)) return true;
|
||||
if (delta < 0) {
|
||||
// Scroll up goes to the next page
|
||||
if (page < pages - 1) page++;
|
||||
nextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (delta > 0) {
|
||||
// Scroll down goes to the previous page
|
||||
if (page > 0) page--;
|
||||
previousPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -85,8 +127,9 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
// Draw the printout
|
||||
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
||||
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
|
||||
|
||||
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
|
||||
renderer.endBatch();
|
||||
}
|
||||
|
||||
@@ -105,4 +148,18 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||
// Skip rendering labels.
|
||||
}
|
||||
|
||||
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
|
||||
public static final PrintoutInfo DEFAULT;
|
||||
|
||||
static {
|
||||
var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
|
||||
Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
|
||||
|
||||
var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
|
||||
Arrays.fill(colourLines, new TextBuffer("f".repeat(PrintoutItem.LINE_MAX_LENGTH)));
|
||||
|
||||
DEFAULT = new PrintoutInfo(1, false, textLines, colourLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A model for {@linkplain PrintoutItem printouts} placed on a lectern.
|
||||
* <p>
|
||||
* This provides two models, {@linkplain #renderPages(PoseStack, VertexConsumer, int, int, int) one for a variable
|
||||
* number of pages}, and {@linkplain #renderBook(PoseStack, VertexConsumer, int, int) one for books}.
|
||||
*
|
||||
* @see CustomLecternRenderer
|
||||
*/
|
||||
public class LecternPrintoutModel {
|
||||
public static final ResourceLocation TEXTURE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/printout");
|
||||
public static final Material MATERIAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE);
|
||||
|
||||
private static final int TEXTURE_WIDTH = 32;
|
||||
private static final int TEXTURE_HEIGHT = 32;
|
||||
|
||||
private static final String PAGE_1 = "page_1";
|
||||
private static final String PAGE_2 = "page_2";
|
||||
private static final String PAGE_3 = "page_3";
|
||||
private static final List<String> PAGES = List.of(PAGE_1, PAGE_2, PAGE_3);
|
||||
|
||||
private final ModelPart pagesRoot;
|
||||
private final ModelPart bookRoot;
|
||||
private final ModelPart[] pages;
|
||||
|
||||
public LecternPrintoutModel() {
|
||||
pagesRoot = buildPages();
|
||||
bookRoot = buildBook();
|
||||
pages = PAGES.stream().map(pagesRoot::getChild).toArray(ModelPart[]::new);
|
||||
}
|
||||
|
||||
private static ModelPart buildPages() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
parts.addOrReplaceChild(
|
||||
PAGE_1,
|
||||
CubeListBuilder.create().texOffs(0, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
PAGE_2,
|
||||
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.125f, 0, 1.5f, (float) Math.PI * (1f / 16), 0, 0)
|
||||
);
|
||||
parts.addOrReplaceChild(
|
||||
PAGE_3,
|
||||
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.25f, 0, -1.5f, (float) -Math.PI * (2f / 16), 0, 0)
|
||||
);
|
||||
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
private static ModelPart buildBook() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"spine",
|
||||
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
|
||||
var angle = (float) Math.toRadians(5);
|
||||
parts.addOrReplaceChild(
|
||||
"left",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
|
||||
);
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"right",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
|
||||
);
|
||||
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
public void renderBook(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay) {
|
||||
bookRoot.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {
|
||||
if (pageCount > pages.length) pageCount = pages.length;
|
||||
var i = 0;
|
||||
for (; i < pageCount; i++) pages[i].visible = true;
|
||||
for (; i < pages.length; i++) pages[i].visible = false;
|
||||
|
||||
pagesRoot.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.LecternRenderer;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
|
||||
/**
|
||||
* A block entity renderer for our {@linkplain CustomLecternBlockEntity lectern}.
|
||||
* <p>
|
||||
* This largely follows {@link LecternRenderer}, but with support for multiple types of item.
|
||||
*/
|
||||
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> {
|
||||
private final LecternPrintoutModel printoutModel;
|
||||
|
||||
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
|
||||
printoutModel = new LecternPrintoutModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5f, 1.0625f, 0.5f);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-lectern.getBlockState().getValue(LecternBlock.FACING).getClockWise().toYRot()));
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(67.5f));
|
||||
poseStack.translate(0, -0.125f, 0);
|
||||
|
||||
var item = lectern.getItem();
|
||||
if (item.getItem() instanceof PrintoutItem printout) {
|
||||
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
|
||||
if (printout.getType() == PrintoutItem.Type.BOOK) {
|
||||
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
|
||||
} else {
|
||||
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutItem.getPageCount(item));
|
||||
}
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.util.ARGB32;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -92,16 +93,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
|
||||
ARGB32.opaque(colour), RenderTypes.FULL_BRIGHT_LIGHTMAP
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -13,9 +13,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 dan200.computercraft.shared.util.ARGB32;
|
||||
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.*;
|
||||
@@ -38,10 +40,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;
|
||||
|
||||
@@ -158,8 +162,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 {
|
||||
@@ -167,7 +171,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 {
|
||||
@@ -177,12 +181,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.
|
||||
@@ -196,16 +200,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.
|
||||
var colourAbgr = ARGB32.toABGR32(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);
|
||||
@@ -214,10 +217,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);
|
||||
@@ -226,10 +226,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);
|
||||
@@ -238,10 +235,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);
|
||||
|
@@ -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.vertex(poseMatrix, x1, y1, z).color(r, g, b, a).uv(u1, v1).uv2(light).endVertex();
|
||||
consumer.vertex(poseMatrix, x1, y2, z).color(r, g, b, a).uv(u1, v2).uv2(light).endVertex();
|
||||
|
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.data.client;
|
||||
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.data.DataProviders;
|
||||
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
|
||||
@@ -30,7 +31,8 @@ public final class ClientDataProviders {
|
||||
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||
out.accept(new ResourceLocation("blocks"), List.of(
|
||||
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
|
||||
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
|
||||
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty()),
|
||||
new SingleFile(LecternPrintoutModel.TEXTURE, Optional.empty())
|
||||
));
|
||||
out.accept(GuiSprites.SPRITE_SHEET, Stream.of(
|
||||
// Buttons
|
||||
|
8
projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json
generated
Normal file
8
projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=east": {"model": "minecraft:block/lectern", "y": 90},
|
||||
"facing=north": {"model": "minecraft:block/lectern", "y": 0},
|
||||
"facing=south": {"model": "minecraft:block/lectern", "y": 180},
|
||||
"facing=west": {"model": "minecraft:block/lectern", "y": 270}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"sources": [
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"},
|
||||
{"type": "minecraft:single", "resource": "computercraft:entity/printout"}
|
||||
]
|
||||
}
|
||||
|
12
projects/common/src/generated/resources/data/computercraft/loot_tables/blocks/lectern.json
generated
Normal file
12
projects/common/src/generated/resources/data/computercraft/loot_tables/blocks/lectern.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"bonus_rolls": 0.0,
|
||||
"conditions": [{"condition": "minecraft:survives_explosion"}],
|
||||
"entries": [{"type": "minecraft:item", "name": "minecraft:lectern"}],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "computercraft:blocks/lectern"
|
||||
}
|
@@ -23,6 +23,7 @@ import net.minecraft.data.models.BlockModelGenerators;
|
||||
import net.minecraft.data.models.blockstates.*;
|
||||
import net.minecraft.data.models.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
@@ -100,6 +101,11 @@ class BlockModelProvider {
|
||||
registerTurtleUpgrade(generators, "block/turtle_speaker", "block/turtle_speaker_face");
|
||||
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
|
||||
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
||||
|
||||
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(
|
||||
ModRegistry.Blocks.LECTERN.get(),
|
||||
Variant.variant().with(VariantProperties.MODEL, ModelLocationUtils.getModelLocation(Blocks.LECTERN))
|
||||
).with(createHorizontalFacingDispatch()));
|
||||
}
|
||||
|
||||
private static void registerDiskDrive(BlockModelGenerators generators) {
|
||||
|
@@ -284,7 +284,9 @@ public final class LanguageProvider implements DataProvider {
|
||||
return Stream.of(
|
||||
RegistryWrappers.BLOCKS.stream()
|
||||
.filter(x -> RegistryWrappers.BLOCKS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||
.map(Block::getDescriptionId),
|
||||
.map(Block::getDescriptionId)
|
||||
// Exclude blocks that just reuse vanilla translations, such as the lectern.
|
||||
.filter(x -> !x.startsWith("block.minecraft.")),
|
||||
RegistryWrappers.ITEMS.stream()
|
||||
.filter(x -> RegistryWrappers.ITEMS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||
.map(Item::getDescriptionId),
|
||||
|
@@ -15,6 +15,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
||||
import net.minecraft.advancements.critereon.StatePropertiesPredicate;
|
||||
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.storage.loot.LootPool;
|
||||
import net.minecraft.world.level.storage.loot.LootTable;
|
||||
@@ -57,6 +58,8 @@ class LootTableProvider {
|
||||
computerDrop(add, ModRegistry.Blocks.TURTLE_NORMAL);
|
||||
computerDrop(add, ModRegistry.Blocks.TURTLE_ADVANCED);
|
||||
|
||||
blockDrop(add, ModRegistry.Blocks.LECTERN, LootItem.lootTableItem(Items.LECTERN), ExplosionCondition.survivesExplosion());
|
||||
|
||||
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable(), LootTable
|
||||
.lootTable()
|
||||
.withPool(LootPool.lootPool()
|
||||
|
@@ -102,8 +102,14 @@ class TagProvider {
|
||||
ModRegistry.Items.MONITOR_ADVANCED.get()
|
||||
);
|
||||
|
||||
// Allow printed books to be placed in bookshelves.
|
||||
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
|
||||
|
||||
// Allow any printout to be placed on lecterns. See also PrintoutItem and CustomLecternBlock.
|
||||
tags.tag(ItemTags.LECTERN_BOOKS).add(
|
||||
ModRegistry.Items.PRINTED_PAGE.get(), ModRegistry.Items.PRINTED_PAGES.get(), ModRegistry.Items.PRINTED_BOOK.get()
|
||||
);
|
||||
|
||||
tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
|
||||
.add(Items.GLASS_BOTTLE)
|
||||
.addTag(ItemTags.BOATS);
|
||||
|
@@ -15,7 +15,7 @@ import java.util.*;
|
||||
* @param <T> The type of object that this registry provides details for.
|
||||
*/
|
||||
public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
||||
private final Collection<DetailProvider<T>> providers = new ArrayList<>();
|
||||
private final Collection<DetailProvider<? super T>> providers = new ArrayList<>();
|
||||
private final DetailProvider<T> basic;
|
||||
|
||||
public DetailRegistryImpl(DetailProvider<T> basic) {
|
||||
@@ -24,7 +24,7 @@ public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addProvider(DetailProvider<T> provider) {
|
||||
public synchronized void addProvider(DetailProvider<? super T> provider) {
|
||||
Objects.requireNonNull(provider, "provider cannot be null");
|
||||
if (!providers.contains(provider)) providers.add(provider);
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@ import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
|
||||
import dan200.computercraft.shared.common.ClearColourRecipe;
|
||||
import dan200.computercraft.shared.common.ColourableRecipe;
|
||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||
@@ -41,6 +40,9 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||
import dan200.computercraft.shared.details.BlockDetails;
|
||||
import dan200.computercraft.shared.details.ItemDetails;
|
||||
import dan200.computercraft.shared.integration.PermissionRegistry;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlock;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.media.items.RecordMedia;
|
||||
@@ -49,7 +51,6 @@ import dan200.computercraft.shared.media.recipes.DiskRecipe;
|
||||
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import dan200.computercraft.shared.network.container.ContainerData;
|
||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
||||
@@ -101,10 +102,12 @@ import net.minecraft.world.item.crafting.CustomRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||
|
||||
@@ -172,6 +175,10 @@ public final class ModRegistry {
|
||||
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
|
||||
() -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
|
||||
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
|
||||
|
||||
public static final RegistryEntry<CustomLecternBlock> LECTERN = REGISTRY.register("lectern", () -> new CustomLecternBlock(
|
||||
BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.5F).sound(SoundType.WOOD).ignitedByLava()
|
||||
));
|
||||
}
|
||||
|
||||
public static class BlockEntities {
|
||||
@@ -213,6 +220,8 @@ public final class ModRegistry {
|
||||
ofBlock(Blocks.WIRELESS_MODEM_NORMAL, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_NORMAL.get(), p, s, false));
|
||||
public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_ADVANCED =
|
||||
ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_ADVANCED.get(), p, s, true));
|
||||
|
||||
public static final RegistryEntry<BlockEntityType<CustomLecternBlockEntity>> LECTERN = ofBlock(Blocks.LECTERN, CustomLecternBlockEntity::new);
|
||||
}
|
||||
|
||||
public static final class Items {
|
||||
@@ -309,11 +318,8 @@ public final class ModRegistry {
|
||||
public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
|
||||
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
||||
|
||||
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||
() -> ContainerData.toType(
|
||||
HeldItemContainerData::new,
|
||||
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
|
||||
));
|
||||
public static final RegistryEntry<MenuType<PrintoutMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||
() -> new MenuType<>((i, c) -> PrintoutMenu.createRemote(i), FeatureFlags.VANILLA_SET));
|
||||
}
|
||||
|
||||
static class ArgumentTypes {
|
||||
|
@@ -63,7 +63,7 @@ public class TableBuilder {
|
||||
/**
|
||||
* Get the number of columns for this table.
|
||||
* <p>
|
||||
* This will be the same as {@link #getHeaders()}'s length if it is is non-{@code null},
|
||||
* This will be the same as {@link #getHeaders()}'s length if it is non-{@code null},
|
||||
* otherwise the length of the first column.
|
||||
*
|
||||
* @return The number of columns.
|
||||
|
@@ -1,68 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class HeldItemMenu extends AbstractContainerMenu {
|
||||
private final ItemStack stack;
|
||||
private final InteractionHand hand;
|
||||
|
||||
public HeldItemMenu(MenuType<? extends HeldItemMenu> type, int id, Player player, InteractionHand hand) {
|
||||
super(type, id);
|
||||
|
||||
this.hand = hand;
|
||||
stack = player.getItemInHand(hand).copy();
|
||||
}
|
||||
|
||||
public ItemStack getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int slot) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
if (!player.isAlive()) return false;
|
||||
|
||||
var stack = player.getItemInHand(hand);
|
||||
return stack == this.stack || !stack.isEmpty() && !this.stack.isEmpty() && stack.getItem() == this.stack.getItem();
|
||||
}
|
||||
|
||||
public static class Factory implements MenuProvider {
|
||||
private final MenuType<HeldItemMenu> type;
|
||||
private final Component name;
|
||||
private final InteractionHand hand;
|
||||
|
||||
public Factory(MenuType<HeldItemMenu> type, ItemStack stack, InteractionHand hand) {
|
||||
this.type = type;
|
||||
name = stack.getHoverName();
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
|
||||
return new HeldItemMenu(type, id, player, hand);
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,9 +35,9 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootParams;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
|
||||
private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
|
||||
@@ -110,9 +110,19 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
return super.getCloneItemStack(world, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public List<ItemStack> getDrops(BlockState state, LootParams.Builder params) {
|
||||
if (params.getOptionalParameter(LootContextParams.BLOCK_ENTITY) instanceof AbstractComputerBlockEntity computer) {
|
||||
params = params.withDynamicDrop(DROP, out -> out.accept(getItem(computer)));
|
||||
}
|
||||
|
||||
return super.getDrops(state, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity tile, ItemStack tool) {
|
||||
// Don't drop blocks here - see onBlockHarvested.
|
||||
// Don't drop blocks here - see playerWillDestroy.
|
||||
player.awardStat(Stats.BLOCK_MINED.get(this));
|
||||
player.causeFoodExhaustion(0.005F);
|
||||
}
|
||||
@@ -120,25 +130,11 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
@Override
|
||||
public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
|
||||
super.playerWillDestroy(world, pos, state, player);
|
||||
if (!(world instanceof ServerLevel serverWorld)) return;
|
||||
if (!(world instanceof ServerLevel serverLevel)) return;
|
||||
|
||||
// We drop the item here instead of doing it in the harvest method, as we should
|
||||
// drop computers for creative players too.
|
||||
|
||||
var tile = world.getBlockEntity(pos);
|
||||
if (tile instanceof AbstractComputerBlockEntity computer) {
|
||||
var context = new LootParams.Builder(serverWorld)
|
||||
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
|
||||
.withParameter(LootContextParams.TOOL, player.getMainHandItem())
|
||||
.withParameter(LootContextParams.THIS_ENTITY, player)
|
||||
.withParameter(LootContextParams.BLOCK_ENTITY, tile)
|
||||
.withDynamicDrop(DROP, out -> out.accept(getItem(computer)));
|
||||
for (var item : state.getDrops(context)) {
|
||||
popResource(world, pos, item);
|
||||
}
|
||||
|
||||
state.spawnAfterBreak(serverWorld, pos, player.getMainHandItem(), true);
|
||||
}
|
||||
dropResources(state, serverLevel, pos, world.getBlockEntity(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -110,7 +110,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
fresh = false;
|
||||
computerID = computer.getID();
|
||||
|
||||
// If the on state has changed, mark as as dirty.
|
||||
// If the on state has changed, mark as dirty.
|
||||
var newOn = computer.isOn();
|
||||
if (on != newOn) {
|
||||
on = newOn;
|
||||
|
@@ -4,16 +4,17 @@
|
||||
|
||||
package dan200.computercraft.shared.container;
|
||||
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A basic implementation of {@link Container} which operates on a {@linkplain #getContents() list of stacks}.
|
||||
*/
|
||||
public interface BasicContainer extends Container {
|
||||
NonNullList<ItemStack> getContents();
|
||||
List<ItemStack> getContents();
|
||||
|
||||
@Override
|
||||
default int getContainerSize() {
|
||||
|
@@ -50,12 +50,12 @@ public class ItemDetails {
|
||||
if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) {
|
||||
var displayTag = tag.getCompound("display");
|
||||
if (displayTag.contains("Lore", Tag.TAG_LIST)) {
|
||||
var loreTag = displayTag.getList("Lore", Tag.TAG_STRING);
|
||||
data.put("lore", loreTag.stream()
|
||||
var lore = displayTag.getList("Lore", Tag.TAG_STRING).stream()
|
||||
.map(ItemDetails::parseTextComponent)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Component::getString)
|
||||
.toList());
|
||||
.toList();
|
||||
if (!lore.isEmpty()) data.put("lore", lore);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,142 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.lectern;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
/**
|
||||
* Extends {@link LecternBlock} with support for {@linkplain PrintoutItem printouts}.
|
||||
* <p>
|
||||
* Unlike the vanilla lectern, this block is never empty. If the book is removed from the lectern, it converts back to
|
||||
* its vanilla version (see {@link #clearLectern(Level, BlockPos, BlockState)}).
|
||||
*
|
||||
* @see PrintoutItem#useOn(UseOnContext) Placing books into a lectern.
|
||||
*/
|
||||
public class CustomLecternBlock extends LecternBlock {
|
||||
public CustomLecternBlock(Properties properties) {
|
||||
super(properties);
|
||||
registerDefaultState(defaultBlockState().setValue(HAS_BOOK, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a vanilla lectern with a custom one.
|
||||
*
|
||||
* @param level The current level.
|
||||
* @param pos The position of the lectern.
|
||||
* @param blockState The current state of the lectern.
|
||||
* @param item The item to place in the custom lectern.
|
||||
*/
|
||||
public static void replaceLectern(Level level, BlockPos pos, BlockState blockState, ItemStack item) {
|
||||
level.setBlockAndUpdate(pos, ModRegistry.Blocks.LECTERN.get().defaultBlockState()
|
||||
.setValue(HAS_BOOK, true)
|
||||
.setValue(FACING, blockState.getValue(FACING))
|
||||
.setValue(POWERED, blockState.getValue(POWERED)));
|
||||
|
||||
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity be) be.setItem(item.split(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a custom lectern and replace it with an empty vanilla one.
|
||||
*
|
||||
* @param level The current level.
|
||||
* @param pos The position of the lectern.
|
||||
* @param blockState The current state of the lectern.
|
||||
*/
|
||||
static void clearLectern(Level level, BlockPos pos, BlockState blockState) {
|
||||
level.setBlockAndUpdate(pos, Blocks.LECTERN.defaultBlockState()
|
||||
.setValue(HAS_BOOK, false)
|
||||
.setValue(FACING, blockState.getValue(FACING))
|
||||
.setValue(POWERED, blockState.getValue(POWERED)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState state) {
|
||||
return new ItemStack(Items.LECTERN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
// If we've no lectern, remove it.
|
||||
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern && lectern.getItem().isEmpty()) {
|
||||
clearLectern(level, pos, state);
|
||||
return;
|
||||
}
|
||||
|
||||
super.tick(state, level, pos, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
||||
if (state.is(newState.getBlock())) return;
|
||||
|
||||
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||
dropItem(level, pos, state, lectern.getItem().copy());
|
||||
}
|
||||
|
||||
super.onRemove(state, level, pos, newState, isMoving);
|
||||
}
|
||||
|
||||
private static void dropItem(Level level, BlockPos pos, BlockState state, ItemStack stack) {
|
||||
if (stack.isEmpty()) return;
|
||||
|
||||
var direction = state.getValue(FACING);
|
||||
var dx = 0.25 * direction.getStepX();
|
||||
var dz = 0.25 * direction.getStepZ();
|
||||
var entity = new ItemEntity(level, pos.getX() + 0.5 + dx, pos.getY() + 1, pos.getZ() + 0.5 + dz, stack);
|
||||
entity.setDefaultPickUpDelay();
|
||||
level.addFreshEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptionId() {
|
||||
return Blocks.LECTERN.getDescriptionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomLecternBlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new CustomLecternBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
|
||||
return level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern ? lectern.getRedstoneSignal() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||
if (player.isSecondaryUseActive()) {
|
||||
// When shift+clicked with an empty hand, drop the item and replace with the normal lectern.
|
||||
clearLectern(level, pos, state);
|
||||
} else {
|
||||
// Otherwise open the screen.
|
||||
player.openMenu(lectern);
|
||||
}
|
||||
|
||||
player.awardStat(Stats.INTERACT_WITH_LECTERN);
|
||||
}
|
||||
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.lectern;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.container.BasicContainer;
|
||||
import dan200.computercraft.shared.container.SingleContainerData;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ContainerData;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.LecternBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The block entity for our {@link CustomLecternBlock}.
|
||||
*
|
||||
* @see LecternBlockEntity
|
||||
*/
|
||||
public final class CustomLecternBlockEntity extends BlockEntity implements MenuProvider {
|
||||
private static final String NBT_ITEM = "Item";
|
||||
private static final String NBT_PAGE = "Page";
|
||||
|
||||
private ItemStack item = ItemStack.EMPTY;
|
||||
private int page, pageCount;
|
||||
|
||||
public CustomLecternBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModRegistry.BlockEntities.LECTERN.get(), pos, blockState);
|
||||
}
|
||||
|
||||
public ItemStack getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
void setItem(ItemStack item) {
|
||||
this.item = item;
|
||||
itemChanged();
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
int getRedstoneSignal() {
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
var progress = pageCount > 1 ? (float) page / (pageCount - 1) : 1F;
|
||||
return Mth.floor(progress * 14f) + 1;
|
||||
}
|
||||
|
||||
return 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the item has changed. This sets up the state for the new item.
|
||||
*/
|
||||
private void itemChanged() {
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
pageCount = PrintoutItem.getPageCount(item);
|
||||
page = Mth.clamp(page, 0, pageCount - 1);
|
||||
} else {
|
||||
pageCount = page = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current page, emitting a redstone pulse if needed.
|
||||
*
|
||||
* @param page The new page.
|
||||
*/
|
||||
private void setPage(int page) {
|
||||
if (this.page == page) return;
|
||||
|
||||
this.page = page;
|
||||
setChanged();
|
||||
if (getLevel() != null) LecternBlock.signalPageChange(getLevel(), getBlockPos(), getBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
|
||||
item = tag.contains(NBT_ITEM, Tag.TAG_COMPOUND) ? ItemStack.of(tag.getCompound(NBT_ITEM)) : ItemStack.EMPTY;
|
||||
page = tag.getInt(NBT_PAGE);
|
||||
itemChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
|
||||
if (!item.isEmpty()) tag.put(NBT_ITEM, item.save(new CompoundTag()));
|
||||
if (item.getItem() instanceof PrintoutItem) tag.putInt(NBT_PAGE, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
var tag = super.getUpdateTag();
|
||||
tag.put(NBT_ITEM, item.save(new CompoundTag()));
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
|
||||
var item = getItem();
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
return new PrintoutMenu(
|
||||
containerId, new LecternContainer(), 0,
|
||||
p -> Container.stillValidBlockEntity(this, player, Container.DEFAULT_DISTANCE_LIMIT),
|
||||
new PrintoutContainerData()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return getItem().getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A read-only container storing the lectern's contents.
|
||||
*/
|
||||
private final class LecternContainer implements BasicContainer {
|
||||
private final List<ItemStack> itemView = new AbstractList<>() {
|
||||
@Override
|
||||
public ItemStack get(int index) {
|
||||
if (index != 0) throw new IndexOutOfBoundsException("Inventory only has one slot");
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getContents() {
|
||||
return itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
// Should never happen, so a no-op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return !isRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ContainerData} for a {@link PrintoutMenu}. This provides a read/write view of the current page.
|
||||
*/
|
||||
private final class PrintoutContainerData implements SingleContainerData {
|
||||
@Override
|
||||
public int get() {
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, int value) {
|
||||
if (index == 0) setPage(value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,136 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.media;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.container.InvisibleSlot;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The menus for {@linkplain PrintoutItem printouts}.
|
||||
* <p>
|
||||
* This is a somewhat similar design to {@link LecternMenu}, which is used to read written books.
|
||||
* <p>
|
||||
* This holds a single slot (containing the printout), and a single data slot ({@linkplain #DATA_CURRENT_PAGE holding
|
||||
* the current page}). The page is set by the client by sending a {@linkplain #clickMenuButton(Player, int) button
|
||||
* press} with an index of {@link #PAGE_BUTTON_OFFSET} plus the current page.
|
||||
* <p>
|
||||
* The client-side screen uses {@linkplain ContainerListener container listeners} to subscribe to item and page changes.
|
||||
* However, listeners aren't fired on the client, so we copy {@link LecternMenu}'s hack and call
|
||||
* {@link #broadcastChanges()} whenever an item or data value are changed.
|
||||
*/
|
||||
public class PrintoutMenu extends AbstractContainerMenu {
|
||||
public static final int DATA_CURRENT_PAGE = 0;
|
||||
private static final int DATA_SIZE = 1;
|
||||
|
||||
public static final int PAGE_BUTTON_OFFSET = 100;
|
||||
|
||||
private final Predicate<Player> valid;
|
||||
private final ContainerData currentPage;
|
||||
|
||||
public PrintoutMenu(
|
||||
int containerId, Container container, int slotIdx, Predicate<Player> valid, ContainerData currentPage
|
||||
) {
|
||||
super(ModRegistry.Menus.PRINTOUT.get(), containerId);
|
||||
this.valid = valid;
|
||||
this.currentPage = currentPage;
|
||||
|
||||
addSlot(new InvisibleSlot(container, slotIdx) {
|
||||
@Override
|
||||
public void setChanged() {
|
||||
super.setChanged();
|
||||
slotsChanged(container); // Trigger listeners on the client.
|
||||
}
|
||||
});
|
||||
addDataSlots(currentPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link PrintoutMenu} for use a remote (client).
|
||||
*
|
||||
* @param containerId The current container id.
|
||||
* @return The constructed container.
|
||||
*/
|
||||
public static PrintoutMenu createRemote(int containerId) {
|
||||
return new PrintoutMenu(containerId, new SimpleContainer(1), 0, p -> true, new SimpleContainerData(DATA_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PrintoutMenu} for the printout in the current player's hand.
|
||||
*
|
||||
* @param containerId The current container id.
|
||||
* @param player The player to open the container.
|
||||
* @param hand The hand containing the item.
|
||||
* @return The constructed container.
|
||||
*/
|
||||
public static PrintoutMenu createInHand(int containerId, Player player, InteractionHand hand) {
|
||||
var currentStack = player.getItemInHand(hand);
|
||||
var currentItem = currentStack.getItem();
|
||||
|
||||
var slot = switch (hand) {
|
||||
case MAIN_HAND -> player.getInventory().selected;
|
||||
case OFF_HAND -> Inventory.SLOT_OFFHAND;
|
||||
};
|
||||
return new PrintoutMenu(
|
||||
containerId, player.getInventory(), slot,
|
||||
p -> player.getItemInHand(hand).getItem() == currentItem, new SimpleContainerData(DATA_SIZE)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return valid.test(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clickMenuButton(Player player, int id) {
|
||||
if (id >= PAGE_BUTTON_OFFSET) {
|
||||
var page = Mth.clamp(id - PAGE_BUTTON_OFFSET, 0, PrintoutItem.getPageCount(getPrintout()) - 1);
|
||||
setData(DATA_CURRENT_PAGE, page);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.clickMenuButton(player, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current printout.
|
||||
*
|
||||
* @return The current printout.
|
||||
*/
|
||||
public ItemStack getPrintout() {
|
||||
return getSlot(0).getItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page.
|
||||
*
|
||||
* @return The current page.
|
||||
*/
|
||||
public int getPage() {
|
||||
return currentPage.get(DATA_CURRENT_PAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(int id, int data) {
|
||||
super.setData(id, data);
|
||||
broadcastChanges(); // Trigger listeners on the client.
|
||||
}
|
||||
}
|
@@ -4,18 +4,23 @@
|
||||
|
||||
package dan200.computercraft.shared.media.items;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlock;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
@@ -50,12 +55,30 @@ public class PrintoutItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||
if (!world.isClientSide) {
|
||||
new HeldItemContainerData(hand)
|
||||
.open(player, new HeldItemMenu.Factory(ModRegistry.Menus.PRINTOUT.get(), player.getItemInHand(hand), hand));
|
||||
public InteractionResult useOn(UseOnContext context) {
|
||||
var level = context.getLevel();
|
||||
var blockPos = context.getClickedPos();
|
||||
var blockState = level.getBlockState(blockPos);
|
||||
if (blockState.is(Blocks.LECTERN) && !blockState.getValue(LecternBlock.HAS_BOOK)) {
|
||||
// If we have an empty lectern, place our book into it.
|
||||
if (!level.isClientSide) {
|
||||
CustomLecternBlock.replaceLectern(level, blockPos, blockState, context.getItemInHand());
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||
var stack = player.getItemInHand(hand);
|
||||
if (!world.isClientSide) {
|
||||
var title = getTitle(stack);
|
||||
var displayTitle = Strings.isNullOrEmpty(title) ? stack.getDisplayName() : Component.literal(title);
|
||||
player.openMenu(new SimpleMenuProvider((id, playerInventory, p) -> PrintoutMenu.createInHand(id, p, hand), displayTitle));
|
||||
}
|
||||
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);
|
||||
}
|
||||
|
||||
private ItemStack createFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
|
||||
|
@@ -1,37 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.network.container;
|
||||
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
|
||||
/**
|
||||
* Opens a printout GUI based on the currently held item.
|
||||
*
|
||||
* @see HeldItemMenu
|
||||
* @see PrintoutItem
|
||||
*/
|
||||
public class HeldItemContainerData implements ContainerData {
|
||||
private final InteractionHand hand;
|
||||
|
||||
public HeldItemContainerData(InteractionHand hand) {
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
public HeldItemContainerData(FriendlyByteBuf buffer) {
|
||||
hand = buffer.readEnum(InteractionHand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
buf.writeEnum(hand);
|
||||
}
|
||||
|
||||
public InteractionHand getHand() {
|
||||
return hand;
|
||||
}
|
||||
}
|
@@ -96,11 +96,13 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
|
||||
|
||||
@Override
|
||||
public void clearRemoved() {
|
||||
super.clearRemoved();
|
||||
updateMedia();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoved() {
|
||||
super.setRemoved();
|
||||
if (recordPlaying) stopRecord();
|
||||
}
|
||||
|
||||
|
@@ -70,10 +70,10 @@ public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
|
||||
) throws LuaException;
|
||||
|
||||
/**
|
||||
* Move a fluid from a connected fluid container into this oneone.
|
||||
* Move a fluid from a connected fluid container into this one.
|
||||
* <p>
|
||||
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
|
||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
||||
* network</em>. Both containers must be attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Container to move fluid to.
|
||||
* @param computer The current computer.
|
||||
|
@@ -30,6 +30,12 @@ import java.util.Objects;
|
||||
* print("On something else")
|
||||
* end
|
||||
* }</pre>
|
||||
* <p>
|
||||
* ## Recipes
|
||||
* <div class="recipe-container">
|
||||
* <mc-recipe recipe="computercraft:pocket_computer_normal"></mc-recipe>
|
||||
* <mc-recipe recipe="computercraft:pocket_computer_advanced"></mc-recipe>
|
||||
* </div>
|
||||
*
|
||||
* @cc.module pocket
|
||||
*/
|
||||
|
@@ -128,12 +128,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) {
|
||||
public void inventoryTick(ItemStack stack, Level world, Entity entity, int compartmentSlot, boolean selected) {
|
||||
// This (in vanilla at least) is only called for players. Don't bother to handle other entities.
|
||||
if (world.isClientSide || !(entity instanceof ServerPlayer player)) return;
|
||||
|
||||
// Find the actual slot the item exists in, aborting if it can't be found.
|
||||
var slot = InventoryUtil.getInventorySlotFromCompartment(player, compartmentSlot, stack);
|
||||
if (slot < 0) return;
|
||||
|
||||
// If we're in the inventory, create a computer and keep it alive.
|
||||
var holder = new PocketHolder.PlayerHolder(player, slotNum);
|
||||
var holder = new PocketHolder.PlayerHolder(player, slot);
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
brain.computer().keepAlive();
|
||||
|
||||
|
@@ -47,18 +47,24 @@ import java.util.Optional;
|
||||
* <p>
|
||||
* ## Turtle upgrades
|
||||
* While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles
|
||||
* can be upgraded with *tools* and [peripherals][`peripheral`]. Turtles have two upgrade slots, one on the left and right
|
||||
* sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the [`turtle.equipLeft`]/[`turtle.equipRight`]
|
||||
* functions.
|
||||
* can be upgraded with upgrades. Turtles have two upgrade slots, one on the left and right sides. Upgrades can be
|
||||
* equipped by crafting a turtle with the upgrade, or calling the [`turtle.equipLeft`]/[`turtle.equipRight`] functions.
|
||||
* <p>
|
||||
* Turtle tools allow you to break blocks ([`turtle.dig`]) and attack entities ([`turtle.attack`]). Some tools are more
|
||||
* suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more
|
||||
* damage. Other tools have more niche use-cases, for instance hoes can til dirt.
|
||||
* By default, any diamond tool may be used as an upgrade (though more may be added with [datapacks]). The diamond
|
||||
* pickaxe may be used to break blocks (with [`turtle.dig`]), while the sword can attack entities ([`turtle.attack`]).
|
||||
* Other tools have more niche use-cases, for instance hoes can til dirt.
|
||||
* <p>
|
||||
* Peripherals (such as the [wireless modem][`modem`] or [`speaker`]) can also be equipped as upgrades. These are then
|
||||
* accessible by accessing the `"left"` or `"right"` peripheral.
|
||||
* Some peripherals (namely [speakers][`speaker`] and Ender and Wireless [modems][`modem`]) can also be equipped as
|
||||
* upgrades. These are then accessible by accessing the `"left"` or `"right"` peripheral.
|
||||
* <p>
|
||||
* ## Recipes
|
||||
* <div class="recipe-container">
|
||||
* <mc-recipe recipe="computercraft:turtle_normal"></mc-recipe>
|
||||
* <mc-recipe recipe="computercraft:turtle_advanced"></mc-recipe>
|
||||
* </div>
|
||||
* <p>
|
||||
* [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics"
|
||||
* [datapacks]: https://datapacks.madefor.cc ""
|
||||
*
|
||||
* @cc.module turtle
|
||||
* @cc.since 1.3
|
||||
@@ -345,7 +351,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
* For instance, if a slot contains 13 blocks of dirt, it has room for another 51.
|
||||
*
|
||||
* @param slot The slot we wish to check. Defaults to the {@link #select selected slot}.
|
||||
* @return The space left in in this slot.
|
||||
* @return The space left in this slot.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
*/
|
||||
@LuaFunction
|
||||
|
@@ -125,11 +125,15 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
||||
if (state.is(newState.getBlock())) return;
|
||||
|
||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity turtle && !turtle.hasMoved()) {
|
||||
Containers.dropContents(level, pos, turtle);
|
||||
}
|
||||
// Most blocks drop items and then remove the BE. However, if a turtle is consuming drops right now, that can
|
||||
// lead to loops where it tries to insert an item back into the inventory. To prevent this, take a reference to
|
||||
// the turtle BE now, remove it, and then drop the items.
|
||||
var turtle = !level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity t && !t.hasMoved()
|
||||
? t : null;
|
||||
|
||||
super.onRemove(state, level, pos, newState, isMoving);
|
||||
|
||||
if (turtle != null) Containers.dropContents(level, pos, turtle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import net.minecraft.util.FastColor;
|
||||
|
||||
/**
|
||||
* Utilities for working with 32-bit ARGB colours.
|
||||
*
|
||||
* @see FastColor.ARGB32
|
||||
*/
|
||||
public final class ARGB32 {
|
||||
private ARGB32() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha channel to be fully opaque.
|
||||
*
|
||||
* @param colour The colour to make opaque.
|
||||
* @return The fully-opaque colour
|
||||
*/
|
||||
public static int opaque(int colour) {
|
||||
return 0xFF000000 | colour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ARGB32 colour to a {@linkplain FastColor.ABGR32 ABGR32} one.
|
||||
*
|
||||
* @param colour The colour to convert.
|
||||
* @return The converted colour.
|
||||
*/
|
||||
public static int toABGR32(int colour) {
|
||||
// Swap B and R components, converting ARGB32 to ABGR32.
|
||||
return colour & 0xFF00FF00 | (colour & 0xFF0000) >> 16 | (colour & 0xFF) << 16;
|
||||
}
|
||||
}
|
@@ -9,9 +9,12 @@ import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
@@ -35,6 +38,28 @@ public final class InventoryUtil {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a slot inside a player's compartment to a slot in the full player's inventory.
|
||||
* <p>
|
||||
* {@link Inventory#tick()} passes in a slot to {@link Item#inventoryTick(ItemStack, Level, Entity, int, boolean)}.
|
||||
* However, this slot corresponds to the index within the current compartment (items, armour, offhand) and not
|
||||
* the actual slot.
|
||||
* <p>
|
||||
* This method searches the relevant compartments (inventory and offhand, skipping armour) for the stack, returning
|
||||
* its slot if found.
|
||||
*
|
||||
* @param player The player holding the item.
|
||||
* @param slot The slot inside the compartment.
|
||||
* @param stack The stack being ticked.
|
||||
* @return The inventory slot, or {@code -1} if the item could not be found in the inventory.
|
||||
*/
|
||||
public static int getInventorySlotFromCompartment(Player player, int slot, ItemStack stack) {
|
||||
if (stack.isEmpty()) throw new IllegalArgumentException("Cannot search for empty stack");
|
||||
if (player.getInventory().getItem(slot) == stack) return slot;
|
||||
if (player.getInventory().getItem(Inventory.SLOT_OFFHAND) == stack) return Inventory.SLOT_OFFHAND;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
var vecStart = new Vec3(
|
||||
pos.getX() + 0.5 + 0.6 * side.getStepX(),
|
||||
|
@@ -109,8 +109,9 @@ public final class TickScheduler {
|
||||
return State.UNLOADED;
|
||||
} else {
|
||||
// This should be impossible: either the block entity is at the above position, or it has been removed.
|
||||
if (level.getBlockEntity(pos) != blockEntity) {
|
||||
throw new IllegalStateException("Expected " + blockEntity + " at " + pos);
|
||||
var currentBlockEntity = level.getBlockEntity(pos);
|
||||
if (currentBlockEntity != blockEntity) {
|
||||
throw new IllegalStateException("Expected " + blockEntity + " at " + pos + ", got " + currentBlockEntity);
|
||||
}
|
||||
|
||||
// Otherwise schedule a tick and remove it from the queue.
|
||||
|
@@ -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": "騒音",
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 212 B |
@@ -0,0 +1,3 @@
|
||||
SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
@@ -6,16 +6,11 @@ package dan200.computercraft.mixin.gametest;
|
||||
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.gametest.framework.GameTestInfo;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(GameTestHelper.class)
|
||||
public interface GameTestHelperAccessor {
|
||||
@Invoker
|
||||
AABB callGetBounds();
|
||||
|
||||
@Accessor
|
||||
GameTestInfo getTestInfo();
|
||||
}
|
||||
|
@@ -19,9 +19,11 @@ import net.minecraft.gametest.framework.GameTest
|
||||
import net.minecraft.gametest.framework.GameTestHelper
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.LeverBlock
|
||||
import net.minecraft.world.level.block.RedstoneLampBlock
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.lwjgl.glfw.GLFW
|
||||
@@ -115,6 +117,19 @@ class Computer_Test {
|
||||
thenOnComputer { callPeripheral("right", "size").assertArrayEquals(54) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a computer item is dropped on explosion.
|
||||
*/
|
||||
@GameTest
|
||||
fun Drops_on_explosion(context: GameTestHelper) = context.sequence {
|
||||
thenExecute {
|
||||
val explosionPos = Vec3.atCenterOf(context.absolutePos(BlockPos(2, 2, 2)))
|
||||
context.level.explode(null, explosionPos.x, explosionPos.y, explosionPos.z, 2.0f, Level.ExplosionInteraction.TNT)
|
||||
|
||||
context.assertItemEntityCountIs(ModRegistry.Items.COMPUTER_NORMAL.get(), 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the client can open the computer UI and interact with it.
|
||||
*/
|
||||
|
@@ -160,5 +160,7 @@ class Monitor_Test {
|
||||
}
|
||||
|
||||
thenScreenshot()
|
||||
|
||||
thenExecute { helper.level.dayTime = Times.NOON }
|
||||
}
|
||||
}
|
||||
|
@@ -53,5 +53,7 @@ class Printout_Test {
|
||||
}
|
||||
|
||||
thenScreenshot()
|
||||
|
||||
thenExecute { helper.level.dayTime = Times.NOON }
|
||||
}
|
||||
}
|
||||
|
@@ -693,6 +693,21 @@ class Turtle_Test {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a turtle can break a block that explodes, causing the turtle itself to explode.
|
||||
*
|
||||
* @see [#585](https://github.com/cc-tweaked/CC-Tweaked/issues/585).
|
||||
*/
|
||||
@GameTest
|
||||
fun Breaks_exploding_block(context: GameTestHelper) = context.sequence {
|
||||
thenOnComputer { turtle.dig(Optional.empty()) }
|
||||
thenWaitUntil { context.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 2)) }
|
||||
thenExecute {
|
||||
context.assertItemEntityCountIs(ModRegistry.Items.TURTLE_NORMAL.get(), 1)
|
||||
context.assertItemEntityCountIs(Items.BONE_BLOCK, 65)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render turtles as an item.
|
||||
*/
|
||||
|
@@ -22,6 +22,7 @@ import net.minecraft.world.Container
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.EntityType
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.context.UseOnContext
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
@@ -252,6 +253,16 @@ fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: Strin
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to [GameTestHelper.assertItemEntityCountIs], but searching anywhere in the structure bounds.
|
||||
*/
|
||||
fun GameTestHelper.assertItemEntityCountIs(expected: Item, count: Int) {
|
||||
val actualCount = getEntities(EntityType.ITEM).sumOf { if (it.item.`is`(expected)) it.item.count else 0 }
|
||||
if (actualCount != count) {
|
||||
throw GameTestAssertException("Expected $count ${expected.description.string} items to exist (found $actualCount)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getName(type: BlockEntityType<*>): ResourceLocation = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(type)!!
|
||||
|
||||
/**
|
||||
|
@@ -15,6 +15,7 @@ import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.client.gui.screens.TitleScreen
|
||||
import net.minecraft.client.tutorial.TutorialSteps
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.gametest.framework.*
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.sounds.SoundSource
|
||||
@@ -74,6 +75,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)
|
||||
@@ -93,7 +95,7 @@ object ClientTestHooks {
|
||||
LEVEL_NAME,
|
||||
LevelSettings("Test Level", GameType.CREATIVE, false, Difficulty.EASY, true, rules, WorldDataConfiguration.DEFAULT),
|
||||
WorldOptions(WorldOptions.randomSeed(), false, false),
|
||||
) { WorldPresets.createNormalWorldDimensions(it) }
|
||||
) { it.registryOrThrow(Registries.WORLD_PRESET).getOrThrow(WorldPresets.FLAT).createWorldDimensions() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,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--
|
||||
|
@@ -13,7 +13,13 @@ import dan200.computercraft.shared.computer.core.ServerContext
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.gametest.framework.*
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.level.GameRules
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
@@ -173,4 +179,23 @@ object TestHooks {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a hook that makes breaking a bone block spawn an explosion.
|
||||
*
|
||||
* It would be more Correct to register a custom block, but that's quite a lot of work, and doesn't seem worth it
|
||||
* for test code.
|
||||
*
|
||||
* See also [Turtle_Test.Breaks_exploding_block].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun onBeforeDestroyBlock(level: LevelAccessor, pos: BlockPos, state: BlockState): Boolean {
|
||||
if (state.block === Blocks.BONE_BLOCK && level is ServerLevel) {
|
||||
val explosionPos = Vec3.atCenterOf(pos)
|
||||
level.explode(null, explosionPos.x, explosionPos.y, explosionPos.z, 4.0f, Level.ExplosionInteraction.TNT)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
138
projects/common/src/testMod/resources/data/cctest/structures/computer_test.drops_on_explosion.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/computer_test.drops_on_explosion.snbt
generated
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [0, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 1], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 2], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 3], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [1, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [2, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:east,state:off}", nbt: {On: 0b, id: "computercraft:computer_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [3, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 1], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 2], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 3], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 1], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [1, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [2, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [3, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 1], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 2], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 3], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:obsidian",
|
||||
"minecraft:barrier",
|
||||
"minecraft:air",
|
||||
"computercraft:computer_normal{facing:east,state:off}"
|
||||
]
|
||||
}
|
139
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.breaks_exploding_block.snbt
generated
Normal file
139
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.breaks_exploding_block.snbt
generated
Normal file
@@ -0,0 +1,139 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [0, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [1, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [2, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [3, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 0], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 1], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 2], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 3], state: "minecraft:obsidian"},
|
||||
{pos: [4, 0, 4], state: "minecraft:obsidian"},
|
||||
{pos: [0, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 1], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 2], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 3], state: "minecraft:barrier"},
|
||||
{pos: [0, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [1, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [2, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.breaks_exploding_block", LeftUpgrade: "minecraft:diamond_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0}}, On: 1b, Owner: {LowerId: -5670393268852517359L, Name: "Player172", UpperId: 3578583684139923613L}, Slot: 0, Items: [{Count: 64b, Slot: 0b, id: "minecraft:bone_block"}], id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:bone_block{axis:y}"},
|
||||
{pos: [2, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [3, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 0], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 1], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 2], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 3], state: "minecraft:barrier"},
|
||||
{pos: [4, 1, 4], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 1], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:barrier"},
|
||||
{pos: [0, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [1, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [2, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [3, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 0], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 1], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 2], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 3], state: "minecraft:barrier"},
|
||||
{pos: [4, 2, 4], state: "minecraft:barrier"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:obsidian",
|
||||
"minecraft:barrier",
|
||||
"minecraft:bone_block{axis:y}",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||
]
|
||||
}
|
@@ -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: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 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: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 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: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 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: "cctest:netherite_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0, Enchantments: [{id: "minecraft:efficiency", lvl: 5s}], RepairCost: 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",
|
||||
|
@@ -8,9 +8,7 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The result of invoking a Lua method.
|
||||
@@ -55,6 +53,12 @@ public final class MethodResult {
|
||||
* <p>
|
||||
* In order to provide a custom object with methods, one may return a {@link IDynamicLuaObject}, or an arbitrary
|
||||
* class with {@link LuaFunction} annotations. Anything else will be converted to {@code nil}.
|
||||
* <p>
|
||||
* Shared objects in a {@link MethodResult} will preserve their sharing when converted to Lua values. For instance,
|
||||
* {@code Map<?, ?> m = new HashMap(); return MethodResult.of(m, m); } will return two values {@code a}, {@code b}
|
||||
* where {@code a == b}. The one exception to this is Java's singleton collections ({@link List#of()},
|
||||
* {@link Set#of()} and {@link Map#of()}), which are always converted to new table. This is not true for other
|
||||
* singleton collections, such as those provided by {@link Collections} or Guava.
|
||||
*
|
||||
* @param value The value to return to the calling Lua function.
|
||||
* @return A method result which returns immediately with the given value.
|
||||
|
@@ -38,7 +38,7 @@ public interface IPeripheral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called when when a computer is attaching to the peripheral.
|
||||
* Is called when a computer is attaching to the peripheral.
|
||||
* <p>
|
||||
* This will occur when a peripheral is placed next to an active computer, when a computer is turned on next to a
|
||||
* peripheral, when a turtle travels into a square next to a peripheral, or when a wired modem adjacent to this
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
|
@@ -101,7 +101,7 @@ public class FSAPI implements ILuaAPI {
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction
|
||||
public final String[] list(String path) throws LuaException {
|
||||
public final List<String> list(String path) throws LuaException {
|
||||
try (var ignored = environment.time(Metrics.FS_OPS)) {
|
||||
return getFileSystem().list(path);
|
||||
} catch (FileSystemException e) {
|
||||
|
@@ -122,7 +122,7 @@ public class OSAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
private static long getEpochForCalendar(Calendar c) {
|
||||
return c.getTime().getTime();
|
||||
return c.getTimeInMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +298,7 @@ public class OSAPI implements ILuaAPI {
|
||||
* textutils.formatTime(os.time())
|
||||
* }</pre>
|
||||
* @cc.since 1.2
|
||||
* @cc.changed 1.80pr1 Add support for getting the local local and UTC time.
|
||||
* @cc.changed 1.80pr1 Add support for getting the local and UTC time.
|
||||
* @cc.changed 1.82.0 Arguments are now case insensitive.
|
||||
* @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps.
|
||||
* @see #date To get a date table that can be converted with this function.
|
||||
|
@@ -177,9 +177,9 @@ public abstract class AbstractHandle {
|
||||
/**
|
||||
* Read the remainder of the file.
|
||||
*
|
||||
* @return The file, or {@code null} if at the end of it.
|
||||
* @return The remaining contents of the file, or {@code null} in the event of an error.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} if we are at the end.
|
||||
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} in the event of an error.
|
||||
* @cc.since 1.80pr1
|
||||
*/
|
||||
@Nullable
|
||||
|
@@ -57,7 +57,7 @@ interface AddressPredicate {
|
||||
prefixSize = Integer.parseInt(prefixSizeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidRuleException(String.format(
|
||||
"Invalid host host '%s': Cannot extract size of CIDR mask from '%s'.",
|
||||
"Invalid host '%s': Cannot extract size of CIDR mask from '%s'.",
|
||||
addressStr + '/' + prefixSizeStr, prefixSizeStr
|
||||
));
|
||||
}
|
||||
|
@@ -270,7 +270,7 @@ final class Generator<T> {
|
||||
}
|
||||
|
||||
// Fold over the original method's arguments, excluding the target in reverse. For each argument, we reduce
|
||||
// a method of type type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
|
||||
// a method of type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
|
||||
// until eventually we've flattened the whole list.
|
||||
for (var i = parameterTypes.size() - 1; i >= 0; i--) {
|
||||
handle = MethodHandles.foldArguments(handle, i + 1, argSelectors.get(i));
|
||||
|
@@ -122,6 +122,10 @@ public class FileSystem {
|
||||
}
|
||||
|
||||
var lastSlash = path.lastIndexOf('/');
|
||||
|
||||
// If the trailing segment is a "..", then just append another one.
|
||||
if (path.substring(lastSlash < 0 ? 0 : lastSlash + 1).equals("..")) return path + "/..";
|
||||
|
||||
if (lastSlash >= 0) {
|
||||
return path.substring(0, lastSlash);
|
||||
} else {
|
||||
@@ -145,7 +149,7 @@ public class FileSystem {
|
||||
return getMount(sanitizePath(path)).getAttributes(sanitizePath(path));
|
||||
}
|
||||
|
||||
public synchronized String[] list(String path) throws FileSystemException {
|
||||
public synchronized List<String> list(String path) throws FileSystemException {
|
||||
path = sanitizePath(path);
|
||||
var mount = getMount(path);
|
||||
|
||||
@@ -161,10 +165,8 @@ public class FileSystem {
|
||||
}
|
||||
|
||||
// Return list
|
||||
var array = new String[list.size()];
|
||||
list.toArray(array);
|
||||
Arrays.sort(array);
|
||||
return array;
|
||||
list.sort(Comparator.naturalOrder());
|
||||
return list;
|
||||
}
|
||||
|
||||
public synchronized boolean exists(String path) throws FileSystemException {
|
||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.methods.LuaMethod;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.util.LuaUtil;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.core.util.SanitisedError;
|
||||
import org.slf4j.Logger;
|
||||
@@ -94,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);
|
||||
@@ -162,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<Object, LuaValue> values) throws LuaError {
|
||||
@@ -183,26 +184,36 @@ public class CobaltLuaMachine implements ILuaMachine {
|
||||
return ValueFactory.valueOf(bytes);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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();
|
||||
values.put(object, wrapped);
|
||||
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 (Map.Entry<?, ?> pair : map.entrySet()) {
|
||||
for (var pair : map.entrySet()) {
|
||||
var key = toValue(pair.getKey(), values);
|
||||
var value = toValue(pair.getValue(), values);
|
||||
if (!key.isNil() && !value.isNil()) table.rawset(key, value);
|
||||
@@ -211,24 +222,29 @@ 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 (Object child : objects) table.rawset(++i, toValue(child, values));
|
||||
for (var child : objects) table.rawset(++i, toValue(child, values));
|
||||
return table;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var wrapped = wrapLuaObject(object);
|
||||
if (wrapped != null) {
|
||||
values.put(object, wrapped);
|
||||
return wrapped;
|
||||
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());
|
||||
|
@@ -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.
|
||||
* <p>
|
||||
* 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) {
|
||||
|
@@ -4,8 +4,6 @@
|
||||
|
||||
package dan200.computercraft.core.util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public enum Colour {
|
||||
BLACK(0x111111),
|
||||
RED(0xcc4c4c),
|
||||
@@ -30,15 +28,6 @@ public enum Colour {
|
||||
return Colour.VALUES[colour];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Colour fromHex(int colour) {
|
||||
for (var entry : VALUES) {
|
||||
if (entry.getHex() == colour) return entry;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private final int hex;
|
||||
private final float red, green, blue;
|
||||
|
||||
|
@@ -4,9 +4,18 @@
|
||||
|
||||
package dan200.computercraft.core.util;
|
||||
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class LuaUtil {
|
||||
private static final List<?> EMPTY_LIST = List.of();
|
||||
private static final Set<?> EMPTY_SET = Set.of();
|
||||
private static final Map<?, ?> EMPTY_MAP = Map.of();
|
||||
|
||||
public static Object[] consArray(Object value, Collection<?> rest) {
|
||||
if (rest.isEmpty()) return new Object[]{ value };
|
||||
|
||||
@@ -14,7 +23,33 @@ public class LuaUtil {
|
||||
var out = new Object[rest.size() + 1];
|
||||
out[0] = value;
|
||||
var i = 1;
|
||||
for (Object additionalType : rest) out[i++] = additionalType;
|
||||
for (var additionalType : rest) out[i++] = additionalType;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a value is a singleton collection, such as one created with {@link List#of()}.
|
||||
* <p>
|
||||
* 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 collection.
|
||||
*/
|
||||
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()}.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@@ -851,13 +851,32 @@ unserialise = unserialize -- GB version
|
||||
|
||||
--[[- Returns a JSON representation of the given data.
|
||||
|
||||
This function attempts to guess whether a table is a JSON array or
|
||||
object. However, empty tables are assumed to be empty objects - use
|
||||
[`textutils.empty_json_array`] to mark an empty array.
|
||||
|
||||
This is largely intended for interacting with various functions from the
|
||||
[`commands`] API, though may also be used in making [`http`] requests.
|
||||
|
||||
Lua has a rather different data model to Javascript/JSON. As a result, some Lua
|
||||
values do not serialise cleanly into JSON.
|
||||
|
||||
- Lua tables can contain arbitrary key-value pairs, but JSON only accepts arrays,
|
||||
and objects (which require a string key). When serialising a table, if it only
|
||||
has numeric keys, then it will be treated as an array. Otherwise, the table will
|
||||
be serialised to an object using the string keys. Non-string keys (such as numbers
|
||||
or tables) will be dropped.
|
||||
|
||||
A consequence of this is that an empty table will always be serialised to an object,
|
||||
not an array. [`textutils.empty_json_array`] may be used to express an empty array.
|
||||
|
||||
- Lua strings are an a sequence of raw bytes, and do not have any specific encoding.
|
||||
However, JSON strings must be valid unicode. By default, non-ASCII characters in a
|
||||
string are serialised to their unicode code point (for instance, `"\xfe"` is
|
||||
converted to `"\u00fe"`). The `unicode_strings` option may be set to treat all input
|
||||
strings as UTF-8.
|
||||
|
||||
- Lua does not distinguish between missing keys (`undefined` in JS) and ones explicitly
|
||||
set to `null`. As a result `{ x = nil }` is serialised to `{}`. [`textutils.json_null`]
|
||||
may be used to get an explicit null value (`{ x = textutils.json_null }` will serialise
|
||||
to `{"x": null}`).
|
||||
|
||||
@param[1] t The value to serialise. Like [`textutils.serialise`], this should not
|
||||
contain recursive tables or functions.
|
||||
@tparam[1,opt] {
|
||||
@@ -925,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
|
||||
|
||||
|
@@ -114,7 +114,7 @@ local vector = {
|
||||
--
|
||||
-- @tparam Vector self The first vector to compute the dot product of.
|
||||
-- @tparam Vector o The second vector to compute the dot product of.
|
||||
-- @treturn Vector The dot product of `self` and `o`.
|
||||
-- @treturn number The dot product of `self` and `o`.
|
||||
-- @usage v1:dot(v2)
|
||||
dot = function(self, o)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
|
@@ -1,3 +1,22 @@
|
||||
# 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.
|
||||
|
||||
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.
|
||||
|
||||
# New features in CC: Tweaked 1.112.0
|
||||
|
||||
* Report a custom error when using `!` instead of `not`.
|
||||
@@ -800,7 +819,7 @@ And several bug fixes:
|
||||
# New features in CC: Tweaked 1.86.2
|
||||
|
||||
* Fix peripheral.getMethods returning an empty table.
|
||||
* Update to Minecraft 1.15.2. This is currently alpha-quality and so is missing missing features and may be unstable.
|
||||
* Update to Minecraft 1.15.2. This is currently alpha-quality and so is missing features and may be unstable.
|
||||
|
||||
# New features in CC: Tweaked 1.86.1
|
||||
|
||||
@@ -1416,7 +1435,7 @@ And several bug fixes:
|
||||
* Turtles can now compare items in their inventories
|
||||
* Turtles can place signs with text on them with `turtle.place( [signText] )`
|
||||
* Turtles now optionally require fuel items to move, and can refuel themselves
|
||||
* The size of the the turtle inventory has been increased to 16
|
||||
* The size of the turtle inventory has been increased to 16
|
||||
* The size of the turtle screen has been increased
|
||||
* New turtle functions: `turtle.compareTo( [slotNum] )`, `turtle.craft()`, `turtle.attack()`, `turtle.attackUp()`, `turtle.attackDown()`, `turtle.dropUp()`, `turtle.dropDown()`, `turtle.getFuelLevel()`, `turtle.refuel()`
|
||||
* New disk function: disk.getID()
|
||||
|
@@ -1,13 +1,10 @@
|
||||
New features in CC: Tweaked 1.112.0
|
||||
New features in CC: Tweaked 1.113.1
|
||||
|
||||
* Report a custom error when using `!` instead of `not`.
|
||||
* Update several translations (zyxkad, MineKID-LP).
|
||||
* Add `cc.strings.split` function.
|
||||
* Update Japanese translation (konumatakaki).
|
||||
* Improve performance of `textutils.urlEncode`.
|
||||
|
||||
Several bug fixes:
|
||||
* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted.
|
||||
* Preserve item data when upgrading pocket computers.
|
||||
* Add missing bounds check to `cc.strings.wrap` (Lupus950).
|
||||
* Fix modems not moving with Create contraptions.
|
||||
* 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.
|
||||
|
@@ -6,14 +6,13 @@
|
||||
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`].
|
||||
|
||||
## Encoding and decoding files
|
||||
This modules exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
|
||||
This module exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
|
||||
The returned encoder/decoder is itself a function, which converts between the two kinds of data.
|
||||
|
||||
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
|
||||
@@ -21,9 +20,9 @@ a specific audio stream. Typically you will want to create a decoder for each st
|
||||
for each one you write.
|
||||
|
||||
## Converting audio to DFPWM
|
||||
DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it.
|
||||
DFPWM is not a popular file format and so standard audio processing tools may not have an option to export to it.
|
||||
Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java
|
||||
application or development builds of [FFmpeg].
|
||||
application or [FFmpeg] 5.1 or later.
|
||||
|
||||
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
|
||||
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
|
||||
@@ -211,7 +210,7 @@ end
|
||||
|
||||
--[[- A convenience function for encoding a complete file of audio at once.
|
||||
|
||||
This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place,
|
||||
This should only be used for complete pieces of audio. If you are writing multiple chunks to the same place,
|
||||
you should use an encoder returned by [`make_encoder`] instead.
|
||||
|
||||
@tparam { number... } input The table of amplitude data.
|
||||
|
@@ -5,11 +5,14 @@
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import com.google.common.io.CharStreams;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.time.Duration.ofSeconds;
|
||||
@@ -30,6 +33,26 @@ public class ComputerTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateObjects() {
|
||||
class CustomApi implements ILuaAPI {
|
||||
@Override
|
||||
public String[] getNames() {
|
||||
return new String[]{ "custom" };
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] getObjects() {
|
||||
return new Object[]{ List.of(), List.of() };
|
||||
}
|
||||
}
|
||||
|
||||
ComputerBootstrap.run("""
|
||||
local x, y = custom.getObjects()
|
||||
assert(x ~= y)
|
||||
""", i -> i.addApi(new CustomApi()), 50);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
var stream = ComputerTest.class.getClassLoader().getResourceAsStream("benchmark.lua");
|
||||
try (var reader = new InputStreamReader(Objects.requireNonNull(stream), StandardCharsets.UTF_8)) {
|
||||
|
@@ -158,6 +158,65 @@ describe("The fs library", function()
|
||||
expect(fs.combine("", "a")):eq("a")
|
||||
expect(fs.combine("a", "", "b", "c")):eq("a/b/c")
|
||||
end)
|
||||
|
||||
it("preserves pattern characters", function()
|
||||
expect(fs.combine("foo*?")):eq("foo*?")
|
||||
end)
|
||||
|
||||
it("sanitises paths", function()
|
||||
expect(fs.combine("foo\":<>|")):eq("foo")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("fs.getName", function()
|
||||
it("returns 'root' for the empty path", function()
|
||||
expect(fs.getName("")):eq("root")
|
||||
expect(fs.getName("foo/..")):eq("root")
|
||||
end)
|
||||
|
||||
it("returns the file name", function()
|
||||
expect(fs.getName("foo/bar")):eq("bar")
|
||||
expect(fs.getName("foo/bar/")):eq("bar")
|
||||
expect(fs.getName("../foo")):eq("foo")
|
||||
end)
|
||||
|
||||
it("returns '..' for parent directories", function()
|
||||
expect(fs.getName("..")):eq("..")
|
||||
end)
|
||||
|
||||
it("preserves pattern characters", function()
|
||||
expect(fs.getName("foo*?")):eq("foo*?")
|
||||
end)
|
||||
|
||||
it("sanitises paths", function()
|
||||
expect(fs.getName("foo\":<>|")):eq("foo")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("fs.getDir", function()
|
||||
it("returns '..' for the empty path", function()
|
||||
expect(fs.getDir("")):eq("..")
|
||||
expect(fs.getDir("foo/..")):eq("..")
|
||||
end)
|
||||
|
||||
it("returns the directory name", function()
|
||||
expect(fs.getDir("foo/bar")):eq("foo")
|
||||
expect(fs.getDir("foo/bar/")):eq("foo")
|
||||
expect(fs.getDir("../foo")):eq("..")
|
||||
end)
|
||||
|
||||
it("returns '..' for parent directories", function()
|
||||
expect(fs.getDir("..")):eq("../..")
|
||||
expect(fs.getDir("../..")):eq("../../..")
|
||||
end)
|
||||
|
||||
it("preserves pattern characters", function()
|
||||
expect(fs.getDir("foo*?/x")):eq("foo*?")
|
||||
end)
|
||||
|
||||
it("sanitises paths", function()
|
||||
expect(fs.getDir("foo\":<>|/x")):eq("foo")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("fs.getSize", function()
|
||||
@@ -200,6 +259,14 @@ describe("The fs library", function()
|
||||
handle.close()
|
||||
end)
|
||||
|
||||
it("reading an empty file returns nil", function()
|
||||
local file = create_test_file ""
|
||||
|
||||
local handle = fs.open(file, mode)
|
||||
expect(handle.read()):eq(nil)
|
||||
handle.close()
|
||||
end)
|
||||
|
||||
it("can read a line of text", function()
|
||||
local file = create_test_file "some\nfile\r\ncontents\n\n"
|
||||
|
||||
@@ -223,6 +290,16 @@ describe("The fs library", function()
|
||||
expect(handle.readLine(true)):eq(nil)
|
||||
handle.close()
|
||||
end)
|
||||
|
||||
it("readAll always returns a string", function()
|
||||
local contents = "some\nfile\ncontents"
|
||||
local file = create_test_file "some\nfile\ncontents"
|
||||
|
||||
local handle = fs.open(file, mode)
|
||||
expect(handle.readAll()):eq(contents)
|
||||
expect(handle.readAll()):eq("")
|
||||
handle.close()
|
||||
end)
|
||||
end
|
||||
|
||||
describe("reading", function()
|
||||
|
@@ -188,4 +188,39 @@ describe("The os library", function()
|
||||
expect.error(os.loadAPI, nil):eq("bad argument #1 (string expected, got nil)")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("os.queueEvent", function()
|
||||
local function roundtrip(...)
|
||||
local event_name = ("event_%08x"):format(math.random(1, 0x7FFFFFFF))
|
||||
os.queueEvent(event_name, ...)
|
||||
return select(2, os.pullEvent(event_name))
|
||||
end
|
||||
|
||||
it("preserves references in tables", function()
|
||||
local tbl = {}
|
||||
local xs = roundtrip({ tbl, tbl })
|
||||
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 = {}
|
||||
local xs, ys = roundtrip(tbl, tbl)
|
||||
expect(xs):ne(ys)
|
||||
end)
|
||||
|
||||
it("clones objects", function()
|
||||
local tbl = {}
|
||||
local xs = roundtrip(tbl)
|
||||
expect(xs):ne(tbl)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@@ -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()
|
||||
|
@@ -13,13 +13,13 @@ correct tokens and positions, and that it can report sensible error messages.
|
||||
We can lex some basic comments:
|
||||
|
||||
```lua
|
||||
-- A basic singleline comment comment
|
||||
-- A basic singleline comment
|
||||
--[ Not a multiline comment
|
||||
--[= Also not a multiline comment!
|
||||
```
|
||||
|
||||
```txt
|
||||
1:1-1:37 COMMENT -- A basic singleline comment comment
|
||||
1:1-1:29 COMMENT -- A basic singleline comment
|
||||
2:1-2:27 COMMENT --[ Not a multiline comment
|
||||
3:1-3:34 COMMENT --[= Also not a multiline comment!
|
||||
```
|
||||
|
@@ -8,6 +8,7 @@ import com.google.auto.service.AutoService;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.util.ARGB32;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.irisshaders.iris.api.v0.IrisTextVertexSink;
|
||||
@@ -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, ARGB32.toABGR32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
projects/fabric/src/generated/resources/data/minecraft/tags/items/lectern_books.json
generated
Normal file
4
projects/fabric/src/generated/resources/data/minecraft/tags/items/lectern_books.json
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"replace": false,
|
||||
"values": ["computercraft:printed_page", "computercraft:printed_pages", "computercraft:printed_book"]
|
||||
}
|
@@ -14,6 +14,7 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
|
||||
import net.minecraft.gametest.framework.GameTestRegistry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
@@ -26,6 +27,7 @@ public class TestMod implements ModInitializer, ClientModInitializer {
|
||||
ServerLifecycleEvents.SERVER_STARTED.addPhaseOrdering(Event.DEFAULT_PHASE, phase);
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(phase, TestHooks::onServerStarted);
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> CCTestCommand.register(dispatcher));
|
||||
PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> !TestHooks.onBeforeDestroyBlock(level, pos, state));
|
||||
|
||||
TestHooks.loadTests(GameTestRegistry::register);
|
||||
}
|
||||
|
@@ -105,6 +105,10 @@ minecraft {
|
||||
configurations {
|
||||
minecraftLibrary { extendsFrom(minecraftEmbed.get()) }
|
||||
|
||||
// Move minecraftLibrary/minecraftEmbed out of implementation, and into runtimeOnly.
|
||||
implementation { setExtendsFrom(extendsFrom - setOf(minecraftLibrary.get(), minecraftEmbed.get())) }
|
||||
runtimeOnly { extendsFrom(minecraftLibrary.get(), minecraftEmbed.get()) }
|
||||
|
||||
val testMinecraftLibrary by registering {
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
@@ -138,18 +142,11 @@ dependencies {
|
||||
minecraftEmbed(libs.jzlib) {
|
||||
jarJar.ranged(this, "[${libs.versions.jzlib.get()},)")
|
||||
}
|
||||
minecraftEmbed(libs.netty.http) {
|
||||
jarJar.ranged(this, "[${libs.versions.netty.get()},)")
|
||||
isTransitive = false
|
||||
}
|
||||
minecraftEmbed(libs.netty.socks) {
|
||||
jarJar.ranged(this, "[${libs.versions.netty.get()},)")
|
||||
isTransitive = false
|
||||
}
|
||||
minecraftEmbed(libs.netty.proxy) {
|
||||
jarJar.ranged(this, "[${libs.versions.netty.get()},)")
|
||||
isTransitive = false
|
||||
}
|
||||
// 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) { isTransitive = false }
|
||||
minecraftLibrary(libs.netty.socks) { isTransitive = false }
|
||||
minecraftLibrary(libs.netty.proxy) { isTransitive = false }
|
||||
|
||||
testFixturesApi(libs.bundles.test)
|
||||
testFixturesApi(libs.bundles.kotlin)
|
||||
@@ -195,9 +192,17 @@ tasks.sourcesJar {
|
||||
tasks.jarJar {
|
||||
finalizedBy("reobfJarJar")
|
||||
archiveClassifier.set("")
|
||||
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) 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/**")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,7 @@ import com.google.auto.service.AutoService;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.util.ARGB32;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.irisshaders.iris.api.v0.IrisTextVertexSink;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
@@ -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, ARGB32.toABGR32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
projects/forge/src/generated/resources/data/minecraft/tags/items/lectern_books.json
generated
Normal file
1
projects/forge/src/generated/resources/data/minecraft/tags/items/lectern_books.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"values": ["computercraft:printed_page", "computercraft:printed_pages", "computercraft:printed_book"]}
|
@@ -12,6 +12,7 @@ import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.RegisterGameTestsEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.level.BlockEvent;
|
||||
import net.minecraftforge.event.server.ServerStartedEvent;
|
||||
import net.minecraftforge.eventbus.api.EventPriority;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
@@ -26,6 +27,10 @@ public class TestMod {
|
||||
var bus = MinecraftForge.EVENT_BUS;
|
||||
bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer()));
|
||||
bus.addListener((RegisterCommandsEvent e) -> CCTestCommand.register(e.getDispatcher()));
|
||||
bus.addListener((BlockEvent.BreakEvent e) -> {
|
||||
if (TestHooks.onBeforeDestroyBlock(e.getLevel(), e.getPos(), e.getState())) e.setCanceled(true);
|
||||
});
|
||||
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> TestMod::onInitializeClient);
|
||||
|
||||
var modBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
@@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
|
||||
|
||||
package cc.tweaked.linter
|
||||
|
||||
import com.google.errorprone.BugPattern
|
||||
import com.google.errorprone.VisitorState
|
||||
import com.google.errorprone.bugpatterns.BugChecker
|
||||
import com.google.errorprone.matchers.Description
|
||||
import com.google.errorprone.util.ASTHelpers
|
||||
import com.sun.source.tree.*
|
||||
import com.sun.source.util.TreeScanner
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol
|
||||
import javax.lang.model.element.Modifier
|
||||
|
||||
@BugPattern(
|
||||
summary = "Checks that a methods invoke their super method.",
|
||||
explanation = """
|
||||
This extends ErrorProne's built in "MustCallSuper" with several additional Minecraft-specific methods.
|
||||
""",
|
||||
severity = BugPattern.SeverityLevel.ERROR,
|
||||
tags = [BugPattern.StandardTags.LIKELY_ERROR],
|
||||
)
|
||||
class ExtraMustCallSuper : BugChecker(), BugChecker.MethodTreeMatcher {
|
||||
companion object {
|
||||
private val REQUIRED_METHODS = setOf(
|
||||
MethodReference("net.minecraft.world.level.block.entity.BlockEntity", "setRemoved"),
|
||||
MethodReference("net.minecraft.world.level.block.entity.BlockEntity", "clearRemoved"),
|
||||
)
|
||||
}
|
||||
|
||||
override fun matchMethod(tree: MethodTree, state: VisitorState): Description {
|
||||
val methodSym: MethodSymbol = ASTHelpers.getSymbol(tree)
|
||||
if (methodSym.modifiers.contains(Modifier.ABSTRACT)) return Description.NO_MATCH
|
||||
|
||||
val superMethod: MethodReference = findRequiredSuper(methodSym, state) ?: return Description.NO_MATCH
|
||||
val foundSuper = SuperScanner(superMethod.method).scan(tree, Unit) ?: false
|
||||
if (foundSuper) return Description.NO_MATCH
|
||||
|
||||
return buildDescription(tree)
|
||||
.setMessage("This method overrides %s#%s but does not call the super method.".format(superMethod.owner, superMethod.method))
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun findRequiredSuper(method: MethodSymbol, state: VisitorState): MethodReference? {
|
||||
for (superMethod in ASTHelpers.findSuperMethods(method, state.types)) {
|
||||
val superName = MethodReference(superMethod.owner.qualifiedName.toString(), superMethod.name.toString())
|
||||
if (REQUIRED_METHODS.contains(superName)) return superName
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private data class MethodReference(val owner: String, val method: String)
|
||||
|
||||
private class SuperScanner(private val methodName: String) : TreeScanner<Boolean?, Unit>() {
|
||||
// Skip visiting other elements.
|
||||
override fun visitClass(tree: ClassTree, state: Unit): Boolean = false
|
||||
override fun visitLambdaExpression(tree: LambdaExpressionTree, state: Unit): Boolean = false
|
||||
|
||||
override fun visitMethodInvocation(tree: MethodInvocationTree, state: Unit): Boolean? {
|
||||
val methodSelect: ExpressionTree = tree.methodSelect
|
||||
if (methodSelect.kind == Tree.Kind.MEMBER_SELECT) {
|
||||
val memberSelect = methodSelect as MemberSelectTree
|
||||
if (ASTHelpers.isSuper(memberSelect.expression) && memberSelect.identifier.contentEquals(methodName)) return true
|
||||
}
|
||||
|
||||
return super.visitMethodInvocation(tree, state)
|
||||
}
|
||||
|
||||
override fun reduce(r1: Boolean?, r2: Boolean?): Boolean = (r1 ?: false) || (r2 ?: false)
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
cc.tweaked.linter.ExtraMustCallSuper
|
||||
cc.tweaked.linter.LoaderOverride
|
||||
cc.tweaked.linter.MissingLoaderOverride
|
||||
cc.tweaked.linter.SideChecker
|
||||
|
Reference in New Issue
Block a user