Merge branch 'mc-1.20.x' into mc-1.20.x-speaker-sound

This commit is contained in:
Jonathan Coates 2024-03-29 09:44:50 +00:00
commit f9c9b6b119
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
101 changed files with 2432 additions and 781 deletions

View File

@ -9,16 +9,16 @@ jobs:
steps: steps:
- name: 📥 Clone repository - name: 📥 Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: 📥 Set up Java - name: 📥 Set up Java
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: 'temurin' distribution: 'temurin'
- name: 📥 Setup Gradle - name: 📥 Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/actions/setup-gradle@v3
with: with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }} cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
@ -82,16 +82,16 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Java - name: Set up Java
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/actions/setup-gradle@v3
with: with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }} cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}

View File

@ -13,16 +13,16 @@ jobs:
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Java - name: Set up Java
uses: actions/setup-java@v1 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/actions/setup-gradle@v3
with: with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }} cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}

View File

@ -40,10 +40,6 @@ repositories {
val mainMaven = maven("https://squiddev.cc/maven") { val mainMaven = maven("https://squiddev.cc/maven") {
name = "SquidDev" name = "SquidDev"
content {
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
includeModule("org.spongepowered", "mixin")
}
} }
exclusiveContent { exclusiveContent {
@ -113,6 +109,8 @@ sourceSets.all {
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull") option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
option("NullAway:CheckOptionalEmptiness") option("NullAway:CheckOptionalEmptiness")
option("NullAway:AcknowledgeRestrictiveAnnotations") option("NullAway:AcknowledgeRestrictiveAnnotations")
excludedPaths = ".*/jmh_generated/.*"
} }
} }
} }

View File

@ -13,8 +13,12 @@ SPDX-License-Identifier: MPL-2.0
<property name="tabWidth" value="4"/> <property name="tabWidth" value="4"/>
<property name="charset" value="UTF-8" /> <property name="charset" value="UTF-8" />
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<module name="SuppressionFilter"> <module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml" /> <property name="file" value="${config_loc}/suppressions.xml" />
</module> </module>
<module name="BeforeExecutionExclusionFileFilter"> <module name="BeforeExecutionExclusionFileFilter">

View File

@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties # Mod properties
isUnstable=false isUnstable=false
modVersion=1.109.7 modVersion=1.110.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle # Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1 mcVersion=1.20.1

View File

@ -26,7 +26,7 @@ slf4j = "2.0.1"
asm = "9.6" asm = "9.6"
autoService = "1.1.1" autoService = "1.1.1"
checkerFramework = "3.42.0" checkerFramework = "3.42.0"
cobalt = "0.9.1" cobalt = "0.9.2"
commonsCli = "1.6.0" commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0" jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2" jsr305 = "3.0.2"
@ -55,7 +55,7 @@ jmh = "1.37"
# Build tools # Build tools
cctJavadoc = "1.8.2" cctJavadoc = "1.8.2"
checkstyle = "10.12.6" checkstyle = "10.14.1"
curseForgeGradle = "1.0.14" curseForgeGradle = "1.0.14"
errorProne-core = "2.23.0" errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0" errorProne-plugin = "3.1.0"
@ -64,16 +64,15 @@ forgeGradle = "6.0.20"
githubRelease = "2.5.2" githubRelease = "2.5.2"
gradleVersions = "0.50.0" gradleVersions = "0.50.0"
ideaExt = "1.1.7" ideaExt = "1.1.7"
illuaminate = "0.1.0-69-gf294ab2" illuaminate = "0.1.0-71-g378d86e"
librarian = "1.+" librarian = "1.+"
lwjgl = "3.3.3" lwjgl = "3.3.3"
minotaur = "2.+" minotaur = "2.+"
mixinGradle = "0.7.38"
nullAway = "0.9.9" nullAway = "0.9.9"
spotless = "6.23.3" spotless = "6.23.3"
taskTree = "2.1.1" taskTree = "2.1.1"
teavm = "0.10.0-SQUID.3" teavm = "0.10.0-SQUID.3"
vanillaExtract = "0.1.1" vanillaExtract = "0.1.2"
versionCatalogUpdate = "0.8.1" versionCatalogUpdate = "0.8.1"
[libraries] [libraries]
@ -171,7 +170,6 @@ githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "g
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" } gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" } librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" } taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" } versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

20
gradlew.bat vendored
View File

@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@ -29,7 +29,7 @@ private Services() {
* @throws IllegalStateException When the service cannot be loaded. * @throws IllegalStateException When the service cannot be loaded.
*/ */
public static <T> T load(Class<T> klass) { public static <T> T load(Class<T> klass) {
var services = ServiceLoader.load(klass).stream().toList(); var services = ServiceLoader.load(klass, klass.getClassLoader()).stream().toList();
return switch (services.size()) { return switch (services.size()) {
case 1 -> services.get(0).get(); case 1 -> services.get(0).get();
case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName()); case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName());

View File

@ -22,6 +22,7 @@
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu; import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
@ -77,8 +78,10 @@ public static void register() {
/** /**
* Register any client-side objects which must be done on the main thread. * Register any client-side objects which must be done on the main thread.
*
* @param itemProperties Callback to register item properties.
*/ */
public static void registerMainThread() { public static void registerMainThread(RegisterItemProperty itemProperties) {
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new); MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new); MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new); MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
@ -90,11 +93,14 @@ public static void registerMainThread() {
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new); MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
registerItemProperty("state", registerItemProperty(itemProperties, "state",
new UnclampedPropertyFunction((stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal()), new UnclampedPropertyFunction((stack, world, player, random) -> {
var computer = ClientPocketComputers.get(stack);
return (computer == null ? ComputerState.OFF : computer.getState()).ordinal();
}),
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
); );
registerItemProperty("coloured", registerItemProperty(itemProperties, "coloured",
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0, (stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
); );
@ -115,9 +121,17 @@ public static void registerTurtleModellers(RegisterTurtleUpgradeModeller registe
} }
@SafeVarargs @SafeVarargs
private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) { private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name); var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
for (var item : items) ItemProperties.register(item.get(), id, getter); for (var item : items) itemProperties.register(item.get(), id, getter);
}
/**
* Register an item property via {@link ItemProperties#register}. Forge and Fabric expose different methods, so we
* supply this via mod-loader-specific code.
*/
public interface RegisterItemProperty {
void register(Item item, ResourceLocation name, ClampedItemPropertyFunction property);
} }
public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) { public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) {
@ -155,17 +169,14 @@ public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register)
} }
private static int getPocketColour(ItemStack stack, int layer) { private static int getPocketColour(ItemStack stack, int layer) {
switch (layer) { return switch (layer) {
case 0: default -> 0xFFFFFF;
default: case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
return 0xFFFFFF; case 2 -> { // Light colour
case 1: // Frame colour var computer = ClientPocketComputers.get(stack);
return IColouredItem.getColourBasic(stack); yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
case 2: { // Light colour
var light = ClientPocketComputers.get(stack).getLightState();
return light == -1 ? Colour.BLACK.getHex() : light;
} }
} };
} }
private static int getTurtleColour(ItemStack stack, int layer) { private static int getTurtleColour(ItemStack stack, int layer) {

View File

@ -17,6 +17,7 @@
import dan200.computercraft.shared.computer.upload.UploadResult; import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.client.ClientNetworkContext; import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity; import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -27,7 +28,6 @@
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
/** /**
@ -67,19 +67,17 @@ public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable
} }
@Override @Override
public void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) { public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal) {
var computer = ClientPocketComputers.get(instanceId, terminal.colour); ClientPocketComputers.setState(instanceId, state, lightState, terminal);
computer.setState(state, lightState);
if (terminal.hasTerminal()) computer.setTerminal(terminal);
} }
@Override @Override
public void handlePocketComputerDeleted(int instanceId) { public void handlePocketComputerDeleted(UUID instanceId) {
ClientPocketComputers.remove(instanceId); ClientPocketComputers.remove(instanceId);
} }
@Override @Override
public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, ByteBuffer buffer) { public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, EncodedAudio buffer) {
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume, buffer); SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume, buffer);
} }

View File

@ -4,21 +4,25 @@
package dan200.computercraft.client.pocket; package dan200.computercraft.client.pocket;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage; import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/** /**
* Maps {@link ServerComputer#getInstanceID()} to locals {@link PocketComputerData}. * Maps {@link ServerComputer#getInstanceUUID()} to locals {@link PocketComputerData}.
* <p> * <p>
* This is populated by {@link PocketComputerDataMessage} and accessed when rendering pocket computers * This is populated by {@link PocketComputerDataMessage} and accessed when rendering pocket computers
*/ */
public final class ClientPocketComputers { public final class ClientPocketComputers {
private static final Int2ObjectMap<PocketComputerData> instances = new Int2ObjectOpenHashMap<>(); private static final Map<UUID, PocketComputerData> instances = new HashMap<>();
private ClientPocketComputers() { private ClientPocketComputers() {
} }
@ -27,25 +31,29 @@ public static void reset() {
instances.clear(); instances.clear();
} }
public static void remove(int id) { public static void remove(UUID id) {
instances.remove(id); instances.remove(id);
} }
/** /**
* Get or create a pocket computer. * Set the state of a pocket computer.
* *
* @param instanceId The instance ID of the pocket computer. * @param instanceId The instance ID of the pocket computer.
* @param advanced Whether this computer has an advanced terminal. * @param state The computer state of the pocket computer.
* @return The pocket computer data. * @param lightColour The current colour of the modem light.
* @param terminalData The current terminal contents.
*/ */
public static PocketComputerData get(int instanceId, boolean advanced) { public static void setState(UUID instanceId, ComputerState state, int lightColour, TerminalState terminalData) {
var computer = instances.get(instanceId); var computer = instances.get(instanceId);
if (computer == null) instances.put(instanceId, computer = new PocketComputerData(advanced)); if (computer == null) {
return computer; instances.put(instanceId, new PocketComputerData(state, lightColour, terminalData));
} else {
computer.setState(state, lightColour, terminalData);
}
} }
public static PocketComputerData get(ItemStack stack) { public static @Nullable PocketComputerData get(ItemStack stack) {
var family = stack.getItem() instanceof PocketComputerItem computer ? computer.getFamily() : ComputerFamily.NORMAL; var id = PocketComputerItem.getInstanceID(stack);
return get(PocketComputerItem.getInstanceID(stack), family != ComputerFamily.NORMAL); return id == null ? null : instances.get(id);
} }
} }

View File

@ -4,13 +4,13 @@
package dan200.computercraft.client.pocket; package dan200.computercraft.client.pocket;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState; import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.pocket.core.PocketServerComputer; import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import javax.annotation.Nullable;
/** /**
* Clientside data about a pocket computer. * Clientside data about a pocket computer.
* <p> * <p>
@ -21,20 +21,22 @@
* @see ClientPocketComputers The registry which holds pocket computers. * @see ClientPocketComputers The registry which holds pocket computers.
* @see PocketServerComputer The server-side pocket computer. * @see PocketServerComputer The server-side pocket computer.
*/ */
public class PocketComputerData { public final class PocketComputerData {
private final NetworkedTerminal terminal; private @Nullable NetworkedTerminal terminal;
private ComputerState state = ComputerState.OFF; private ComputerState state;
private int lightColour = -1; private int lightColour;
public PocketComputerData(boolean colour) { PocketComputerData(ComputerState state, int lightColour, TerminalState terminalData) {
terminal = new NetworkedTerminal(Config.pocketTermWidth, Config.pocketTermHeight, colour); this.state = state;
this.lightColour = lightColour;
if (terminalData.hasTerminal()) terminal = terminalData.create();
} }
public int getLightState() { public int getLightState() {
return state != ComputerState.OFF ? lightColour : -1; return state != ComputerState.OFF ? lightColour : -1;
} }
public Terminal getTerminal() { public @Nullable NetworkedTerminal getTerminal() {
return terminal; return terminal;
} }
@ -42,12 +44,16 @@ public ComputerState getState() {
return state; return state;
} }
public void setState(ComputerState state, int lightColour) { void setState(ComputerState state, int lightColour, TerminalState terminalData) {
this.state = state; this.state = state;
this.lightColour = lightColour; this.lightColour = lightColour;
}
public void setTerminal(TerminalState state) { if (terminalData.hasTerminal()) {
state.apply(terminal); if (terminal == null) {
terminal = terminalData.create();
} else {
terminalData.apply(terminal);
}
}
} }
} }

View File

@ -11,6 +11,7 @@
import dan200.computercraft.client.render.text.FixedWidthFontRenderer; import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -32,10 +33,16 @@ private PocketItemRenderer() {
@Override @Override
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) { protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
var computer = ClientPocketComputers.get(stack); var computer = ClientPocketComputers.get(stack);
var terminal = computer.getTerminal(); var terminal = computer == null ? null : computer.getTerminal();
var termWidth = terminal.getWidth(); int termWidth, termHeight;
var termHeight = terminal.getHeight(); if (terminal == null) {
termWidth = Config.pocketTermWidth;
termHeight = Config.pocketTermHeight;
} else {
termWidth = terminal.getWidth();
termHeight = terminal.getHeight();
}
var width = termWidth * FONT_WIDTH + MARGIN * 2; var width = termWidth * FONT_WIDTH + MARGIN * 2;
var height = termHeight * FONT_HEIGHT + MARGIN * 2; var height = termHeight * FONT_HEIGHT + MARGIN * 2;
@ -60,14 +67,15 @@ protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, I
renderFrame(matrix, bufferSource, family, frameColour, light, width, height); renderFrame(matrix, bufferSource, family, frameColour, light, width, height);
// Render the light // Render the light
var lightColour = ClientPocketComputers.get(stack).getLightState(); var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
if (lightColour == -1) lightColour = Colour.BLACK.getHex();
renderLight(transform, bufferSource, lightColour, width, height); renderLight(transform, bufferSource, lightColour, width, height);
FixedWidthFontRenderer.drawTerminal( var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL));
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL)), if (terminal == null) {
MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
); } else {
FixedWidthFontRenderer.drawTerminal(quadEmitter, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
}
transform.popPose(); transform.popPose();
} }

View File

@ -5,6 +5,7 @@
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel; import com.mojang.blaze3d.audio.Channel;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.sounds.AudioStream; import net.minecraft.client.sounds.AudioStream;
@ -36,7 +37,7 @@ class DfpwmStream implements AudioStream {
/** /**
* The {@link Channel} which this sound is playing on. * The {@link Channel} which this sound is playing on.
* *
* @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer) * @see SpeakerInstance#playAudio(SpeakerPosition, float, EncodedAudio)
*/ */
@Nullable @Nullable
Channel channel; Channel channel;
@ -44,21 +45,23 @@ class DfpwmStream implements AudioStream {
/** /**
* The underlying {@link SoundEngine} executor. * The underlying {@link SoundEngine} executor.
* *
* @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer) * @see SpeakerInstance#playAudio(SpeakerPosition, float, EncodedAudio)
* @see SoundEngine#executor * @see SoundEngine#executor
*/ */
@Nullable @Nullable
Executor executor; Executor executor;
private int charge = 0; // q
private int strength = 0; // s
private int lowPassCharge; private int lowPassCharge;
private boolean previousBit = false;
DfpwmStream() { DfpwmStream() {
} }
void push(ByteBuffer input) { void push(EncodedAudio audio) {
var charge = audio.charge();
var strength = audio.strength();
var previousBit = audio.previousBit();
var input = audio.audio();
var readable = input.remaining(); var readable = input.remaining();
var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder()); var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder());

View File

@ -6,12 +6,12 @@
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.core.util.Nullability; import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer;
/** /**
* An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound. * An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound.
@ -25,7 +25,7 @@ public class SpeakerInstance {
SpeakerInstance() { SpeakerInstance() {
} }
private void pushAudio(ByteBuffer buffer) { private void pushAudio(EncodedAudio buffer) {
var sound = this.sound; var sound = this.sound;
var stream = currentStream; var stream = currentStream;
@ -43,7 +43,7 @@ private void pushAudio(ByteBuffer buffer) {
} }
} }
public void playAudio(SpeakerPosition position, float volume, ByteBuffer buffer) { public void playAudio(SpeakerPosition position, float volume, EncodedAudio buffer) {
pushAudio(buffer); pushAudio(buffer);
var soundManager = Minecraft.getInstance().getSoundManager(); var soundManager = Minecraft.getInstance().getSoundManager();

View File

@ -6,6 +6,7 @@
import dan200.computercraft.api.network.wired.WiredNetworkChange; import dan200.computercraft.api.network.wired.WiredNetworkChange;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.PeripheralHelpers;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -52,7 +53,7 @@ static WiredNetworkChangeImpl changeOf(Map<String, IPeripheral> oldPeripherals,
var oldValue = entry.getValue(); var oldValue = entry.getValue();
if (newPeripherals.containsKey(oldKey)) { if (newPeripherals.containsKey(oldKey)) {
var rightValue = added.get(oldKey); var rightValue = added.get(oldKey);
if (oldValue.equals(rightValue)) { if (PeripheralHelpers.equals(oldValue, rightValue)) {
added.remove(oldKey); added.remove(oldKey);
} else { } else {
removed.put(oldKey, oldValue); removed.put(oldKey, oldValue);

View File

@ -134,7 +134,7 @@ private static int dump(CommandSourceStack source) {
} else if (b.getLevel() == world) { } else if (b.getLevel() == world) {
return 1; return 1;
} else { } else {
return Integer.compare(a.getInstanceID(), b.getInstanceID()); return a.getInstanceUUID().compareTo(b.getInstanceUUID());
} }
}); });
@ -159,7 +159,8 @@ private static int dump(CommandSourceStack source) {
*/ */
private static int dumpComputer(CommandSourceStack source, ServerComputer computer) { private static int dumpComputer(CommandSourceStack source, ServerComputer computer) {
var table = new TableBuilder("Dump"); var table = new TableBuilder("Dump");
table.row(header("Instance"), text(Integer.toString(computer.getInstanceID()))); table.row(header("Instance ID"), text(Integer.toString(computer.getInstanceID())));
table.row(header("Instance UUID"), text(computer.getInstanceUUID().toString()));
table.row(header("Id"), text(Integer.toString(computer.getID()))); table.row(header("Id"), text(Integer.toString(computer.getID())));
table.row(header("Label"), text(computer.getLabel())); table.row(header("Label"), text(computer.getLabel()));
table.row(header("On"), bool(computer.isOn())); table.row(header("On"), bool(computer.isOn()));
@ -338,11 +339,7 @@ private static Component linkComputer(CommandSourceStack source, @Nullable Serve
if (computer == null) { if (computer == null) {
out.append("#" + computerId + " ").append(coloured("(unloaded)", ChatFormatting.GRAY)); out.append("#" + computerId + " ").append(coloured("(unloaded)", ChatFormatting.GRAY));
} else { } else {
out.append(link( out.append(makeComputerDumpCommand(computer));
text("#" + computerId + " ").append(coloured("(instance " + computer.getInstanceID() + ")", ChatFormatting.GRAY)),
makeComputerCommand("dump", computer),
Component.translatable("commands.computercraft.dump.action")
));
} }
// And, if we're a player, some useful links // And, if we're a player, some useful links
@ -372,10 +369,6 @@ private static Component linkComputer(CommandSourceStack source, @Nullable Serve
return out; return out;
} }
private static String makeComputerCommand(String command, ServerComputer computer) {
return String.format("/computercraft %s @c[instance=%d]", command, computer.getInstanceID());
}
private static Component linkPosition(CommandSourceStack context, ServerComputer computer) { private static Component linkPosition(CommandSourceStack context, ServerComputer computer) {
if (ModRegistry.Permissions.PERMISSION_TP.test(context)) { if (ModRegistry.Permissions.PERMISSION_TP.test(context)) {
return link( return link(
@ -392,7 +385,7 @@ private static Component linkPosition(CommandSourceStack context, ServerComputer
var file = new File(ServerContext.get(source.getServer()).storageDir().toFile(), "computer/" + id); var file = new File(ServerContext.get(source.getServer()).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return null; if (!file.isDirectory()) return null;
return link( return clientLink(
text("\u270E"), text("\u270E"),
"/" + CLIENT_OPEN_FOLDER + " " + id, "/" + CLIENT_OPEN_FOLDER + " " + id,
Component.translatable("commands.computercraft.dump.open_path") Component.translatable("commands.computercraft.dump.open_path")

View File

@ -16,7 +16,10 @@
import net.minecraft.advancements.critereon.MinMaxBounds; import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.UuidArgument;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentContents;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -30,17 +33,21 @@
import static dan200.computercraft.shared.command.CommandUtils.suggestOnServer; import static dan200.computercraft.shared.command.CommandUtils.suggestOnServer;
import static dan200.computercraft.shared.command.Exceptions.*; import static dan200.computercraft.shared.command.Exceptions.*;
import static dan200.computercraft.shared.command.arguments.ArgumentParserUtils.consume; import static dan200.computercraft.shared.command.arguments.ArgumentParserUtils.consume;
import static dan200.computercraft.shared.command.text.ChatHelpers.makeComputerDumpCommand;
public record ComputerSelector( public record ComputerSelector(
String selector, String selector,
OptionalInt instanceId, OptionalInt instanceId,
@Nullable UUID instanceUuid,
OptionalInt computerId, OptionalInt computerId,
@Nullable String label, @Nullable String label,
@Nullable ComputerFamily family, @Nullable ComputerFamily family,
@Nullable AABB bounds, @Nullable AABB bounds,
@Nullable MinMaxBounds.Doubles range @Nullable MinMaxBounds.Doubles range
) { ) {
private static final ComputerSelector all = new ComputerSelector("@c[]", OptionalInt.empty(), OptionalInt.empty(), null, null, null, null); private static final ComputerSelector all = new ComputerSelector("@c[]", OptionalInt.empty(), null, OptionalInt.empty(), null, null, null, null);
private static UuidArgument uuidArgument = UuidArgument.uuid();
/** /**
* A {@link ComputerSelector} which matches all computers. * A {@link ComputerSelector} which matches all computers.
@ -59,8 +66,13 @@ public static ComputerSelector all() {
*/ */
public Stream<ServerComputer> find(CommandSourceStack source) { public Stream<ServerComputer> find(CommandSourceStack source) {
var context = ServerContext.get(source.getServer()); var context = ServerContext.get(source.getServer());
if (instanceId.isPresent()) { if (instanceId().isPresent()) {
var computer = context.registry().get(instanceId.getAsInt()); var computer = context.registry().get(instanceId().getAsInt());
return computer != null && matches(source, computer) ? Stream.of(computer) : Stream.of();
}
if (instanceUuid() != null) {
var computer = context.registry().get(instanceUuid());
return computer != null && matches(source, computer) ? Stream.of(computer) : Stream.of(); return computer != null && matches(source, computer) ? Stream.of(computer) : Stream.of();
} }
@ -79,7 +91,7 @@ public ServerComputer findOne(CommandSourceStack source) throws CommandSyntaxExc
if (computers.isEmpty()) throw COMPUTER_ARG_NONE.create(selector); if (computers.isEmpty()) throw COMPUTER_ARG_NONE.create(selector);
if (computers.size() == 1) return computers.iterator().next(); if (computers.size() == 1) return computers.iterator().next();
var builder = new StringBuilder(); var builder = MutableComponent.create(ComponentContents.EMPTY);
var first = true; var first = true;
for (var computer : computers) { for (var computer : computers) {
if (first) { if (first) {
@ -88,12 +100,12 @@ public ServerComputer findOne(CommandSourceStack source) throws CommandSyntaxExc
builder.append(", "); builder.append(", ");
} }
builder.append(computer.getInstanceID()); builder.append(makeComputerDumpCommand(computer));
} }
// We have an incorrect number of computers: throw an error // We have an incorrect number of computers: throw an error
throw COMPUTER_ARG_MANY.create(selector, builder.toString()); throw COMPUTER_ARG_MANY.create(selector, builder);
} }
/** /**
@ -105,6 +117,7 @@ public ServerComputer findOne(CommandSourceStack source) throws CommandSyntaxExc
*/ */
public boolean matches(CommandSourceStack source, ServerComputer computer) { public boolean matches(CommandSourceStack source, ServerComputer computer) {
return (instanceId().isEmpty() || computer.getInstanceID() == instanceId().getAsInt()) return (instanceId().isEmpty() || computer.getInstanceID() == instanceId().getAsInt())
&& (instanceUuid() == null || computer.getInstanceUUID().equals(instanceUuid()))
&& (computerId().isEmpty() || computer.getID() == computerId().getAsInt()) && (computerId().isEmpty() || computer.getID() == computerId().getAsInt())
&& (label == null || Objects.equals(computer.getLabel(), label)) && (label == null || Objects.equals(computer.getLabel(), label))
&& (family == null || computer.getFamily() == family) && (family == null || computer.getFamily() == family)
@ -127,6 +140,7 @@ public static ComputerSelector parse(StringReader reader) throws CommandSyntaxEx
if (consume(reader, "@c[")) { if (consume(reader, "@c[")) {
parseSelector(builder, reader); parseSelector(builder, reader);
} else { } else {
// TODO(1.20.5): Only parse computer ids here.
var kind = reader.peek(); var kind = reader.peek();
if (kind == '@') { if (kind == '@') {
reader.skip(); reader.skip();
@ -143,7 +157,7 @@ public static ComputerSelector parse(StringReader reader) throws CommandSyntaxEx
} }
var selector = reader.getString().substring(start, reader.getCursor()); var selector = reader.getString().substring(start, reader.getCursor());
return new ComputerSelector(selector, builder.instanceId, builder.computerId, builder.label, builder.family, builder.bounds, builder.range); return new ComputerSelector(selector, builder.instanceId, builder.instanceUuid, builder.computerId, builder.label, builder.family, builder.bounds, builder.range);
} }
private static void parseSelector(Builder builder, StringReader reader) throws CommandSyntaxException { private static void parseSelector(Builder builder, StringReader reader) throws CommandSyntaxException {
@ -260,6 +274,7 @@ private static SuggestionsBuilder suggestions(StringReader reader) {
private static final class Builder { private static final class Builder {
private OptionalInt instanceId = OptionalInt.empty(); private OptionalInt instanceId = OptionalInt.empty();
private @Nullable UUID instanceUuid = null;
private OptionalInt computerId = OptionalInt.empty(); private OptionalInt computerId = OptionalInt.empty();
private @Nullable String label; private @Nullable String label;
private @Nullable ComputerFamily family; private @Nullable ComputerFamily family;
@ -282,8 +297,8 @@ public static Map<String, Option> options() {
var optionList = new Option[]{ var optionList = new Option[]{
new Option( new Option(
"instance", "instance",
(reader, builder) -> builder.instanceId = OptionalInt.of(reader.readInt()), (reader, builder) -> builder.instanceUuid = uuidArgument.parse(reader),
suggestComputers(c -> Integer.toString(c.getInstanceID())) suggestComputers(c -> c.getInstanceUUID().toString())
), ),
new Option( new Option(
"id", "id",

View File

@ -4,6 +4,8 @@
package dan200.computercraft.shared.command.text; package dan200.computercraft.shared.command.text;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.ClickEvent;
@ -53,6 +55,13 @@ public static Component link(MutableComponent component, String command, Compone
return link(component, new ClickEvent(ClickEvent.Action.RUN_COMMAND, command), toolTip); return link(component, new ClickEvent(ClickEvent.Action.RUN_COMMAND, command), toolTip);
} }
public static Component clientLink(MutableComponent component, String command, Component toolTip) {
var event = PlatformHelper.get().canClickRunClientCommand()
? new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)
: new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command);
return link(component, event, toolTip);
}
public static Component link(Component component, ClickEvent click, Component toolTip) { public static Component link(Component component, ClickEvent click, Component toolTip) {
var style = component.getStyle(); var style = component.getStyle();
@ -73,4 +82,16 @@ public static MutableComponent copy(String text) {
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy"))) .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy")))
); );
} }
public static String makeComputerCommand(String command, ServerComputer computer) {
return String.format("/computercraft %s @c[instance=%s]", command, computer.getInstanceUUID());
}
public static Component makeComputerDumpCommand(ServerComputer computer) {
return link(
text("#" + computer.getID()),
makeComputerCommand("dump", computer),
Component.translatable("commands.computercraft.dump.action")
);
}
} }

View File

@ -23,6 +23,7 @@
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.EntityBlock;
@ -174,6 +175,15 @@ public final void onNeighborChange(BlockState state, LevelReader world, BlockPos
if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbour); if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbour);
} }
@Override
@Deprecated
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
var be = level.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighbourShapeChanged(direction);
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Nullable @Nullable
@Override @Override
@Deprecated @Deprecated

View File

@ -36,13 +36,14 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
public abstract class AbstractComputerBlockEntity extends BlockEntity implements IComputerBlockEntity, Nameable, MenuProvider { public abstract class AbstractComputerBlockEntity extends BlockEntity implements IComputerBlockEntity, Nameable, MenuProvider {
private static final String NBT_ID = "ComputerId"; private static final String NBT_ID = "ComputerId";
private static final String NBT_LABEL = "Label"; private static final String NBT_LABEL = "Label";
private static final String NBT_ON = "On"; private static final String NBT_ON = "On";
private int instanceID = -1; private @Nullable UUID instanceID = null;
private int computerID = -1; private int computerID = -1;
protected @Nullable String label = null; protected @Nullable String label = null;
private boolean on = false; private boolean on = false;
@ -66,7 +67,7 @@ protected void unload() {
var computer = getServerComputer(); var computer = getServerComputer();
if (computer != null) computer.close(); if (computer != null) computer.close();
instanceID = -1; instanceID = null;
} }
@Override @Override
@ -122,7 +123,7 @@ protected void serverTick() {
// Update any peripherals that have changed. // Update any peripherals that have changed.
if (invalidSides != 0) { if (invalidSides != 0) {
for (var direction : DirectionUtil.FACINGS) { for (var direction : DirectionUtil.FACINGS) {
if ((invalidSides & (1 << direction.ordinal())) != 0) refreshPeripheral(computer, direction); if (DirectionUtil.isSet(invalidSides, direction)) refreshPeripheral(computer, direction);
} }
} }
@ -294,7 +295,18 @@ public void neighborChanged(BlockPos neighbour) {
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods // If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
// handle this incorrectly. // handle this incorrectly.
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, getBlockPos().relative(dir)); for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, getBlockPos().relative(dir));
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty. invalidSides = DirectionUtil.ALL_SIDES; // Mark all peripherals as dirty.
}
/**
* Called when a neighbour block's shape changes.
* <p>
* Unlike {@link #neighborChanged(BlockPos)}, we don't update redstone, only peripherals.
*
* @param direction The side that changed.
*/
public void neighbourShapeChanged(Direction direction) {
invalidSides |= 1 << direction.ordinal();
} }
/** /**
@ -401,7 +413,7 @@ protected void loadClient(CompoundTag nbt) {
} }
protected void transferStateFrom(AbstractComputerBlockEntity copy) { protected void transferStateFrom(AbstractComputerBlockEntity copy) {
if (copy.computerID != computerID || copy.instanceID != instanceID) { if (copy.computerID != computerID || !Objects.equals(copy.instanceID, instanceID)) {
unload(); unload();
instanceID = copy.instanceID; instanceID = copy.instanceID;
computerID = copy.computerID; computerID = copy.computerID;
@ -411,7 +423,7 @@ protected void transferStateFrom(AbstractComputerBlockEntity copy) {
lockCode = copy.lockCode; lockCode = copy.lockCode;
BlockEntityHelpers.updateBlock(this); BlockEntityHelpers.updateBlock(this);
} }
copy.instanceID = -1; copy.instanceID = null;
} }
@Override @Override

View File

@ -26,11 +26,13 @@
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function; import java.util.function.Function;
public class ServerComputer implements InputHandler, ComputerEnvironment { public class ServerComputer implements InputHandler, ComputerEnvironment {
private final int instanceID; private final int instanceID;
private final UUID instanceUUID = UUID.randomUUID();
private ServerLevel level; private ServerLevel level;
private BlockPos position; private BlockPos position;
@ -119,9 +121,9 @@ public int pollAndResetChanges() {
return computer.pollAndResetChanges(); return computer.pollAndResetChanges();
} }
public int register() { public UUID register() {
ServerContext.get(level.getServer()).registry().add(instanceID, this); ServerContext.get(level.getServer()).registry().add(this);
return instanceID; return instanceUUID;
} }
void unload() { void unload() {
@ -130,7 +132,7 @@ void unload() {
public void close() { public void close() {
unload(); unload();
ServerContext.get(level.getServer()).registry().remove(instanceID); ServerContext.get(level.getServer()).registry().remove(this);
} }
private void sendToAllInteracting(Function<AbstractContainerMenu, NetworkMessage<ClientNetworkContext>> createPacket) { private void sendToAllInteracting(Function<AbstractContainerMenu, NetworkMessage<ClientNetworkContext>> createPacket) {
@ -150,6 +152,10 @@ public int getInstanceID() {
return instanceID; return instanceID;
} }
public UUID getInstanceUUID() {
return instanceUUID;
}
public int getID() { public int getID() {
return computer.getID(); return computer.getID();
} }

View File

@ -8,14 +8,14 @@
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.*;
import java.util.Random;
public class ServerComputerRegistry { public class ServerComputerRegistry {
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private final int sessionId = RANDOM.nextInt(); private final int sessionId = RANDOM.nextInt();
private final Int2ObjectMap<ServerComputer> computers = new Int2ObjectOpenHashMap<>(); private final Int2ObjectMap<ServerComputer> computersByInstanceId = new Int2ObjectOpenHashMap<>();
private final Map<UUID, ServerComputer> computersByInstanceUuid = new HashMap<>();
private int nextInstanceId; private int nextInstanceId;
public int getSessionID() { public int getSessionID() {
@ -28,50 +28,64 @@ int getUnusedInstanceID() {
@Nullable @Nullable
public ServerComputer get(int instanceID) { public ServerComputer get(int instanceID) {
return instanceID >= 0 ? computers.get(instanceID) : null; return instanceID >= 0 ? computersByInstanceId.get(instanceID) : null;
} }
@Nullable @Nullable
public ServerComputer get(int sessionId, int instanceId) { public ServerComputer get(@Nullable UUID instanceID) {
return instanceID != null ? computersByInstanceUuid.get(instanceID) : null;
}
@Nullable
public ServerComputer get(int sessionId, @Nullable UUID instanceId) {
return sessionId == this.sessionId ? get(instanceId) : null; return sessionId == this.sessionId ? get(instanceId) : null;
} }
void update() { void update() {
var it = getComputers().iterator(); var it = computersByInstanceId.values().iterator();
while (it.hasNext()) { while (it.hasNext()) {
var computer = it.next(); var computer = it.next();
if (computer.hasTimedOut()) { if (computer.hasTimedOut()) {
computer.unload(); computer.unload();
computer.onRemoved(); computer.onRemoved();
it.remove(); it.remove();
computersByInstanceUuid.remove(computer.getInstanceUUID());
} else { } else {
computer.tickServer(); computer.tickServer();
} }
} }
} }
void add(int instanceID, ServerComputer computer) { void add(ServerComputer computer) {
remove(instanceID); var instanceID = computer.getInstanceID();
computers.put(instanceID, computer); var instanceUUID = computer.getInstanceUUID();
nextInstanceId = Math.max(nextInstanceId, instanceID + 1);
}
void remove(int instanceID) { if (computersByInstanceId.containsKey(instanceID)) {
var computer = get(instanceID); throw new IllegalStateException("Duplicate computer " + instanceID);
if (computer != null) {
computer.unload();
computer.onRemoved();
} }
computers.remove(instanceID); if (computersByInstanceUuid.containsKey(instanceUUID)) {
throw new IllegalStateException("Duplicate computer " + instanceUUID);
}
computersByInstanceId.put(instanceID, computer);
computersByInstanceUuid.put(instanceUUID, computer);
}
void remove(ServerComputer computer) {
computer.unload();
computer.onRemoved();
computersByInstanceId.remove(computer.getInstanceID());
computersByInstanceUuid.remove(computer.getInstanceUUID());
} }
void close() { void close() {
for (var computer : getComputers()) computer.unload(); for (var computer : getComputers()) computer.unload();
computers.clear(); computersByInstanceId.clear();
computersByInstanceUuid.clear();
} }
public Collection<ServerComputer> getComputers() { public Collection<ServerComputer> getComputers() {
return computers.values(); return computersByInstanceId.values();
} }
} }

View File

@ -25,7 +25,7 @@ public ViewComputerMenu(int id, Inventory player, ComputerContainerData data) {
private static boolean canInteractWith(ServerComputer computer, Player player) { private static boolean canInteractWith(ServerComputer computer, Player player) {
// If this computer no longer exists then discard it. // If this computer no longer exists then discard it.
if (ServerContext.get(computer.getLevel().getServer()).registry().get(computer.getInstanceID()) != computer) { if (ServerContext.get(computer.getLevel().getServer()).registry().get(computer.getInstanceUUID()) != computer) {
return false; return false;
} }

View File

@ -18,10 +18,9 @@
* states, etc... * states, etc...
*/ */
public class TerminalState { public class TerminalState {
public final boolean colour; private final boolean colour;
private final int width;
public final int width; private final int height;
public final int height;
@Nullable @Nullable
private final ByteBuf buffer; private final ByteBuf buffer;

View File

@ -8,6 +8,7 @@
import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.terminal.TerminalState; import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.computer.upload.UploadResult; import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
@ -15,7 +16,6 @@
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
/** /**
@ -30,11 +30,11 @@ public interface ClientNetworkContext {
void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name); void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name);
void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal); void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal);
void handlePocketComputerDeleted(int instanceId); void handlePocketComputerDeleted(UUID instanceId);
void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, ByteBuffer audio); void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, EncodedAudio audio);
void handleSpeakerMove(UUID source, SpeakerPosition.Message position); void handleSpeakerMove(UUID source, SpeakerPosition.Message position);

View File

@ -13,24 +13,26 @@
import dan200.computercraft.shared.pocket.core.PocketServerComputer; import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import java.util.UUID;
/** /**
* Provides additional data about a client computer, such as its ID and current state. * Provides additional data about a client computer, such as its ID and current state.
*/ */
public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkContext> { public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkContext> {
private final int instanceId; private final UUID clientId;
private final ComputerState state; private final ComputerState state;
private final int lightState; private final int lightState;
private final TerminalState terminal; private final TerminalState terminal;
public PocketComputerDataMessage(PocketServerComputer computer, boolean sendTerminal) { public PocketComputerDataMessage(PocketServerComputer computer, boolean sendTerminal) {
instanceId = computer.getInstanceID(); clientId = computer.getInstanceUUID();
state = computer.getState(); state = computer.getState();
lightState = computer.getLight(); lightState = computer.getLight();
terminal = sendTerminal ? computer.getTerminalState() : new TerminalState((NetworkedTerminal) null); terminal = sendTerminal ? computer.getTerminalState() : new TerminalState((NetworkedTerminal) null);
} }
public PocketComputerDataMessage(FriendlyByteBuf buf) { public PocketComputerDataMessage(FriendlyByteBuf buf) {
instanceId = buf.readVarInt(); clientId = buf.readUUID();
state = buf.readEnum(ComputerState.class); state = buf.readEnum(ComputerState.class);
lightState = buf.readVarInt(); lightState = buf.readVarInt();
terminal = new TerminalState(buf); terminal = new TerminalState(buf);
@ -38,7 +40,7 @@ public PocketComputerDataMessage(FriendlyByteBuf buf) {
@Override @Override
public void write(FriendlyByteBuf buf) { public void write(FriendlyByteBuf buf) {
buf.writeVarInt(instanceId); buf.writeUUID(clientId);
buf.writeEnum(state); buf.writeEnum(state);
buf.writeVarInt(lightState); buf.writeVarInt(lightState);
terminal.write(buf); terminal.write(buf);
@ -46,7 +48,7 @@ public void write(FriendlyByteBuf buf) {
@Override @Override
public void handle(ClientNetworkContext context) { public void handle(ClientNetworkContext context) {
context.handlePocketComputerData(instanceId, state, lightState, terminal); context.handlePocketComputerData(clientId, state, lightState, terminal);
} }
@Override @Override

View File

@ -9,21 +9,23 @@
import dan200.computercraft.shared.network.NetworkMessages; import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import java.util.UUID;
public class PocketComputerDeletedClientMessage implements NetworkMessage<ClientNetworkContext> { public class PocketComputerDeletedClientMessage implements NetworkMessage<ClientNetworkContext> {
private final int instanceId; private final UUID instanceId;
public PocketComputerDeletedClientMessage(int instanceId) { public PocketComputerDeletedClientMessage(UUID instanceId) {
this.instanceId = instanceId; this.instanceId = instanceId;
} }
public PocketComputerDeletedClientMessage(FriendlyByteBuf buffer) { public PocketComputerDeletedClientMessage(FriendlyByteBuf buffer) {
instanceId = buffer.readVarInt(); instanceId = buffer.readUUID();
} }
@Override @Override
public void write(FriendlyByteBuf buf) { public void write(FriendlyByteBuf buf) {
buf.writeVarInt(instanceId); buf.writeUUID(instanceId);
} }
@Override @Override

View File

@ -7,11 +7,11 @@
import dan200.computercraft.shared.network.MessageType; import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage; import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages; import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity; import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
/** /**
@ -24,10 +24,10 @@
public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkContext> { public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source; private final UUID source;
private final SpeakerPosition.Message pos; private final SpeakerPosition.Message pos;
private final ByteBuffer content; private final EncodedAudio content;
private final float volume; private final float volume;
public SpeakerAudioClientMessage(UUID source, SpeakerPosition pos, float volume, ByteBuffer content) { public SpeakerAudioClientMessage(UUID source, SpeakerPosition pos, float volume, EncodedAudio content) {
this.source = source; this.source = source;
this.pos = pos.asMessage(); this.pos = pos.asMessage();
this.content = content; this.content = content;
@ -38,10 +38,7 @@ public SpeakerAudioClientMessage(FriendlyByteBuf buf) {
source = buf.readUUID(); source = buf.readUUID();
pos = SpeakerPosition.Message.read(buf); pos = SpeakerPosition.Message.read(buf);
volume = buf.readFloat(); volume = buf.readFloat();
content = EncodedAudio.read(buf);
var bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
content = ByteBuffer.wrap(bytes);
} }
@Override @Override
@ -49,7 +46,7 @@ public void write(FriendlyByteBuf buf) {
buf.writeUUID(source); buf.writeUUID(source);
pos.write(buf); pos.write(buf);
buf.writeFloat(volume); buf.writeFloat(volume);
buf.writeBytes(content.duplicate()); content.write(buf);
} }
@Override @Override

View File

@ -11,7 +11,7 @@
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IDynamicPeripheral; import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.platform.RegistryWrappers; import dan200.computercraft.core.computer.GuardedLuaContext;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
@ -27,13 +27,16 @@ public final class GenericPeripheral implements IDynamicPeripheral {
private final Set<String> additionalTypes; private final Set<String> additionalTypes;
private final List<SaturatedMethod> methods; private final List<SaturatedMethod> methods;
GenericPeripheral(BlockEntity tile, Direction side, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) { private @Nullable GuardedLuaContext contextWrapper;
private final GuardedLuaContext.Guard guard;
GenericPeripheral(BlockEntity tile, Direction side, String type, Set<String> additionalTypes, List<SaturatedMethod> methods) {
this.side = side; this.side = side;
var type = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(tile.getType());
this.tile = tile; this.tile = tile;
this.type = name != null ? name : type.toString(); this.type = type;
this.additionalTypes = additionalTypes; this.additionalTypes = additionalTypes;
this.methods = methods; this.methods = methods;
this.guard = () -> !tile.isRemoved() && tile.getLevel() != null && tile.getLevel().isLoaded(tile.getBlockPos());
} }
public Direction side() { public Direction side() {
@ -49,7 +52,12 @@ public String[] getMethodNames() {
@Override @Override
public MethodResult callMethod(IComputerAccess computer, ILuaContext context, int method, IArguments arguments) throws LuaException { public MethodResult callMethod(IComputerAccess computer, ILuaContext context, int method, IArguments arguments) throws LuaException {
return methods.get(method).apply(context, computer, arguments); var contextWrapper = this.contextWrapper;
if (contextWrapper == null || !contextWrapper.wraps(context)) {
contextWrapper = this.contextWrapper = new GuardedLuaContext(context, guard);
}
return methods.get(method).apply(contextWrapper, computer, arguments);
} }
@Override @Override

View File

@ -10,6 +10,9 @@
import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.core.methods.PeripheralMethod;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -25,6 +28,8 @@
* See the platform-specific peripheral providers for the usage of this. * See the platform-specific peripheral providers for the usage of this.
*/ */
final class GenericPeripheralBuilder { final class GenericPeripheralBuilder {
private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralBuilder.class);
private @Nullable String name; private @Nullable String name;
private final Set<String> additionalTypes = new HashSet<>(0); private final Set<String> additionalTypes = new HashSet<>(0);
private final ArrayList<SaturatedMethod> methods = new ArrayList<>(); private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
@ -33,8 +38,24 @@ final class GenericPeripheralBuilder {
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) { IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
if (methods.isEmpty()) return null; if (methods.isEmpty()) return null;
String type;
if (name == null) {
var typeId = BlockEntityType.getKey(blockEntity.getType());
if (typeId == null) {
LOG.error(
"Block entity {} for {} was not registered. Skipping creating a generic peripheral for it.",
blockEntity, blockEntity.getBlockState().getBlock()
);
return null;
}
type = typeId.toString();
} else {
type = name;
}
methods.trimToSize(); methods.trimToSize();
return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods); return new GenericPeripheral(blockEntity, side, type, additionalTypes, methods);
} }
void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod<PeripheralMethod> info) { void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod<PeripheralMethod> info) {

View File

@ -129,7 +129,6 @@ public boolean onCustomDestroyBlock(BlockState state, Level world, BlockPos pos,
world.setBlockAndUpdate(pos, correctConnections(world, pos, newState)); world.setBlockAndUpdate(pos, correctConnections(world, pos, newState));
cable.modemChanged();
cable.connectionsChanged(); cable.connectionsChanged();
if (!world.isClientSide && !player.getAbilities().instabuild) { if (!world.isClientSide && !player.getAbilities().instabuild) {
Block.popResource(world, pos, item); Block.popResource(world, pos, item);
@ -162,10 +161,7 @@ public ItemStack getCloneItemStack(BlockState state, @Nullable HitResult hit, Bl
@Override @Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
var tile = world.getBlockEntity(pos); var tile = world.getBlockEntity(pos);
if (tile instanceof CableBlockEntity cable) { if (tile instanceof CableBlockEntity cable && cable.hasCable()) cable.connectionsChanged();
if (cable.hasCable()) cable.connectionsChanged();
}
super.setPlacedBy(world, pos, state, placer, stack); super.setPlacedBy(world, pos, state, placer, stack);
} }
@ -177,14 +173,37 @@ public FluidState getFluidState(BlockState state) {
@Override @Override
@Deprecated @Deprecated
public BlockState updateShape(BlockState state, Direction side, BlockState otherState, LevelAccessor world, BlockPos pos, BlockPos otherPos) { public BlockState updateShape(BlockState state, Direction side, BlockState otherState, LevelAccessor level, BlockPos pos, BlockPos otherPos) {
WaterloggableHelpers.updateShape(state, world, pos); WaterloggableHelpers.updateShape(state, level, pos);
// Should never happen, but handle the case where we've no modem or cable. // Should never happen, but handle the case where we've no modem or cable.
if (!state.getValue(CABLE) && state.getValue(MODEM) == CableModemVariant.None) { if (!state.getValue(CABLE) && state.getValue(MODEM) == CableModemVariant.None) {
return getFluidState(state).createLegacyBlock(); return getFluidState(state).createLegacyBlock();
} }
return world instanceof Level level ? state.setValue(CONNECTIONS.get(side), doesConnectVisually(state, level, pos, side)) : state; // Pop our modem if needed.
var dir = state.getValue(MODEM).getFacing();
if (dir != null && dir.equals(side) && !canSupportCenter(level, otherPos, side.getOpposite())) {
// If we've no cable, follow normal Minecraft logic and just remove the block.
if (!state.getValue(CABLE)) return getFluidState(state).createLegacyBlock();
// Otherwise remove the cable and drop the modem manually.
state = state.setValue(CableBlock.MODEM, CableModemVariant.None);
if (level instanceof Level actualLevel) {
Block.popResource(actualLevel, pos, new ItemStack(ModRegistry.Items.WIRED_MODEM.get()));
}
if (level.getBlockEntity(pos) instanceof CableBlockEntity cable) cable.scheduleConnectionsChanged();
}
var modem = state.getValue(MODEM);
if (modem.getFacing() == side && modem.isPeripheralOn() && level.getBlockEntity(pos) instanceof CableBlockEntity cable) {
cable.queueRefreshPeripheral();
}
return level instanceof Level actualLevel
? state.setValue(CONNECTIONS.get(side), doesConnectVisually(state, actualLevel, pos, side))
: state;
} }
@Override @Override
@ -230,6 +249,7 @@ public static BlockState correctConnections(Level world, BlockPos pos, BlockStat
@Override @Override
@Deprecated @Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS;
return world.getBlockEntity(pos) instanceof CableBlockEntity modem ? modem.use(player) : InteractionResult.PASS; return world.getBlockEntity(pos) instanceof CableBlockEntity modem ? modem.use(player) : InteractionResult.PASS;
} }

View File

@ -7,7 +7,6 @@
import dan200.computercraft.api.network.wired.WiredElement; import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode; import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.text.ChatHelpers; import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess; import dan200.computercraft.shared.platform.ComponentAccess;
@ -20,21 +19,16 @@
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
public class CableBlockEntity extends BlockEntity { public class CableBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
private final class CableElement extends WiredModemElement { private final class CableElement extends WiredModemElement {
@Override @Override
public Level getLevel() { public Level getLevel() {
@ -57,34 +51,22 @@ protected void detachPeripheral(String name) {
} }
} }
private boolean invalidPeripheral; private boolean refreshPeripheral;
private boolean peripheralAccessAllowed;
private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral(PlatformHelper.get().createPeripheralAccess(this, x -> queueRefreshPeripheral())); private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral(PlatformHelper.get().createPeripheralAccess(this, x -> queueRefreshPeripheral()));
private @Nullable Runnable modemChanged; private @Nullable Runnable modemChanged;
private boolean connectionsFormed = false; private boolean refreshConnections = false;
private boolean connectionsChanged = false;
private final WiredModemElement cable = new CableElement(); private final WiredModemElement cable = new CableElement();
private final WiredNode node = cable.getNode(); private final WiredNode node = cable.getNode();
private final TickScheduler.Token tickToken = new TickScheduler.Token(this); private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
private final WiredModemPeripheral modem = new WiredModemPeripheral( private final WiredModemPeripheral modem = new WiredModemPeripheral(
new ModemState(() -> TickScheduler.schedule(tickToken)), new ModemState(() -> TickScheduler.schedule(tickToken)), cable, peripheral, this
cable
) { ) {
@Override
protected WiredModemLocalPeripheral getLocalPeripheral() {
return peripheral;
}
@Override @Override
public Vec3 getPosition() { public Vec3 getPosition() {
return Vec3.atCenterOf(getBlockPos().relative(getDirection())); var dir = getModemDirection();
} return Vec3.atCenterOf(dir == null ? getBlockPos() : getBlockPos().relative(dir));
@Override
public Object getTarget() {
return CableBlockEntity.this;
} }
}; };
@ -94,93 +76,61 @@ public CableBlockEntity(BlockEntityType<? extends CableBlockEntity> type, BlockP
super(type, pos, state); super(type, pos, state);
} }
private void onRemove() {
if (level == null || !level.isClientSide) {
node.remove();
connectionsFormed = false;
}
}
@Override @Override
public void setRemoved() { public void setRemoved() {
super.setRemoved(); super.setRemoved();
modem.removed(); modem.removed();
onRemove(); if (level == null || !level.isClientSide) node.remove();
} }
@Override @Override
public void clearRemoved() { public void clearRemoved() {
super.clearRemoved(); super.clearRemoved();
refreshConnections = refreshPeripheral = true;
TickScheduler.schedule(tickToken); TickScheduler.schedule(tickToken);
} }
@Override @Override
@Deprecated @Deprecated
public void setBlockState(BlockState state) { public void setBlockState(BlockState state) {
var direction = getMaybeDirection(); var direction = getModemDirection();
var hasCable = hasCable();
super.setBlockState(state); super.setBlockState(state);
// We invalidate both the modem and element if the modem's direction is different. // We invalidate both the modem and element if the modem direction or cable are different.
if (getMaybeDirection() != direction && modemChanged != null) modemChanged.run(); if (modemChanged != null && (hasCable() != hasCable || getModemDirection() != direction)) modemChanged.run();
} }
@Nullable @Nullable
private Direction getMaybeDirection() { private Direction getModemDirection() {
return getBlockState().getValue(CableBlock.MODEM).getFacing(); return getBlockState().getValue(CableBlock.MODEM).getFacing();
} }
private Direction getDirection() {
var direction = getMaybeDirection();
return direction == null ? Direction.NORTH : direction;
}
void neighborChanged(BlockPos neighbour) { void neighborChanged(BlockPos neighbour) {
var dir = getDirection(); var dir = getModemDirection();
if (neighbour.equals(getBlockPos().relative(dir)) && hasModem() && !getBlockState().canSurvive(getLevel(), getBlockPos())) { if (!level.isClientSide && dir != null && getBlockPos().relative(dir).equals(neighbour) && isPeripheralOn()) {
if (hasCable()) { queueRefreshPeripheral();
// Drop the modem and convert to cable
Block.popResource(getLevel(), getBlockPos(), new ItemStack(ModRegistry.Items.WIRED_MODEM.get()));
getLevel().setBlockAndUpdate(getBlockPos(), getBlockState().setValue(CableBlock.MODEM, CableModemVariant.None));
modemChanged();
connectionsChanged();
} else {
// Drop everything and remove block
Block.popResource(getLevel(), getBlockPos(), new ItemStack(ModRegistry.Items.WIRED_MODEM.get()));
getLevel().removeBlock(getBlockPos(), false);
// This'll call #destroy(), so we don't need to reset the network here.
}
return;
}
if (!level.isClientSide && peripheralAccessAllowed) {
var facing = getDirection();
if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral();
} }
} }
private void queueRefreshPeripheral() { void queueRefreshPeripheral() {
if (invalidPeripheral) return; refreshPeripheral = true;
invalidPeripheral = true;
TickScheduler.schedule(tickToken); TickScheduler.schedule(tickToken);
} }
private void refreshPeripheral() {
invalidPeripheral = false;
if (level != null && !isRemoved() && peripheral.attach(level, getBlockPos(), getDirection())) {
updateConnectedPeripherals();
}
}
InteractionResult use(Player player) { InteractionResult use(Player player) {
if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS;
if (!canAttachPeripheral()) return InteractionResult.FAIL; if (!canAttachPeripheral()) return InteractionResult.FAIL;
if (getLevel().isClientSide) return InteractionResult.SUCCESS; if (getLevel().isClientSide) return InteractionResult.SUCCESS;
var oldName = peripheral.getConnectedName(); var oldName = peripheral.getConnectedName();
togglePeripheralAccess(); if (isPeripheralOn()) {
detachPeripheral();
} else {
attachPeripheral();
}
var newName = peripheral.getConnectedName(); var newName = peripheral.getConnectedName();
if (!Objects.equals(newName, oldName)) { if (!Objects.equals(newName, oldName)) {
if (oldName != null) { if (oldName != null) {
player.displayClientMessage(Component.translatable("chat.computercraft.wired_modem.peripheral_disconnected", player.displayClientMessage(Component.translatable("chat.computercraft.wired_modem.peripheral_disconnected",
@ -198,14 +148,11 @@ InteractionResult use(Player player) {
@Override @Override
public void load(CompoundTag nbt) { public void load(CompoundTag nbt) {
super.load(nbt); super.load(nbt);
// Fallback to the previous (incorrect) key
peripheralAccessAllowed = nbt.getBoolean(NBT_PERIPHERAL_ENABLED) || nbt.getBoolean("PeirpheralAccess");
peripheral.read(nbt, ""); peripheral.read(nbt, "");
} }
@Override @Override
public void saveAdditional(CompoundTag nbt) { public void saveAdditional(CompoundTag nbt) {
nbt.putBoolean(NBT_PERIPHERAL_ENABLED, peripheralAccessAllowed);
peripheral.write(nbt, ""); peripheral.write(nbt, "");
super.saveAdditional(nbt); super.saveAdditional(nbt);
} }
@ -214,7 +161,7 @@ private void updateBlockState() {
var state = getBlockState(); var state = getBlockState();
var oldVariant = state.getValue(CableBlock.MODEM); var oldVariant = state.getValue(CableBlock.MODEM);
var newVariant = CableModemVariant var newVariant = CableModemVariant
.from(oldVariant.getFacing(), modem.getModemState().isOpen(), peripheralAccessAllowed); .from(oldVariant.getFacing(), modem.getModemState().isOpen(), peripheral.hasPeripheral());
if (oldVariant != newVariant) { if (oldVariant != newVariant) {
level.setBlockAndUpdate(getBlockPos(), state.setValue(CableBlock.MODEM, newVariant)); level.setBlockAndUpdate(getBlockPos(), state.setValue(CableBlock.MODEM, newVariant));
@ -224,31 +171,24 @@ private void updateBlockState() {
void blockTick() { void blockTick() {
if (getLevel().isClientSide) return; if (getLevel().isClientSide) return;
if (invalidPeripheral) refreshPeripheral(); if (refreshPeripheral) {
refreshPeripheral = false;
if (isPeripheralOn()) attachPeripheral();
}
if (modem.getModemState().pollChanged()) updateBlockState(); if (modem.getModemState().pollChanged()) updateBlockState();
if (!connectionsFormed) { if (refreshConnections) connectionsChanged();
connectionsFormed = true;
connectionsChanged();
if (peripheralAccessAllowed) {
peripheral.attach(level, worldPosition, getDirection());
updateConnectedPeripherals();
}
}
if (connectionsChanged) connectionsChanged();
} }
private void scheduleConnectionsChanged() { void scheduleConnectionsChanged() {
connectionsChanged = true; refreshConnections = true;
TickScheduler.schedule(tickToken); TickScheduler.schedule(tickToken);
} }
void connectionsChanged() { void connectionsChanged() {
if (getLevel().isClientSide) return; if (getLevel().isClientSide) return;
connectionsChanged = false; refreshConnections = false;
var state = getBlockState(); var state = getBlockState();
var world = getLevel(); var world = getLevel();
@ -269,51 +209,24 @@ void connectionsChanged() {
this.node.disconnectFrom(node); this.node.disconnectFrom(node);
} }
} }
// If we can no longer attach peripherals, then detach any which may have existed
if (!canAttachPeripheral()) detachPeripheral();
} }
void modemChanged() { private void attachPeripheral() {
// Tell anyone who cares that the connection state has changed var dir = Objects.requireNonNull(getModemDirection(), "Attaching without a modem");
if (modemChanged != null) modemChanged.run(); if (peripheral.attach(getLevel(), getBlockPos(), dir)) updateConnectedPeripherals();
updateBlockState();
if (getLevel().isClientSide) return;
// If we can no longer attach peripherals, then detach any
// which may have existed
if (!canAttachPeripheral() && peripheralAccessAllowed) {
peripheralAccessAllowed = false;
peripheral.detach();
node.updatePeripherals(Map.of());
setChanged();
updateBlockState();
}
} }
private void togglePeripheralAccess() { private void detachPeripheral() {
if (!peripheralAccessAllowed) { if (peripheral.detach()) updateConnectedPeripherals();
peripheral.attach(level, getBlockPos(), getDirection());
if (!peripheral.hasPeripheral()) return;
peripheralAccessAllowed = true;
node.updatePeripherals(peripheral.toMap());
} else {
peripheral.detach();
peripheralAccessAllowed = false;
node.updatePeripherals(Map.of());
}
updateBlockState(); updateBlockState();
} }
private void updateConnectedPeripherals() { private void updateConnectedPeripherals() {
var peripherals = peripheral.toMap(); node.updatePeripherals(peripheral.toMap());
if (peripherals.isEmpty()) {
// If there are no peripherals then disable access and update the display state.
peripheralAccessAllowed = false;
updateBlockState();
}
node.updatePeripherals(peripherals);
} }
@Nullable @Nullable
@ -323,7 +236,11 @@ public WiredElement getWiredElement(@Nullable Direction direction) {
@Nullable @Nullable
public IPeripheral getPeripheral(@Nullable Direction direction) { public IPeripheral getPeripheral(@Nullable Direction direction) {
return direction == null || getMaybeDirection() == direction ? modem : null; return direction == null || getModemDirection() == direction ? modem : null;
}
private boolean isPeripheralOn() {
return getBlockState().getValue(CableBlock.MODEM).isPeripheralOn();
} }
public void onModemChanged(Runnable callback) { public void onModemChanged(Runnable callback) {

View File

@ -35,10 +35,7 @@ boolean placeAt(Level world, BlockPos pos, BlockState state) {
world.playSound(null, pos, soundType.getPlaceSound(), SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F); world.playSound(null, pos, soundType.getPlaceSound(), SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F);
var tile = world.getBlockEntity(pos); var tile = world.getBlockEntity(pos);
if (tile instanceof CableBlockEntity cable) { if (tile instanceof CableBlockEntity cable) cable.connectionsChanged();
cable.modemChanged();
cable.connectionsChanged();
}
return true; return true;
} }

View File

@ -10,49 +10,57 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;
public enum CableModemVariant implements StringRepresentable { public enum CableModemVariant implements StringRepresentable {
None("none", null), None("none", null, false, false),
DownOff("down_off", Direction.DOWN), DownOff("down_off", Direction.DOWN, false, false),
UpOff("up_off", Direction.UP), UpOff("up_off", Direction.UP, false, false),
NorthOff("north_off", Direction.NORTH), NorthOff("north_off", Direction.NORTH, false, false),
SouthOff("south_off", Direction.SOUTH), SouthOff("south_off", Direction.SOUTH, false, false),
WestOff("west_off", Direction.WEST), WestOff("west_off", Direction.WEST, false, false),
EastOff("east_off", Direction.EAST), EastOff("east_off", Direction.EAST, false, false),
DownOn("down_on", Direction.DOWN), DownOn("down_on", Direction.DOWN, true, false),
UpOn("up_on", Direction.UP), UpOn("up_on", Direction.UP, true, false),
NorthOn("north_on", Direction.NORTH), NorthOn("north_on", Direction.NORTH, true, false),
SouthOn("south_on", Direction.SOUTH), SouthOn("south_on", Direction.SOUTH, true, false),
WestOn("west_on", Direction.WEST), WestOn("west_on", Direction.WEST, true, false),
EastOn("east_on", Direction.EAST), EastOn("east_on", Direction.EAST, true, false),
DownOffPeripheral("down_off_peripheral", Direction.DOWN), DownOffPeripheral("down_off_peripheral", Direction.DOWN, false, true),
UpOffPeripheral("up_off_peripheral", Direction.UP), UpOffPeripheral("up_off_peripheral", Direction.UP, false, true),
NorthOffPeripheral("north_off_peripheral", Direction.NORTH), NorthOffPeripheral("north_off_peripheral", Direction.NORTH, false, true),
SouthOffPeripheral("south_off_peripheral", Direction.SOUTH), SouthOffPeripheral("south_off_peripheral", Direction.SOUTH, false, true),
WestOffPeripheral("west_off_peripheral", Direction.WEST), WestOffPeripheral("west_off_peripheral", Direction.WEST, false, true),
EastOffPeripheral("east_off_peripheral", Direction.EAST), EastOffPeripheral("east_off_peripheral", Direction.EAST, false, true),
DownOnPeripheral("down_on_peripheral", Direction.DOWN), DownOnPeripheral("down_on_peripheral", Direction.DOWN, true, true),
UpOnPeripheral("up_on_peripheral", Direction.UP), UpOnPeripheral("up_on_peripheral", Direction.UP, true, true),
NorthOnPeripheral("north_on_peripheral", Direction.NORTH), NorthOnPeripheral("north_on_peripheral", Direction.NORTH, true, true),
SouthOnPeripheral("south_on_peripheral", Direction.SOUTH), SouthOnPeripheral("south_on_peripheral", Direction.SOUTH, true, true),
WestOnPeripheral("west_on_peripheral", Direction.WEST), WestOnPeripheral("west_on_peripheral", Direction.WEST, true, true),
EastOnPeripheral("east_on_peripheral", Direction.EAST); EastOnPeripheral("east_on_peripheral", Direction.EAST, true, true);
private static final CableModemVariant[] VALUES = values(); private static final CableModemVariant[] VALUES = values();
private final String name; private final String name;
private final @Nullable Direction facing; private final @Nullable Direction facing;
private final boolean modemOn, peripheralOn;
CableModemVariant(String name, @Nullable Direction facing) { CableModemVariant(String name, @Nullable Direction facing, boolean modemOn, boolean peripheralOn) {
this.name = name; this.name = name;
this.facing = facing; this.facing = facing;
this.modemOn = modemOn;
this.peripheralOn = peripheralOn;
if (ordinal() != getIndex(facing, modemOn, peripheralOn)) throw new IllegalStateException("Mismatched ordinal");
} }
public static CableModemVariant from(Direction facing) { public static CableModemVariant from(Direction facing) {
return facing == null ? None : VALUES[1 + facing.get3DDataValue()]; return VALUES[1 + facing.get3DDataValue()];
}
private static int getIndex(@Nullable Direction facing, boolean modem, boolean peripheral) {
var state = (modem ? 1 : 0) + (peripheral ? 2 : 0);
return facing == null ? 0 : 1 + 6 * state + facing.get3DDataValue();
} }
public static CableModemVariant from(@Nullable Direction facing, boolean modem, boolean peripheral) { public static CableModemVariant from(@Nullable Direction facing, boolean modem, boolean peripheral) {
var state = (modem ? 1 : 0) + (peripheral ? 2 : 0); return VALUES[getIndex(facing, modem, peripheral)];
return facing == null ? None : VALUES[1 + 6 * state + facing.get3DDataValue()];
} }
@Override @Override
@ -64,6 +72,14 @@ public String getSerializedName() {
return facing; return facing;
} }
public boolean isModemOn() {
return modemOn;
}
public boolean isPeripheralOn() {
return peripheralOn;
}
@Override @Override
public String toString() { public String toString() {
return name; return name;

View File

@ -7,12 +7,14 @@
import dan200.computercraft.annotations.ForgeOverride; import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource; import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.EntityBlock;
@ -49,13 +51,27 @@ public final InteractionResult use(BlockState state, Level world, BlockPos pos,
@Override @Override
@Deprecated @Deprecated
public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.neighborChanged(neighbourPos); if (state.getValue(PERIPHERAL_ON) && level.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) {
modem.queueRefreshPeripheral(direction);
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
if (state.getValue(PERIPHERAL_ON) && level.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) {
modem.neighborChanged(neighbourPos);
}
} }
@ForgeOverride @ForgeOverride
public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) { public final void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbour) {
if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.neighborChanged(neighbour); if (state.getValue(PERIPHERAL_ON) && level.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) {
modem.neighborChanged(neighbour);
}
} }
@Override @Override

View File

@ -32,8 +32,6 @@
import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.PERIPHERAL_ON; import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.PERIPHERAL_ON;
public class WiredModemFullBlockEntity extends BlockEntity { public class WiredModemFullBlockEntity extends BlockEntity {
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
private static final class FullElement extends WiredModemElement { private static final class FullElement extends WiredModemElement {
private final WiredModemFullBlockEntity entity; private final WiredModemFullBlockEntity entity;
@ -70,11 +68,9 @@ public Vec3 getPosition() {
private final WiredModemPeripheral[] modems = new WiredModemPeripheral[6]; private final WiredModemPeripheral[] modems = new WiredModemPeripheral[6];
private boolean peripheralAccessAllowed = false;
private final WiredModemLocalPeripheral[] peripherals = new WiredModemLocalPeripheral[6]; private final WiredModemLocalPeripheral[] peripherals = new WiredModemLocalPeripheral[6];
private boolean connectionsFormed = false; private boolean refreshConnections = false;
private boolean connectionsChanged = false;
private final TickScheduler.Token tickToken = new TickScheduler.Token(this); private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
private final ModemState modemState = new ModemState(() -> TickScheduler.schedule(tickToken)); private final ModemState modemState = new ModemState(() -> TickScheduler.schedule(tickToken));
@ -96,31 +92,30 @@ public WiredModemFullBlockEntity(BlockEntityType<WiredModemFullBlockEntity> type
@Override @Override
public void setRemoved() { public void setRemoved() {
super.setRemoved(); super.setRemoved();
if (level == null || !level.isClientSide) {
node.remove(); for (var modem : modems) {
connectionsFormed = false; if (modem != null) modem.removed();
} }
if (level == null || !level.isClientSide) node.remove();
}
@Override
public void clearRemoved() {
super.clearRemoved();
refreshConnections = true;
invalidSides = DirectionUtil.ALL_SIDES;
TickScheduler.schedule(tickToken);
} }
void neighborChanged(BlockPos neighbour) { void neighborChanged(BlockPos neighbour) {
if (!level.isClientSide && peripheralAccessAllowed) { for (var facing : DirectionUtil.FACINGS) {
for (var facing : DirectionUtil.FACINGS) { if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral(facing);
if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral(facing);
}
} }
} }
private void queueRefreshPeripheral(Direction facing) { void queueRefreshPeripheral(Direction facing) {
if (invalidSides == 0) TickScheduler.schedule(tickToken);
invalidSides |= 1 << facing.ordinal(); invalidSides |= 1 << facing.ordinal();
} TickScheduler.schedule(tickToken);
private void refreshPeripheral(Direction facing) {
invalidSides &= ~(1 << facing.ordinal());
var peripheral = peripherals[facing.ordinal()];
if (level != null && !isRemoved() && peripheral.attach(level, getBlockPos(), facing)) {
updateConnectedPeripherals();
}
} }
public InteractionResult use(Player player) { public InteractionResult use(Player player) {
@ -129,7 +124,11 @@ public InteractionResult use(Player player) {
// On server, we interacted if a peripheral was found // On server, we interacted if a peripheral was found
var oldPeriphNames = getConnectedPeripheralNames(); var oldPeriphNames = getConnectedPeripheralNames();
togglePeripheralAccess(); if (isPeripheralOn()) {
detachPeripherals();
} else {
attachPeripherals(DirectionUtil.ALL_SIDES);
}
var periphNames = getConnectedPeripheralNames(); var periphNames = getConnectedPeripheralNames();
if (!Objects.equals(periphNames, oldPeriphNames)) { if (!Objects.equals(periphNames, oldPeriphNames)) {
@ -158,65 +157,45 @@ private static void sendPeripheralChanges(Player player, String kind, Collection
@Override @Override
public void load(CompoundTag nbt) { public void load(CompoundTag nbt) {
super.load(nbt); super.load(nbt);
peripheralAccessAllowed = nbt.getBoolean(NBT_PERIPHERAL_ENABLED);
for (var i = 0; i < peripherals.length; i++) peripherals[i].read(nbt, Integer.toString(i)); for (var i = 0; i < peripherals.length; i++) peripherals[i].read(nbt, Integer.toString(i));
} }
@Override @Override
public void saveAdditional(CompoundTag nbt) { public void saveAdditional(CompoundTag nbt) {
nbt.putBoolean(NBT_PERIPHERAL_ENABLED, peripheralAccessAllowed);
for (var i = 0; i < peripherals.length; i++) peripherals[i].write(nbt, Integer.toString(i)); for (var i = 0; i < peripherals.length; i++) peripherals[i].write(nbt, Integer.toString(i));
super.saveAdditional(nbt); super.saveAdditional(nbt);
} }
private void updateBlockState() {
var state = getBlockState();
boolean modemOn = modemState.isOpen(), peripheralOn = peripheralAccessAllowed;
if (state.getValue(MODEM_ON) == modemOn && state.getValue(PERIPHERAL_ON) == peripheralOn) return;
getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(MODEM_ON, modemOn).setValue(PERIPHERAL_ON, peripheralOn));
}
@Override
public void clearRemoved() {
super.clearRemoved();
TickScheduler.schedule(tickToken);
}
void blockTick() { void blockTick() {
if (getLevel().isClientSide) return; if (getLevel().isClientSide) return;
if (invalidSides != 0) { if (invalidSides != 0) {
for (var direction : DirectionUtil.FACINGS) { var oldInvalidSides = invalidSides;
if ((invalidSides & (1 << direction.ordinal())) != 0) refreshPeripheral(direction); invalidSides = 0;
} if (isPeripheralOn()) attachPeripherals(oldInvalidSides);
} }
if (modemState.pollChanged()) updateBlockState(); if (modemState.pollChanged()) updateModemBlockState();
if (!connectionsFormed) { if (refreshConnections) connectionsChanged();
connectionsFormed = true; }
connectionsChanged(); private void updateModemBlockState() {
if (peripheralAccessAllowed) { var state = getBlockState();
for (var facing : DirectionUtil.FACINGS) { var modemOn = modemState.isOpen();
peripherals[facing.ordinal()].attach(level, getBlockPos(), facing); if (state.getValue(MODEM_ON) == modemOn) return;
}
updateConnectedPeripherals();
}
}
if (connectionsChanged) connectionsChanged(); getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(MODEM_ON, modemOn));
} }
private void scheduleConnectionsChanged() { private void scheduleConnectionsChanged() {
connectionsChanged = true; refreshConnections = true;
TickScheduler.schedule(tickToken); TickScheduler.schedule(tickToken);
} }
private void connectionsChanged() { private void connectionsChanged() {
if (getLevel().isClientSide) return; if (getLevel().isClientSide) return;
connectionsChanged = false; refreshConnections = false;
var world = getLevel(); var world = getLevel();
var current = getBlockPos(); var current = getBlockPos();
@ -231,57 +210,48 @@ private void connectionsChanged() {
} }
} }
private void togglePeripheralAccess() { private List<String> getConnectedPeripheralNames() {
if (!peripheralAccessAllowed) { List<String> peripherals = new ArrayList<>(6);
var hasAny = false;
for (var facing : DirectionUtil.FACINGS) {
var peripheral = peripherals[facing.ordinal()];
peripheral.attach(level, getBlockPos(), facing);
hasAny |= peripheral.hasPeripheral();
}
if (!hasAny) return;
peripheralAccessAllowed = true;
node.updatePeripherals(getConnectedPeripherals());
} else {
peripheralAccessAllowed = false;
for (var peripheral : peripherals) peripheral.detach();
node.updatePeripherals(Map.of());
}
updateBlockState();
}
private Set<String> getConnectedPeripheralNames() {
if (!peripheralAccessAllowed) return Set.of();
Set<String> peripherals = new HashSet<>(6);
for (var peripheral : this.peripherals) { for (var peripheral : this.peripherals) {
var name = peripheral.getConnectedName(); var name = peripheral.getConnectedName();
if (name != null) peripherals.add(name); if (name != null) peripherals.add(name);
} }
peripherals.sort(String::compareTo);
return peripherals; return peripherals;
} }
private Map<String, IPeripheral> getConnectedPeripherals() { private void attachPeripherals(int sides) {
if (!peripheralAccessAllowed) return Map.of(); var anyChanged = false;
Map<String, IPeripheral> peripherals = new HashMap<>(6); Map<String, IPeripheral> attachedPeripherals = new HashMap<>(6);
for (var peripheral : this.peripherals) peripheral.extendMap(peripherals);
return Collections.unmodifiableMap(peripherals);
}
private void updateConnectedPeripherals() { for (var facing : DirectionUtil.FACINGS) {
var peripherals = getConnectedPeripherals(); var peripheral = peripherals[facing.ordinal()];
if (peripherals.isEmpty()) { if (DirectionUtil.isSet(sides, facing)) anyChanged |= peripheral.attach(getLevel(), getBlockPos(), facing);
// If there are no peripherals then disable access and update the display state. peripheral.extendMap(attachedPeripherals);
peripheralAccessAllowed = false;
updateBlockState();
} }
node.updatePeripherals(peripherals); if (anyChanged) node.updatePeripherals(attachedPeripherals);
updatePeripheralBlocKState(!attachedPeripherals.isEmpty());
}
private void detachPeripherals() {
var anyChanged = false;
for (var peripheral : peripherals) anyChanged |= peripheral.detach();
if (anyChanged) node.updatePeripherals(Map.of());
updatePeripheralBlocKState(false);
}
private void updatePeripheralBlocKState(boolean peripheralOn) {
var state = getBlockState();
if (state.getValue(PERIPHERAL_ON) == peripheralOn) return;
getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(PERIPHERAL_ON, peripheralOn));
}
private boolean isPeripheralOn() {
return getBlockState().getValue(PERIPHERAL_ON);
} }
public WiredElement getElement() { public WiredElement getElement() {
@ -295,22 +265,11 @@ public WiredModemPeripheral getPeripheral(@Nullable Direction side) {
var peripheral = modems[side.ordinal()]; var peripheral = modems[side.ordinal()];
if (peripheral != null) return peripheral; if (peripheral != null) return peripheral;
var localPeripheral = peripherals[side.ordinal()]; return modems[side.ordinal()] = new WiredModemPeripheral(modemState, element, peripherals[side.ordinal()], this) {
return modems[side.ordinal()] = new WiredModemPeripheral(modemState, element) {
@Override
protected WiredModemLocalPeripheral getLocalPeripheral() {
return localPeripheral;
}
@Override @Override
public Vec3 getPosition() { public Vec3 getPosition() {
return Vec3.atCenterOf(getBlockPos().relative(side)); return Vec3.atCenterOf(getBlockPos().relative(side));
} }
@Override
public Object getTarget() {
return WiredModemFullBlockEntity.this;
}
}; };
} }
} }

View File

@ -6,6 +6,7 @@
import dan200.computercraft.api.ComputerCraftTags; import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.PeripheralHelpers;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.platform.ComponentAccess; import dan200.computercraft.shared.platform.ComponentAccess;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -65,7 +66,7 @@ public boolean attach(Level world, BlockPos origin, Direction direction) {
this.id = ServerContext.get(assertNonNull(world.getServer())).getNextId("peripheral." + type); this.id = ServerContext.get(assertNonNull(world.getServer())).getNextId("peripheral." + type);
} }
return oldPeripheral == null || !oldPeripheral.equals(peripheral); return !PeripheralHelpers.equals(oldPeripheral, peripheral);
} }
} }

View File

@ -15,6 +15,7 @@
import dan200.computercraft.api.peripheral.NotAttachedException; import dan200.computercraft.api.peripheral.NotAttachedException;
import dan200.computercraft.api.peripheral.WorkMonitor; import dan200.computercraft.api.peripheral.WorkMonitor;
import dan200.computercraft.core.apis.PeripheralAPI; import dan200.computercraft.core.apis.PeripheralAPI;
import dan200.computercraft.core.computer.GuardedLuaContext;
import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.core.methods.PeripheralMethod;
import dan200.computercraft.core.util.LuaUtil; import dan200.computercraft.core.util.LuaUtil;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
@ -22,6 +23,7 @@
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -34,12 +36,21 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
private static final Logger LOG = LoggerFactory.getLogger(WiredModemPeripheral.class); private static final Logger LOG = LoggerFactory.getLogger(WiredModemPeripheral.class);
private final WiredModemElement modem; private final WiredModemElement modem;
private final WiredModemLocalPeripheral localPeripheral;
private final BlockEntity target;
private final Map<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> peripheralWrappers = new HashMap<>(1); private final Map<IComputerAccess, ConcurrentMap<String, RemotePeripheralWrapper>> peripheralWrappers = new HashMap<>(1);
public WiredModemPeripheral(ModemState state, WiredModemElement modem) { public WiredModemPeripheral(
ModemState state,
WiredModemElement modem,
WiredModemLocalPeripheral localPeripheral,
BlockEntity target
) {
super(state); super(state);
this.modem = modem; this.modem = modem;
this.localPeripheral = localPeripheral;
this.target = target;
} }
//region IPacketSender implementation //region IPacketSender implementation
@ -62,8 +73,6 @@ protected PacketNetwork getNetwork() {
public Level getLevel() { public Level getLevel() {
return modem.getLevel(); return modem.getLevel();
} }
protected abstract WiredModemLocalPeripheral getLocalPeripheral();
//endregion //endregion
@Override @Override
@ -207,7 +216,7 @@ public final MethodResult callRemote(IComputerAccess computer, ILuaContext conte
*/ */
@LuaFunction @LuaFunction
public final @Nullable Object[] getNameLocal() { public final @Nullable Object[] getNameLocal() {
var local = getLocalPeripheral().getConnectedName(); var local = localPeripheral.getConnectedName();
return local == null ? null : new Object[]{ local }; return local == null ? null : new Object[]{ local };
} }
@ -218,8 +227,7 @@ public void attach(IComputerAccess computer) {
ConcurrentMap<String, RemotePeripheralWrapper> wrappers; ConcurrentMap<String, RemotePeripheralWrapper> wrappers;
synchronized (peripheralWrappers) { synchronized (peripheralWrappers) {
wrappers = peripheralWrappers.get(computer); wrappers = peripheralWrappers.computeIfAbsent(computer, k -> new ConcurrentHashMap<>());
if (wrappers == null) peripheralWrappers.put(computer, wrappers = new ConcurrentHashMap<>());
} }
synchronized (modem.getRemotePeripherals()) { synchronized (modem.getRemotePeripherals()) {
@ -245,11 +253,13 @@ public void detach(IComputerAccess computer) {
} }
@Override @Override
public boolean equals(@Nullable IPeripheral other) { public final boolean equals(@Nullable IPeripheral other) {
if (other instanceof WiredModemPeripheral otherModem) { return other instanceof WiredModemPeripheral otherModem && otherModem.modem == modem;
return otherModem.modem == modem; }
}
return false; @Override
public final Object getTarget() {
return target;
} }
//endregion //endregion
@ -272,12 +282,11 @@ public void detachPeripheral(String name) {
var wrapper = wrappers.remove(name); var wrapper = wrappers.remove(name);
if (wrapper != null) wrapper.detach(); if (wrapper != null) wrapper.detach();
} }
} }
} }
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) { private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) { if (!peripherals.containsKey(periphName) && !periphName.equals(localPeripheral.getConnectedName())) {
var methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral); var methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral);
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods); var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods);
peripherals.put(periphName, wrapper); peripherals.put(periphName, wrapper);
@ -296,7 +305,7 @@ private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String
return wrappers == null ? null : wrappers.get(remoteName); return wrappers == null ? null : wrappers.get(remoteName);
} }
private static class RemotePeripheralWrapper implements IComputerAccess { private static class RemotePeripheralWrapper implements IComputerAccess, GuardedLuaContext.Guard {
private final WiredModemElement element; private final WiredModemElement element;
private final IPeripheral peripheral; private final IPeripheral peripheral;
private final IComputerAccess computer; private final IComputerAccess computer;
@ -309,6 +318,8 @@ private static class RemotePeripheralWrapper implements IComputerAccess {
private volatile boolean attached; private volatile boolean attached;
private final Set<String> mounts = new HashSet<>(); private final Set<String> mounts = new HashSet<>();
private @Nullable GuardedLuaContext contextWrapper;
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map<String, PeripheralMethod> methods) { RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map<String, PeripheralMethod> methods) {
this.element = element; this.element = element;
this.peripheral = peripheral; this.peripheral = peripheral;
@ -356,7 +367,19 @@ public Collection<String> getMethodNames() {
public MethodResult callMethod(ILuaContext context, String methodName, IArguments arguments) throws LuaException { public MethodResult callMethod(ILuaContext context, String methodName, IArguments arguments) throws LuaException {
var method = methodMap.get(methodName); var method = methodMap.get(methodName);
if (method == null) throw new LuaException("No such method " + methodName); if (method == null) throw new LuaException("No such method " + methodName);
return method.apply(peripheral, context, this, arguments);
// Wrap the ILuaContext. We try to reuse the previous context where possible to avoid allocations.
var contextWrapper = this.contextWrapper;
if (contextWrapper == null || !contextWrapper.wraps(context)) {
contextWrapper = this.contextWrapper = new GuardedLuaContext(context, this);
}
return method.apply(peripheral, contextWrapper, this, arguments);
}
@Override
public boolean checkValid() {
return attached;
} }
// IComputerAccess implementation // IComputerAccess implementation

View File

@ -56,8 +56,11 @@ public boolean pollTerminalChanged() {
void read(TerminalState state) { void read(TerminalState state) {
if (state.hasTerminal()) { if (state.hasTerminal()) {
if (terminal == null) terminal = new NetworkedTerminal(state.width, state.height, state.colour); if (terminal == null) {
state.apply(terminal); terminal = state.create();
} else {
state.apply(terminal);
}
terminalChanged = true; terminalChanged = true;
} else { } else {
if (terminal != null) { if (terminal != null) {

View File

@ -37,7 +37,7 @@ class DfpwmState {
private boolean unplayed = true; private boolean unplayed = true;
private long clientEndTime = PauseAwareTimer.getTime(); private long clientEndTime = PauseAwareTimer.getTime();
private float pendingVolume = 1.0f; private float pendingVolume = 1.0f;
private @Nullable ByteBuffer pendingAudio; private @Nullable EncodedAudio pendingAudio;
synchronized boolean pushBuffer(LuaTable<?, ?> table, int size, Optional<Double> volume) throws LuaException { synchronized boolean pushBuffer(LuaTable<?, ?> table, int size, Optional<Double> volume) throws LuaException {
if (pendingAudio != null) return false; if (pendingAudio != null) return false;
@ -45,6 +45,10 @@ synchronized boolean pushBuffer(LuaTable<?, ?> table, int size, Optional<Double>
var outSize = size / 8; var outSize = size / 8;
var buffer = ByteBuffer.allocate(outSize); var buffer = ByteBuffer.allocate(outSize);
var initialCharge = charge;
var initialStrength = strength;
var initialPreviousBit = previousBit;
for (var i = 0; i < outSize; i++) { for (var i = 0; i < outSize; i++) {
var thisByte = 0; var thisByte = 0;
for (var j = 1; j <= 8; j++) { for (var j = 1; j <= 8; j++) {
@ -80,7 +84,7 @@ synchronized boolean pushBuffer(LuaTable<?, ?> table, int size, Optional<Double>
buffer.flip(); buffer.flip();
pendingAudio = buffer; pendingAudio = new EncodedAudio(initialCharge, initialStrength, initialPreviousBit, buffer);
pendingVolume = (float) clampVolume(volume.orElse((double) pendingVolume)); pendingVolume = (float) clampVolume(volume.orElse((double) pendingVolume));
return true; return true;
} }
@ -89,12 +93,12 @@ boolean shouldSendPending(long now) {
return pendingAudio != null && now >= clientEndTime - CLIENT_BUFFER; return pendingAudio != null && now >= clientEndTime - CLIENT_BUFFER;
} }
ByteBuffer pullPending(long now) { EncodedAudio pullPending(long now) {
var audio = pendingAudio; var audio = pendingAudio;
if (audio == null) throw new IllegalStateException("Should not pull pending audio yet"); if (audio == null) throw new IllegalStateException("Should not pull pending audio yet");
pendingAudio = null; pendingAudio = null;
// Compute when we should consider sending the next packet. // Compute when we should consider sending the next packet.
clientEndTime = Math.max(now, clientEndTime) + (audio.remaining() * SECOND * 8 / SAMPLE_RATE); clientEndTime = Math.max(now, clientEndTime) + (audio.audio().remaining() * SECOND * 8 / SAMPLE_RATE);
unplayed = false; unplayed = false;
return audio; return audio;
} }

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.speaker;
import net.minecraft.network.FriendlyByteBuf;
import java.nio.ByteBuffer;
/**
* A chunk of encoded audio, along with the state required for the decoder to reproduce the original audio samples.
*
* @param charge The DFPWM charge.
* @param strength The DFPWM strength.
* @param previousBit The previous bit.
* @param audio The block of encoded audio.
*/
public record EncodedAudio(int charge, int strength, boolean previousBit, ByteBuffer audio) {
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(charge());
buf.writeVarInt(strength());
buf.writeBoolean(previousBit());
buf.writeBytes(audio().duplicate());
}
public static EncodedAudio read(FriendlyByteBuf buf) {
var charge = buf.readVarInt();
var strength = buf.readVarInt();
var previousBit = buf.readBoolean();
var bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
return new EncodedAudio(charge, strength, previousBit, ByteBuffer.wrap(bytes));
}
}

View File

@ -18,16 +18,18 @@
import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage; import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.network.server.ServerNetworking;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.PauseAwareTimer; import dan200.computercraft.shared.util.PauseAwareTimer;
import net.minecraft.ResourceLocationException;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.protocol.game.ClientboundSoundPacket; import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.item.RecordItem;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -252,15 +254,15 @@ public final boolean playSound(ILuaContext context, String name, Optional<Double
var volume = (float) clampVolume(checkFinite(1, volumeA.orElse(1.0))); var volume = (float) clampVolume(checkFinite(1, volumeA.orElse(1.0)));
var pitch = (float) checkFinite(2, pitchA.orElse(1.0)); var pitch = (float) checkFinite(2, pitchA.orElse(1.0));
ResourceLocation identifier; var identifier = ResourceLocation.tryParse(name);
try { if (identifier == null) throw new LuaException("Malformed sound name '" + name + "' ");
identifier = new ResourceLocation(name);
} catch (ResourceLocationException e) { // Prevent playing music discs.
throw new LuaException("Malformed sound name '" + name + "' "); var soundEvent = PlatformHelper.get().tryGetRegistryObject(Registries.SOUND_EVENT, identifier);
} if (soundEvent != null && RecordItem.getBySound(soundEvent) != null) return false;
synchronized (lock) { synchronized (lock) {
if (dfpwmState != null && dfpwmState.isPlaying()) return false; if (pendingSound != null || (dfpwmState != null && dfpwmState.isPlaying())) return false;
dfpwmState = null; dfpwmState = null;
pendingSound = new PendingSound<>(identifier, volume, pitch); pendingSound = new PendingSound<>(identifier, volume, pitch);
return true; return true;

View File

@ -389,4 +389,13 @@ default double getReachDistance(Player player) {
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) * @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)
*/ */
InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock); InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock);
/**
* Whether {@link net.minecraft.network.chat.ClickEvent.Action#RUN_COMMAND} can be used to run client commands.
*
* @return Whether client commands can be triggered from chat components.
*/
default boolean canClickRunClientCommand() {
return true;
}
} }

View File

@ -190,6 +190,6 @@ protected void onTerminalChanged() {
@Override @Override
protected void onRemoved() { protected void onRemoved() {
super.onRemoved(); super.onRemoved();
ServerNetworking.sendToAllPlayers(new PocketComputerDeletedClientMessage(getInstanceID()), getLevel().getServer()); ServerNetworking.sendToAllPlayers(new PocketComputerDeletedClientMessage(getInstanceUUID()), getLevel().getServer());
} }
} }

View File

@ -43,6 +43,7 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
public class PocketComputerItem extends Item implements IComputerItem, IMedia, IColouredItem { public class PocketComputerItem extends Item implements IComputerItem, IMedia, IColouredItem {
private static final String NBT_UPGRADE = "Upgrade"; private static final String NBT_UPGRADE = "Upgrade";
@ -188,10 +189,9 @@ public String getCreatorModId(ItemStack stack) {
} }
public PocketServerComputer createServerComputer(ServerLevel level, Entity entity, @Nullable Container inventory, ItemStack stack) { public PocketServerComputer createServerComputer(ServerLevel level, Entity entity, @Nullable Container inventory, ItemStack stack) {
var sessionID = getSessionID(stack);
var registry = ServerContext.get(level.getServer()).registry(); var registry = ServerContext.get(level.getServer()).registry();
var computer = (PocketServerComputer) registry.get(sessionID, getInstanceID(stack)); var computer = (PocketServerComputer) registry.get(getSessionID(stack), getInstanceID(stack));
if (computer == null) { if (computer == null) {
var computerID = getComputerID(stack); var computerID = getComputerID(stack);
if (computerID < 0) { if (computerID < 0) {
@ -201,8 +201,9 @@ public PocketServerComputer createServerComputer(ServerLevel level, Entity entit
computer = new PocketServerComputer(level, entity.blockPosition(), getComputerID(stack), getLabel(stack), getFamily()); computer = new PocketServerComputer(level, entity.blockPosition(), getComputerID(stack), getLabel(stack), getFamily());
setInstanceID(stack, computer.register()); var tag = stack.getOrCreateTag();
setSessionID(stack, registry.getSessionID()); tag.putInt(NBT_SESSION, registry.getSessionID());
tag.putUUID(NBT_INSTANCE, computer.register());
var upgrade = getUpgrade(stack); var upgrade = getUpgrade(stack);
@ -267,13 +268,9 @@ public boolean setLabel(ItemStack stack, @Nullable String label) {
return null; return null;
} }
public static int getInstanceID(ItemStack stack) { public static @Nullable UUID getInstanceID(ItemStack stack) {
var nbt = stack.getTag(); var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_INSTANCE) ? nbt.getInt(NBT_INSTANCE) : -1; return nbt != null && nbt.hasUUID(NBT_INSTANCE) ? nbt.getUUID(NBT_INSTANCE) : null;
}
private static void setInstanceID(ItemStack stack, int instanceID) {
stack.getOrCreateTag().putInt(NBT_INSTANCE, instanceID);
} }
private static int getSessionID(ItemStack stack) { private static int getSessionID(ItemStack stack) {
@ -281,10 +278,6 @@ private static int getSessionID(ItemStack stack) {
return nbt != null && nbt.contains(NBT_SESSION) ? nbt.getInt(NBT_SESSION) : -1; return nbt != null && nbt.contains(NBT_SESSION) ? nbt.getInt(NBT_SESSION) : -1;
} }
private static void setSessionID(ItemStack stack, int sessionID) {
stack.getOrCreateTag().putInt(NBT_SESSION, sessionID);
}
private static boolean isMarkedOn(ItemStack stack) { private static boolean isMarkedOn(ItemStack stack) {
var nbt = stack.getTag(); var nbt = stack.getTag();
return nbt != null && nbt.getBoolean(NBT_ON); return nbt != null && nbt.getBoolean(NBT_ON);

View File

@ -24,10 +24,9 @@
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList; import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
@ -136,17 +135,8 @@ public void loadServer(CompoundTag nbt) {
super.loadServer(nbt); super.loadServer(nbt);
// Read inventory // Read inventory
var nbttaglist = nbt.getList("Items", Tag.TAG_COMPOUND); ContainerHelper.loadAllItems(nbt, inventory);
inventory.clear(); for (var i = 0; i < inventory.size(); i++) inventorySnapshot.set(i, inventory.get(i).copy());
inventorySnapshot.clear();
for (var i = 0; i < nbttaglist.size(); i++) {
var tag = nbttaglist.getCompound(i);
var slot = tag.getByte("Slot") & 0xff;
if (slot < getContainerSize()) {
inventory.set(slot, ItemStack.of(tag));
inventorySnapshot.set(slot, inventory.get(slot).copy());
}
}
// Read state // Read state
brain.readFromNBT(nbt); brain.readFromNBT(nbt);
@ -155,16 +145,7 @@ public void loadServer(CompoundTag nbt) {
@Override @Override
public void saveAdditional(CompoundTag nbt) { public void saveAdditional(CompoundTag nbt) {
// Write inventory // Write inventory
var nbttaglist = new ListTag(); ContainerHelper.saveAllItems(nbt, inventory);
for (var i = 0; i < INVENTORY_SIZE; i++) {
if (!inventory.get(i).isEmpty()) {
var tag = new CompoundTag();
tag.putByte("Slot", (byte) i);
inventory.get(i).save(tag);
nbttaglist.add(tag);
}
}
nbt.put("Items", nbttaglist);
// Write brain // Write brain
nbt = brain.writeToNBT(nbt); nbt = brain.writeToNBT(nbt);

View File

@ -14,6 +14,7 @@
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.util.PeripheralHelpers;
import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
@ -589,7 +590,7 @@ private void updatePeripherals(ServerComputer serverComputer) {
} }
var existing = peripherals.get(side); var existing = peripherals.get(side);
if (existing == peripheral || (existing != null && peripheral != null && existing.equals(peripheral))) { if (PeripheralHelpers.equals(existing, peripheral)) {
// If the peripheral is the same, just use that. // If the peripheral is the same, just use that.
peripheral = existing; peripheral = existing;
} else { } else {

View File

@ -54,7 +54,7 @@ public TurtleCommandResult execute(ITurtleAccess turtle) {
case ContainerTransfer.NO_SPACE: case ContainerTransfer.NO_SPACE:
return TurtleCommandResult.failure("No space for items"); return TurtleCommandResult.failure("No space for items");
case ContainerTransfer.NO_ITEMS: case ContainerTransfer.NO_ITEMS:
return TurtleCommandResult.failure("No items to drop"); return TurtleCommandResult.failure("No items to take");
default: default:
turtle.playAnimation(TurtleAnimation.WAIT); turtle.playAnimation(TurtleAnimation.WAIT);
return TurtleCommandResult.success(); return TurtleCommandResult.success();

View File

@ -11,6 +11,11 @@ public final class DirectionUtil {
private DirectionUtil() { private DirectionUtil() {
} }
/**
* A bitmask indicating all sides.
*/
public static final int ALL_SIDES = (1 << 6) - 1;
public static final Direction[] FACINGS = Direction.values(); public static final Direction[] FACINGS = Direction.values();
public static ComputerSide toLocal(Direction front, Direction dir) { public static ComputerSide toLocal(Direction front, Direction dir) {
@ -31,4 +36,15 @@ public static float toPitchAngle(Direction dir) {
default -> 0.0f; default -> 0.0f;
}; };
} }
/**
* Determine if a direction is in a bitmask.
*
* @param mask The bitmask to test
* @param direction The direction to check.
* @return Whether the direction is in a bitmask.
*/
public static boolean isSet(int mask, Direction direction) {
return (mask & (1 << direction.ordinal())) != 0;
}
} }

View File

@ -7,7 +7,6 @@ accessWidener v1 named
# Additional access wideners for vanilla code. This is a effectively the subset of Fabric's transitive access wideners # Additional access wideners for vanilla code. This is a effectively the subset of Fabric's transitive access wideners
# that we actually use # that we actually use
accessible method net/minecraft/client/renderer/item/ItemProperties register (Lnet/minecraft/world/item/Item;Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/item/ClampedItemPropertyFunction;)V
accessible method net/minecraft/client/renderer/blockentity/BlockEntityRenderers register (Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/client/renderer/blockentity/BlockEntityRendererProvider;)V accessible method net/minecraft/client/renderer/blockentity/BlockEntityRenderers register (Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/client/renderer/blockentity/BlockEntityRendererProvider;)V
accessible class net/minecraft/world/item/CreativeModeTab$Output accessible class net/minecraft/world/item/CreativeModeTab$Output
accessible field net/minecraft/world/item/CreativeModeTabs OP_BLOCKS Lnet/minecraft/resources/ResourceKey; accessible field net/minecraft/world/item/CreativeModeTabs OP_BLOCKS Lnet/minecraft/resources/ResourceKey;

View File

@ -4,6 +4,7 @@
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -16,7 +17,7 @@ public void testDecodesBytes() {
var stream = new DfpwmStream(); var stream = new DfpwmStream();
var input = ByteBuffer.wrap(new byte[]{ 43, -31, 33, 44, 30, -16, -85, 23, -3, -55, 46, -70, 68, -67, 74, -96, -68, 16, 94, -87, -5, 87, 11, -16, 19, 92, 85, -71, 126, 5, -84, 64, 17, -6, 85, -11, -1, -87, -12, 1, 85, -56, 33, -80, 82, 104, -93, 17, 126, 23, 91, -30, 37, -32, 117, -72, -58, 11, -76, 19, -108, 86, -65, -10, -1, -68, -25, 10, -46, 85, 124, -54, 15, -24, 43, -94, 117, 63, -36, 15, -6, 88, 87, -26, -83, 106, 41, 13, -28, -113, -10, -66, 119, -87, -113, 68, -55, 40, -107, 62, 20, 72, 3, -96, 114, -87, -2, 39, -104, 30, 20, 42, 84, 24, 47, 64, 43, 61, -35, 95, -65, 42, 61, 42, -50, 4, -9, 81 }); var input = ByteBuffer.wrap(new byte[]{ 43, -31, 33, 44, 30, -16, -85, 23, -3, -55, 46, -70, 68, -67, 74, -96, -68, 16, 94, -87, -5, 87, 11, -16, 19, 92, 85, -71, 126, 5, -84, 64, 17, -6, 85, -11, -1, -87, -12, 1, 85, -56, 33, -80, 82, 104, -93, 17, 126, 23, 91, -30, 37, -32, 117, -72, -58, 11, -76, 19, -108, 86, -65, -10, -1, -68, -25, 10, -46, 85, 124, -54, 15, -24, 43, -94, 117, 63, -36, 15, -6, 88, 87, -26, -83, 106, 41, 13, -28, -113, -10, -66, 119, -87, -113, 68, -55, 40, -107, 62, 20, 72, 3, -96, 114, -87, -2, 39, -104, 30, 20, 42, 84, 24, 47, 64, 43, 61, -35, 95, -65, 42, 61, 42, -50, 4, -9, 81 });
stream.push(input); stream.push(new EncodedAudio(0, 0, false, input));
var buffer = stream.read(1024 + 1); var buffer = stream.read(1024 + 1);
assertEquals(1024, buffer.remaining(), "Must have read 1024 bytes"); assertEquals(1024, buffer.remaining(), "Must have read 1024 bytes");

View File

@ -22,6 +22,7 @@
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.UUID;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@ -39,19 +40,19 @@ public void Parse_basic_inputs(String input, ComputerSelector expected) throws C
public static Arguments[] getArgumentTestCases() { public static Arguments[] getArgumentTestCases() {
return new Arguments[]{ return new Arguments[]{
// Legacy selectors // Legacy selectors
Arguments.of("@some_label", new ComputerSelector("@some_label", OptionalInt.empty(), OptionalInt.empty(), "some_label", null, null, null)), Arguments.of("@some_label", new ComputerSelector("@some_label", OptionalInt.empty(), null, OptionalInt.empty(), "some_label", null, null, null)),
Arguments.of("~normal", new ComputerSelector("~normal", OptionalInt.empty(), OptionalInt.empty(), null, ComputerFamily.NORMAL, null, null)), Arguments.of("~normal", new ComputerSelector("~normal", OptionalInt.empty(), null, OptionalInt.empty(), null, ComputerFamily.NORMAL, null, null)),
Arguments.of("#123", new ComputerSelector("#123", OptionalInt.empty(), OptionalInt.of(123), null, null, null, null)), Arguments.of("#123", new ComputerSelector("#123", OptionalInt.empty(), null, OptionalInt.of(123), null, null, null, null)),
Arguments.of("123", new ComputerSelector("123", OptionalInt.of(123), OptionalInt.empty(), null, null, null, null)), Arguments.of("123", new ComputerSelector("123", OptionalInt.of(123), null, OptionalInt.empty(), null, null, null, null)),
// New selectors // New selectors
Arguments.of("@c[]", new ComputerSelector("@c[]", OptionalInt.empty(), OptionalInt.empty(), null, null, null, null)), Arguments.of("@c[]", new ComputerSelector("@c[]", OptionalInt.empty(), null, OptionalInt.empty(), null, null, null, null)),
Arguments.of("@c[instance=123]", new ComputerSelector("@c[instance=123]", OptionalInt.of(123), OptionalInt.empty(), null, null, null, null)), Arguments.of("@c[instance=5e18f505-62f7-46f8-83f3-792f03224724]", new ComputerSelector("@c[instance=5e18f505-62f7-46f8-83f3-792f03224724]", OptionalInt.empty(), UUID.fromString("5e18f505-62f7-46f8-83f3-792f03224724"), OptionalInt.empty(), null, null, null, null)),
Arguments.of("@c[id=123]", new ComputerSelector("@c[id=123]", OptionalInt.empty(), OptionalInt.of(123), null, null, null, null)), Arguments.of("@c[id=123]", new ComputerSelector("@c[id=123]", OptionalInt.empty(), null, OptionalInt.of(123), null, null, null, null)),
Arguments.of("@c[label=\"foo\"]", new ComputerSelector("@c[label=\"foo\"]", OptionalInt.empty(), OptionalInt.empty(), "foo", null, null, null)), Arguments.of("@c[label=\"foo\"]", new ComputerSelector("@c[label=\"foo\"]", OptionalInt.empty(), null, OptionalInt.empty(), "foo", null, null, null)),
Arguments.of("@c[family=normal]", new ComputerSelector("@c[family=normal]", OptionalInt.empty(), OptionalInt.empty(), null, ComputerFamily.NORMAL, null, null)), Arguments.of("@c[family=normal]", new ComputerSelector("@c[family=normal]", OptionalInt.empty(), null, OptionalInt.empty(), null, ComputerFamily.NORMAL, null, null)),
// Complex selectors // Complex selectors
Arguments.of("@c[ id = 123 , ]", new ComputerSelector("@c[ id = 123 , ]", OptionalInt.empty(), OptionalInt.of(123), null, null, null, null)), Arguments.of("@c[ id = 123 , ]", new ComputerSelector("@c[ id = 123 , ]", OptionalInt.empty(), null, OptionalInt.of(123), null, null, null, null)),
Arguments.of("@c[id=123,family=normal]", new ComputerSelector("@c[id=123,family=normal]", OptionalInt.empty(), OptionalInt.of(123), null, ComputerFamily.NORMAL, null, null)), Arguments.of("@c[id=123,family=normal]", new ComputerSelector("@c[id=123,family=normal]", OptionalInt.empty(), null, OptionalInt.of(123), null, ComputerFamily.NORMAL, null, null)),
}; };
} }

View File

@ -11,7 +11,8 @@
import java.util.Random; import java.util.Random;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/** /**
* Tests {@link TerminalState} round tripping works as expected. * Tests {@link TerminalState} round tripping works as expected.
@ -42,6 +43,7 @@ private static NetworkedTerminal randomTerminal() {
private static void checkEqual(Terminal expected, Terminal actual) { private static void checkEqual(Terminal expected, Terminal actual) {
assertNotNull(expected, "Expected cannot be null"); assertNotNull(expected, "Expected cannot be null");
assertNotNull(actual, "Actual cannot be null"); assertNotNull(actual, "Actual cannot be null");
assertEquals(expected.isColour(), actual.isColour(), "isColour must match");
assertEquals(expected.getHeight(), actual.getHeight(), "Heights must match"); assertEquals(expected.getHeight(), actual.getHeight(), "Heights must match");
assertEquals(expected.getWidth(), actual.getWidth(), "Widths must match"); assertEquals(expected.getWidth(), actual.getWidth(), "Widths must match");
@ -51,13 +53,6 @@ private static void checkEqual(Terminal expected, Terminal actual) {
} }
private static NetworkedTerminal read(FriendlyByteBuf buffer) { private static NetworkedTerminal read(FriendlyByteBuf buffer) {
var state = new TerminalState(buffer); return new TerminalState(buffer).create();
assertTrue(state.colour);
if (!state.hasTerminal()) return null;
var other = new NetworkedTerminal(state.width, state.height, true);
state.apply(other);
return other;
} }
} }

View File

@ -23,7 +23,7 @@ public void testEncoder() throws LuaException {
var state = new DfpwmState(); var state = new DfpwmState();
state.pushBuffer(new ObjectLuaTable(inputTbl), input.length, Optional.empty()); state.pushBuffer(new ObjectLuaTable(inputTbl), input.length, Optional.empty());
var result = state.pullPending(0); var result = state.pullPending(0).audio();
var contents = new byte[result.remaining()]; var contents = new byte[result.remaining()];
result.get(contents); result.get(contents);

View File

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.test.core.ArbitraryByteBuffer;
import io.netty.buffer.Unpooled;
import net.jqwik.api.*;
import net.minecraft.network.FriendlyByteBuf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
class EncodedAudioTest {
/**
* Sends the audio on a roundtrip, ensuring that its contents are reassembled on the other end.
*
* @param audio The message to send.
*/
@Property
public void testRoundTrip(@ForAll("audio") EncodedAudio audio) {
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
audio.write(buffer);
var converted = EncodedAudio.read(buffer);
assertEquals(buffer.readableBytes(), 0, "Whole packet was read");
assertThat("Messages are equal", converted, equalTo(converted));
}
@Provide
Arbitrary<EncodedAudio> audio() {
return Combinators.combine(
Arbitraries.integers(),
Arbitraries.integers(),
Arbitraries.of(true, false),
ArbitraryByteBuffer.bytes().ofMaxSize(1000)
).as(EncodedAudio::new);
}
}

View File

@ -11,11 +11,15 @@
import dan200.computercraft.core.computer.ComputerSide import dan200.computercraft.core.computer.ComputerSide
import dan200.computercraft.gametest.api.* import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.getApi import dan200.computercraft.test.core.computer.getApi
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionHand
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.LeverBlock import net.minecraft.world.level.block.LeverBlock
import net.minecraft.world.level.block.RedstoneLampBlock import net.minecraft.world.level.block.RedstoneLampBlock
@ -101,6 +105,17 @@ fun Computer_peripheral(context: GameTestHelper) = context.sequence {
} }
} }
/**
* Check chest peripherals are reattached with a new size.
*/
@GameTest
fun Chest_resizes_on_change(context: GameTestHelper) = context.sequence {
thenOnComputer { callPeripheral("right", "size").assertArrayEquals(27) }
thenExecute { context.placeItemAt(ItemStack(Items.CHEST), BlockPos(2, 2, 2), Direction.WEST) }
thenIdle(1)
thenOnComputer { callPeripheral("right", "size").assertArrayEquals(54) }
}
/** /**
* Check the client can open the computer UI and interact with it. * Check the client can open the computer UI and interact with it.
*/ */

View File

@ -7,18 +7,21 @@
import dan200.computercraft.api.lua.ObjectArguments import dan200.computercraft.api.lua.ObjectArguments
import dan200.computercraft.core.apis.PeripheralAPI import dan200.computercraft.core.apis.PeripheralAPI
import dan200.computercraft.core.computer.ComputerSide import dan200.computercraft.core.computer.ComputerSide
import dan200.computercraft.gametest.api.getBlockEntity import dan200.computercraft.gametest.api.*
import dan200.computercraft.gametest.api.sequence import dan200.computercraft.impl.network.wired.WiredNodeImpl
import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.gametest.api.thenStartComputer
import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock import dan200.computercraft.shared.peripheral.modem.wired.CableBlock
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant
import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.LuaTaskContext import dan200.computercraft.test.core.computer.LuaTaskContext
import dan200.computercraft.test.core.computer.getApi import dan200.computercraft.test.core.computer.getApi
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.block.Blocks
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -83,7 +86,86 @@ fun Full_modems_form_networks(helper: GameTestHelper) = helper.sequence {
thenExecute { thenExecute {
val modem1 = helper.getBlockEntity(BlockPos(1, 2, 1), ModRegistry.BlockEntities.WIRED_MODEM_FULL.get()) val modem1 = helper.getBlockEntity(BlockPos(1, 2, 1), ModRegistry.BlockEntities.WIRED_MODEM_FULL.get())
val modem2 = helper.getBlockEntity(BlockPos(3, 2, 1), ModRegistry.BlockEntities.WIRED_MODEM_FULL.get()) val modem2 = helper.getBlockEntity(BlockPos(3, 2, 1), ModRegistry.BlockEntities.WIRED_MODEM_FULL.get())
assertEquals(modem1.element.node.network, modem2.element.node.network, "On the same network") assertEquals((modem1.element.node as WiredNodeImpl).network, (modem2.element.node as WiredNodeImpl).network, "On the same network")
}
}
/**
* Modems do not include the current peripheral when attached.
*/
@GameTest
fun Cable_modem_does_not_report_self(helper: GameTestHelper) = helper.sequence {
// Modem does not report the computer as a peripheral.
thenOnComputer { assertEquals(listOf("back", "right"), getPeripheralNames()) }
// However, if we connect the network, the other modem does.
thenExecute {
helper.setBlock(
BlockPos(1, 2, 3),
ModRegistry.Blocks.CABLE.get().defaultBlockState().setValue(CableBlock.CABLE, true),
)
}
thenIdle(2)
thenOnComputer { assertEquals(listOf("back", "computer_0", "right"), getPeripheralNames()) }
}
/**
* Modems do not include the current peripheral when attached.
*/
@GameTest
fun Full_block_modem_does_not_report_self(helper: GameTestHelper) = helper.sequence {
// Modem does not report the computer as a peripheral.
thenOnComputer { assertEquals(listOf("back", "right"), getPeripheralNames()) }
// However, if we connect the network, the other modem does.
thenExecute {
helper.setBlock(
BlockPos(1, 2, 3),
ModRegistry.Blocks.CABLE.get().defaultBlockState().setValue(CableBlock.CABLE, true),
)
}
thenIdle(2)
thenOnComputer { assertEquals(listOf("back", "computer_1", "right"), getPeripheralNames()) }
}
/**
* Test wired modems (without a cable) drop an item when the adjacent block is removed.
*/
@GameTest
fun Modem_drops_when_neighbour_removed(helper: GameTestHelper) = helper.sequence {
thenExecute {
helper.setBlock(BlockPos(2, 3, 2), Blocks.AIR)
helper.assertItemEntityPresent(ModRegistry.Items.WIRED_MODEM.get(), BlockPos(2, 2, 2), 0.0)
helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 2))
}
}
/**
* Test wired modems (with a cable) drop an item, but keep their cable when the adjacent block is removed.
*/
@GameTest
fun Modem_keeps_cable_when_neighbour_removed(helper: GameTestHelper) = helper.sequence {
thenExecute {
helper.setBlock(BlockPos(2, 3, 2), Blocks.AIR)
helper.assertItemEntityPresent(ModRegistry.Items.WIRED_MODEM.get(), BlockPos(2, 2, 2), 0.0)
helper.assertBlockIs(BlockPos(2, 2, 2)) {
it.block == ModRegistry.Blocks.CABLE.get() && it.getValue(CableBlock.MODEM) == CableModemVariant.None && it.getValue(CableBlock.CABLE)
}
}
}
/**
* Check chest peripherals are reattached with a new size.
*/
@GameTest
fun Chest_resizes_on_change(context: GameTestHelper) = context.sequence {
thenOnComputer {
callRemotePeripheral("minecraft:chest_0", "size").assertArrayEquals(27)
}
thenExecute { context.placeItemAt(ItemStack(Items.CHEST), BlockPos(2, 2, 2), Direction.WEST) }
thenIdle(1)
thenOnComputer {
callRemotePeripheral("minecraft:chest_0", "size").assertArrayEquals(54)
} }
} }
} }
@ -105,7 +187,7 @@ fun Full_modems_form_networks(helper: GameTestHelper) = helper.sequence {
if (!peripheral.isPresent(side)) continue if (!peripheral.isPresent(side)) continue
peripherals.add(side) peripherals.add(side)
val hasType = peripheral.hasType(side, "modem") val hasType = peripheral.hasType(side, "peripheral_hub")
if (hasType == null || hasType[0] != true) continue if (hasType == null || hasType[0] != true) continue
val names = peripheral.call(context, ObjectArguments(side, "getNamesRemote")).await() ?: continue val names = peripheral.call(context, ObjectArguments(side, "getNamesRemote")).await() ?: continue
@ -116,3 +198,22 @@ fun Full_modems_form_networks(helper: GameTestHelper) = helper.sequence {
peripherals.sort() peripherals.sort()
return peripherals return peripherals
} }
private suspend fun LuaTaskContext.callRemotePeripheral(name: String, method: String, vararg args: Any): Array<out Any?>? {
val peripheral = getApi<PeripheralAPI>()
if (peripheral.isPresent(name)) return peripheral.call(context, ObjectArguments(name, method, *args)).await()
for (side in ComputerSide.NAMES) {
if (!peripheral.isPresent(side)) continue
val hasType = peripheral.hasType(side, "peripheral_hub")
if (hasType == null || hasType[0] != true) continue
val isPresent = peripheral.call(context, ObjectArguments(side, "isPresentRemote", name)).await() ?: continue
if (isPresent[0] as Boolean) {
return peripheral.call(context, ObjectArguments(side, "callRemote", name, method, *args)).await()
}
}
throw IllegalArgumentException("No such peripheral $name")
}

View File

@ -38,10 +38,10 @@ fun Sync_state(context: GameTestHelper) = context.sequence {
// And ensure its synced to the client. // And ensure its synced to the client.
thenIdle(4) thenIdle(4)
thenOnClient { thenOnClient {
val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem) val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem)!!
assertEquals(ComputerState.ON, pocketComputer.state) assertEquals(ComputerState.ON, pocketComputer.state)
val term = pocketComputer.terminal val term = pocketComputer.terminal!!
assertEquals("Hello, world!", term.getLine(0).toString().trim(), "Terminal contents is synced") assertEquals("Hello, world!", term.getLine(0).toString().trim(), "Terminal contents is synced")
} }
// Update the terminal contents again. // Update the terminal contents again.
@ -54,10 +54,10 @@ fun Sync_state(context: GameTestHelper) = context.sequence {
// And ensure the new computer state and terminal are sent. // And ensure the new computer state and terminal are sent.
thenIdle(4) thenIdle(4)
thenOnClient { thenOnClient {
val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem) val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem)!!
assertEquals(ComputerState.BLINKING, pocketComputer.state) assertEquals(ComputerState.BLINKING, pocketComputer.state)
val term = pocketComputer.terminal val term = pocketComputer.terminal!!
assertEquals("Updated text :)", term.getLine(0).toString().trim(), "Terminal contents is synced") assertEquals("Updated text :)", term.getLine(0).toString().trim(), "Terminal contents is synced")
} }
} }

View File

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.gametest.api.thenOnComputer
import dan200.computercraft.gametest.api.tryMultipleTimes
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral
import dan200.computercraft.test.core.assertArrayEquals
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.sounds.SoundEvents
class Speaker_Test {
/**
* [SpeakerPeripheral.playSound] fails if there is already a sound queued.
*/
@GameTest
fun Fails_to_play_multiple_sounds(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString())
.assertArrayEquals(true)
tryMultipleTimes(2) { // We could technically call this a tick later, so try twice
callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString())
.assertArrayEquals(false)
}
}
}
/**
* [SpeakerPeripheral.playSound] will not play records.
*/
@GameTest
fun Will_not_play_record(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
callPeripheral("right", "playSound", SoundEvents.MUSIC_DISC_PIGSTEP.location.toString())
.assertArrayEquals(false)
}
}
}

View File

@ -329,8 +329,6 @@ fun Use_compostors(helper: GameTestHelper) = helper.sequence {
/** /**
* Checks turtles can be cleaned in cauldrons. * Checks turtles can be cleaned in cauldrons.
*
* Currently not required as turtles can no longer right-click cauldrons.
*/ */
@GameTest @GameTest
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence {
@ -643,7 +641,20 @@ fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
} }
} }
// TODO: Turtle sucking from items /**
* `turtle.suck` only pulls for the current side.
*/
@GameTest
fun Sided_suck(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
turtle.suckUp(Optional.empty()).await().assertArrayEquals(true)
turtle.getItemDetail(context, Optional.empty(), Optional.empty()).await().assertArrayEquals(
mapOf("name" to "minecraft:iron_ingot", "count" to 8),
)
turtle.suckUp(Optional.empty()).await().assertArrayEquals(false, "No items to take")
}
}
/** /**
* Render turtles as an item. * Render turtles as an item.

View File

@ -19,15 +19,19 @@
import net.minecraft.gametest.framework.* import net.minecraft.gametest.framework.*
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.EntityType
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BarrelBlockEntity import net.minecraft.world.level.block.entity.BarrelBlockEntity
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.Property import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.hamcrest.StringDescription import org.hamcrest.StringDescription
@ -304,3 +308,29 @@ private fun getName(type: BlockEntityType<*>): ResourceLocation = RegistryWrappe
container.setItem(slot, item) container.setItem(slot, item)
container.setChanged() container.setChanged()
} }
/**
* An alternative version ot [GameTestHelper.placeAt], which sets the player's held item first.
*
* This is required for compatibility with Forge, which uses the in-hand stack, rather than the stack requested.
*/
fun GameTestHelper.placeItemAt(stack: ItemStack, pos: BlockPos, direction: Direction) {
val player = makeMockPlayer()
player.setItemInHand(InteractionHand.MAIN_HAND, stack)
val absolutePos = absolutePos(pos.relative(direction))
val hit = BlockHitResult(Vec3.atCenterOf(absolutePos), direction, absolutePos, false)
stack.useOn(UseOnContext(player, InteractionHand.MAIN_HAND, hit))
}
/**
* Run a function multiple times until it succeeds.
*/
inline fun tryMultipleTimes(count: Int, action: () -> Unit) {
for (remaining in count - 1 downTo 0) {
try {
action()
} catch (e: AssertionError) {
if (remaining == 0) throw e
}
}
}

View File

@ -86,6 +86,7 @@ fun onServerStarted(server: MinecraftServer) {
Printer_Test::class.java, Printer_Test::class.java,
Printout_Test::class.java, Printout_Test::class.java,
Recipe_Test::class.java, Recipe_Test::class.java,
Speaker_Test::class.java,
Turtle_Test::class.java, Turtle_Test::class.java,
) )

View File

@ -0,0 +1,138 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:chest{facing:north,type:single,waterlogged:false}", nbt: {Items: [], id: "minecraft:chest"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "computer_test.chest_resizes_on_change", On: 1b, id: "computercraft:computer_normal"}},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"minecraft:chest{facing:north,type:single,waterlogged:false}",
"computercraft:computer_normal{facing:north,state:on}"
]
}

View File

@ -0,0 +1,139 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:east_off_peripheral,north:false,south:false,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 1b, PeripheralId: 0, PeripheralType: "computer", id: "computercraft:cable"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "modem_test.cable_modem_does_not_report_self", On: 1b, id: "computercraft:computer_normal"}},
{pos: [2, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:north_off,north:true,south:false,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"computercraft:cable{cable:true,down:false,east:true,modem:east_off_peripheral,north:false,south:false,up:false,waterlogged:false,west:false}",
"computercraft:computer_normal{facing:north,state:on}",
"computercraft:cable{cable:true,down:false,east:false,modem:north_off,north:true,south:false,up:false,waterlogged:false,west:false}"
]
}

View File

@ -0,0 +1,139 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:chest{facing:north,type:single,waterlogged:false}", nbt: {Items: [], id: "minecraft:chest"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "modem_test.chest_resizes_on_change", On: 1b, id: "computercraft:computer_normal"}},
{pos: [3, 1, 2], state: "computercraft:wired_modem_full{modem:false,peripheral:true}", nbt: {PeripheralId2: 2, PeripheralId4: 0, PeripheralType2: "computer", PeripheralType4: "minecraft:chest", id: "computercraft:wired_modem_full"}},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"minecraft:chest{facing:north,type:single,waterlogged:false}",
"computercraft:computer_normal{facing:north,state:on}",
"computercraft:wired_modem_full{modem:false,peripheral:true}"
]
}

View File

@ -0,0 +1,139 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:wired_modem_full{modem:false,peripheral:true}", nbt: {PeripheralAccess: 1b, PeripheralId5: 1, PeripheralType5: "computer", id: "computercraft:wired_modem_full"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "modem_test.full_block_modem_does_not_report_self", On: 1b, id: "computercraft:computer_normal"}},
{pos: [2, 1, 3], state: "computercraft:wired_modem_full{modem:false,peripheral:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:wired_modem_full"}},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"computercraft:wired_modem_full{modem:false,peripheral:true}",
"computercraft:computer_normal{facing:north,state:on}",
"computercraft:wired_modem_full{modem:false,peripheral:false}"
]
}

View File

@ -28,7 +28,7 @@
{pos: [4, 0, 3], state: "minecraft:polished_andesite"}, {pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"}, {pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}}, {pos: [0, 1, 0], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}},
{pos: [0, 1, 1], state: "computercraft:cable{cable:true,down:false,east:true,modem:north_on,north:true,south:false,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 1b, PeripheralId: 1, PeripheralType: "printer", id: "computercraft:cable"}}, {pos: [0, 1, 1], state: "computercraft:cable{cable:true,down:false,east:true,modem:north_off_peripheral,north:true,south:false,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 1b, PeripheralId: 1, PeripheralType: "printer", id: "computercraft:cable"}},
{pos: [0, 1, 2], state: "minecraft:air"}, {pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"}, {pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:none}", nbt: {Height: 1, Width: 1, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}}, {pos: [0, 1, 4], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:none}", nbt: {Height: 1, Width: 1, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
@ -36,7 +36,7 @@
{pos: [1, 1, 1], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [1, 1, 1], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [1, 1, 2], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [1, 1, 2], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [1, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [1, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [1, 1, 4], state: "computercraft:cable{cable:true,down:false,east:false,modem:west_on,north:true,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 1b, PeripheralId: 1, PeripheralType: "monitor", id: "computercraft:cable"}}, {pos: [1, 1, 4], state: "computercraft:cable{cable:true,down:false,east:false,modem:west_off_peripheral,north:true,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 1b, PeripheralId: 1, PeripheralType: "monitor", id: "computercraft:cable"}},
{pos: [2, 1, 0], state: "minecraft:air"}, {pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"}, {pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"}, {pos: [2, 1, 2], state: "minecraft:air"},
@ -133,11 +133,11 @@
"minecraft:polished_andesite", "minecraft:polished_andesite",
"minecraft:air", "minecraft:air",
"computercraft:printer{bottom:false,facing:north,top:false}", "computercraft:printer{bottom:false,facing:north,top:false}",
"computercraft:cable{cable:true,down:false,east:true,modem:north_on,north:true,south:false,up:false,waterlogged:false,west:false}", "computercraft:cable{cable:true,down:false,east:true,modem:north_off_peripheral,north:true,south:false,up:false,waterlogged:false,west:false}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:none}", "computercraft:monitor_advanced{facing:north,orientation:north,state:none}",
"computercraft:cable{cable:true,down:false,east:false,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}", "computercraft:cable{cable:true,down:false,east:false,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}",
"computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}",
"computercraft:cable{cable:true,down:false,east:false,modem:west_on,north:true,south:false,up:false,waterlogged:false,west:true}", "computercraft:cable{cable:true,down:false,east:false,modem:west_off_peripheral,north:true,south:false,up:false,waterlogged:false,west:true}",
"computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:false,up:false,waterlogged:false,west:false}", "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:false,up:false,waterlogged:false,west:false}",
"computercraft:computer_advanced{facing:north,state:blinking}", "computercraft:computer_advanced{facing:north,state:blinking}",
"computercraft:cable{cable:true,down:false,east:false,modem:north_off,north:true,south:false,up:false,waterlogged:false,west:true}" "computercraft:cable{cable:true,down:false,east:false,modem:north_off,north:true,south:false,up:false,waterlogged:false,west:true}"

View File

@ -28,7 +28,7 @@
{pos: [4, 0, 3], state: "minecraft:polished_andesite"}, {pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"}, {pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}}, {pos: [0, 1, 0], state: "computercraft:printer{bottom:false,facing:north,top:false}", nbt: {Items: [], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}},
{pos: [0, 1, 1], state: "computercraft:cable{cable:true,down:false,east:false,modem:north_on,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 1b, PeripheralId: 0, PeripheralType: "printer", id: "computercraft:cable"}}, {pos: [0, 1, 1], state: "computercraft:cable{cable:true,down:false,east:false,modem:north_off_peripheral,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 1b, PeripheralId: 0, PeripheralType: "printer", id: "computercraft:cable"}},
{pos: [0, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:true,south:false,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [0, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:true,south:false,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [0, 1, 3], state: "minecraft:air"}, {pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:none}", nbt: {Height: 1, Width: 1, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}}, {pos: [0, 1, 4], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:none}", nbt: {Height: 1, Width: 1, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
@ -36,7 +36,7 @@
{pos: [1, 1, 1], state: "minecraft:air"}, {pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [1, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [1, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [1, 1, 3], state: "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
{pos: [1, 1, 4], state: "computercraft:cable{cable:true,down:false,east:false,modem:west_on,north:true,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 1b, PeripheralId: 0, PeripheralType: "monitor", id: "computercraft:cable"}}, {pos: [1, 1, 4], state: "computercraft:cable{cable:true,down:false,east:false,modem:west_off_peripheral,north:true,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 1b, PeripheralId: 0, PeripheralType: "monitor", id: "computercraft:cable"}},
{pos: [2, 1, 0], state: "minecraft:air"}, {pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"}, {pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}}, {pos: [2, 1, 2], state: "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:false,up:false,waterlogged:false,west:true}", nbt: {PeripheralAccess: 0b, id: "computercraft:cable"}},
@ -133,12 +133,12 @@
"minecraft:polished_andesite", "minecraft:polished_andesite",
"minecraft:air", "minecraft:air",
"computercraft:printer{bottom:false,facing:north,top:false}", "computercraft:printer{bottom:false,facing:north,top:false}",
"computercraft:cable{cable:true,down:false,east:false,modem:north_on,north:true,south:true,up:false,waterlogged:false,west:false}", "computercraft:cable{cable:true,down:false,east:false,modem:north_off_peripheral,north:true,south:true,up:false,waterlogged:false,west:false}",
"computercraft:cable{cable:true,down:false,east:true,modem:none,north:true,south:false,up:false,waterlogged:false,west:false}", "computercraft:cable{cable:true,down:false,east:true,modem:none,north:true,south:false,up:false,waterlogged:false,west:false}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:none}", "computercraft:monitor_advanced{facing:north,orientation:north,state:none}",
"computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}", "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:true,up:false,waterlogged:false,west:true}",
"computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}", "computercraft:cable{cable:true,down:false,east:false,modem:none,north:true,south:true,up:false,waterlogged:false,west:false}",
"computercraft:cable{cable:true,down:false,east:false,modem:west_on,north:true,south:false,up:false,waterlogged:false,west:true}", "computercraft:cable{cable:true,down:false,east:false,modem:west_off_peripheral,north:true,south:false,up:false,waterlogged:false,west:true}",
"computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:false,up:false,waterlogged:false,west:true}", "computercraft:cable{cable:true,down:false,east:true,modem:none,north:false,south:false,up:false,waterlogged:false,west:true}",
"computercraft:cable{cable:true,down:false,east:true,modem:east_off,north:false,south:false,up:false,waterlogged:false,west:true}", "computercraft:cable{cable:true,down:false,east:true,modem:east_off,north:false,south:false,up:false,waterlogged:false,west:true}",
"computercraft:computer_advanced{facing:north,state:blinking}" "computercraft:computer_advanced{facing:north,state:blinking}"

View File

@ -0,0 +1,138 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 1, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 1, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 1, 2], state: "computercraft:cable{cable:false,down:false,east:false,modem:up_off,north:false,south:false,up:false,waterlogged:false,west:false}", nbt: {id: "computercraft:cable"}},
{pos: [2, 1, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 1, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 1, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 2, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 2, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 2, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 2, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 2, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 2, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:light_gray_stained_glass",
"minecraft:air",
"computercraft:cable{cable:false,down:false,east:false,modem:up_off,north:false,south:false,up:false,waterlogged:false,west:false}"
]
}

View File

@ -0,0 +1,138 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 1, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 1, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 1, 2], state: "computercraft:cable{cable:true,down:false,east:false,modem:up_off,north:false,south:false,up:true,waterlogged:false,west:false}", nbt: {id: "computercraft:cable"}},
{pos: [2, 1, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 1, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 1, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 2, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 2, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 2, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 2, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 2, 2], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 2, 3], state: "minecraft:light_gray_stained_glass"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:light_gray_stained_glass",
"minecraft:air",
"computercraft:cable{cable:true,down:false,east:false,modem:up_off,north:false,south:false,up:true,waterlogged:false,west:false}"
]
}

View File

@ -0,0 +1,138 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:speaker{facing:north}", nbt: {id: "computercraft:speaker"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "speaker_test.fails_to_play_multiple_sounds", On: 1b, id: "computercraft:computer_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"computercraft:speaker{facing:north}",
"computercraft:computer_normal{facing:north,state:on}"
]
}

View File

@ -0,0 +1,138 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:speaker{facing:north}", nbt: {id: "computercraft:speaker"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "speaker_test.will_not_play_record", On: 1b, id: "computercraft:computer_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"computercraft:speaker{facing:north}",
"computercraft:computer_normal{facing:north,state:on}"
]
}

View File

@ -0,0 +1,138 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.sided_suck", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:furnace{facing:north,lit:false}", nbt: {BurnTime: 0s, CookTime: 0s, CookTimeTotal: 200s, Items: [{Count: 64b, Slot: 1b, id: "minecraft:coal"}, {Count: 8b, Slot: 2b, id: "minecraft:iron_ingot"}], RecipesUsed: {"minecraft:iron_ingot_from_smelting_raw_iron": 8}, id: "minecraft:furnace"}},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{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:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{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:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:north,waterlogged:false}",
"minecraft:furnace{facing:north,lit:false}"
]
}

View File

@ -143,27 +143,27 @@ public final void queueEvent(String name, IArguments args) throws LuaException {
/** /**
* Starts a timer that will run for the specified number of seconds. Once * Starts a timer that will run for the specified number of seconds. Once
* the timer fires, a {@code timer} event will be added to the queue with * the timer fires, a [`timer`] event will be added to the queue with the ID
* the ID returned from this function as the first parameter. * returned from this function as the first parameter.
* <p> * <p>
* As with [sleep][`os.sleep`], {@code timer} will automatically be rounded up * As with [sleep][`os.sleep`], the time will automatically be rounded up to
* to the nearest multiple of 0.05 seconds, as it waits for a fixed amount * the nearest multiple of 0.05 seconds, as it waits for a fixed amount of
* of world ticks. * world ticks.
* *
* @param timer The number of seconds until the timer fires. * @param time The number of seconds until the timer fires.
* @return The ID of the new timer. This can be used to filter the * @return The ID of the new timer. This can be used to filter the [`timer`]
* {@code timer} event, or {@link #cancelTimer cancel the timer}. * event, or {@linkplain #cancelTimer cancel the timer}.
* @throws LuaException If the time is below zero. * @throws LuaException If the time is below zero.
* @see #cancelTimer To cancel a timer. * @see #cancelTimer To cancel a timer.
*/ */
@LuaFunction @LuaFunction
public final int startTimer(double timer) throws LuaException { public final int startTimer(double time) throws LuaException {
return apiEnvironment.startTimer(Math.round(checkFinite(0, timer) / 0.05)); return apiEnvironment.startTimer(Math.round(checkFinite(0, time) / 0.05));
} }
/** /**
* Cancels a timer previously started with startTimer. This will stop the * Cancels a timer previously started with {@link #startTimer(double)}. This
* timer from firing. * will stop the timer from firing.
* *
* @param token The ID of the timer to cancel. * @param token The ID of the timer to cancel.
* @cc.since 1.6 * @cc.since 1.6
@ -399,10 +399,9 @@ public final long epoch(Optional<String> args) throws LuaException {
* Returns a date string (or table) using a specified format string and * Returns a date string (or table) using a specified format string and
* optional time to format. * optional time to format.
* <p> * <p>
* The format string takes the same formats as C's {@code strftime} function * The format string takes the same formats as C's [strftime](http://www.cplusplus.com/reference/ctime/strftime/)
* (http://www.cplusplus.com/reference/ctime/strftime/). In extension, it * function. The format string can also be prefixed with an exclamation mark
* can be prefixed with an exclamation mark ({@code !}) to use UTC time * ({@code !}) to use UTC time instead of the server's local timezone.
* instead of the server's local timezone.
* <p> * <p>
* If the format is exactly {@code *t} (optionally prefixed with {@code !}), a * If the format is exactly {@code *t} (optionally prefixed with {@code !}), a
* table will be returned instead. This table has fields for the year, month, * table will be returned instead. This table has fields for the year, month,

View File

@ -11,6 +11,7 @@
import dan200.computercraft.api.peripheral.NotAttachedException; import dan200.computercraft.api.peripheral.NotAttachedException;
import dan200.computercraft.api.peripheral.WorkMonitor; import dan200.computercraft.api.peripheral.WorkMonitor;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.GuardedLuaContext;
import dan200.computercraft.core.methods.MethodSupplier; import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.core.methods.PeripheralMethod;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
@ -26,7 +27,7 @@
* @hidden * @hidden
*/ */
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener { public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener {
private class PeripheralWrapper extends ComputerAccess { private class PeripheralWrapper extends ComputerAccess implements GuardedLuaContext.Guard {
private final String side; private final String side;
private final IPeripheral peripheral; private final IPeripheral peripheral;
@ -35,6 +36,8 @@ private class PeripheralWrapper extends ComputerAccess {
private final Map<String, PeripheralMethod> methodMap; private final Map<String, PeripheralMethod> methodMap;
private boolean attached = false; private boolean attached = false;
private @Nullable GuardedLuaContext contextWrapper;
PeripheralWrapper(IPeripheral peripheral, String side) { PeripheralWrapper(IPeripheral peripheral, String side) {
super(environment); super(environment);
this.side = side; this.side = side;
@ -91,9 +94,21 @@ public MethodResult call(ILuaContext context, String methodName, IArguments argu
if (method == null) throw new LuaException("No such method " + methodName); if (method == null) throw new LuaException("No such method " + methodName);
try (var ignored = environment.time(Metrics.PERIPHERAL_OPS)) { // Wrap the ILuaContext. We try to reuse the previous context where possible to avoid allocations - this
return method.apply(peripheral, context, this, arguments); // should be pretty common as ILuaMachine uses a constant context.
var contextWrapper = this.contextWrapper;
if (contextWrapper == null || !contextWrapper.wraps(context)) {
contextWrapper = this.contextWrapper = new GuardedLuaContext(context, this);
} }
try (var ignored = environment.time(Metrics.PERIPHERAL_OPS)) {
return method.apply(peripheral, contextWrapper, this, arguments);
}
}
@Override
public boolean checkValid() {
return isAttached();
} }
// IComputerAccess implementation // IComputerAccess implementation

View File

@ -11,6 +11,7 @@
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.PeripheralHelpers;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ -268,9 +269,7 @@ public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) {
synchronized (peripherals) { synchronized (peripherals) {
var index = side.ordinal(); var index = side.ordinal();
var existing = peripherals[index]; var existing = peripherals[index];
if ((existing == null && peripheral != null) || if (!PeripheralHelpers.equals(existing, peripheral)) {
(existing != null && peripheral == null) ||
(existing != null && !existing.equals(peripheral))) {
peripherals[index] = peripheral; peripherals[index] = peripheral;
if (peripheralListener != null) peripheralListener.onPeripheralChanged(side, peripheral); if (peripheralListener != null) peripheralListener.onPeripheralChanged(side, peripheral);
} }

View File

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaTask;
/**
* A {@link ILuaContext} which checks if context is valid when before executing
* {@linkplain #issueMainThreadTask(LuaTask) main-thread tasks}.
*/
public final class GuardedLuaContext implements ILuaContext {
private final ILuaContext original;
private final Guard guard;
public GuardedLuaContext(ILuaContext original, Guard guard) {
this.original = original;
this.guard = guard;
}
/**
* Determine if this {@link GuardedLuaContext} wraps another context.
* <p>
* This may be used to avoid constructing new guarded contexts, in a pattern something like:
*
* <pre>{@code
* var contextWrapper = this.contextWrapper;
* if(contextWrapper == null || !contextWrapper.wraps(context)) {
* contextWrapper = this.contextWrapper = new GuardedLuaContext(context, this);
* }
* }</pre>
*
* @param context The original context.
* @return Whether {@code this} wraps {@code context}.
*/
public boolean wraps(ILuaContext context) {
return original == context;
}
@Override
public long issueMainThreadTask(LuaTask task) throws LuaException {
return original.issueMainThreadTask(() -> guard.checkValid() ? task.execute() : null);
}
/**
* The function which checks if the context is still valid.
*/
@FunctionalInterface
public interface Guard {
boolean checkValid();
}
}

View File

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.util;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nullable;
/**
* Utilities for working with {@linkplain IPeripheral peripherals}.
*/
public final class PeripheralHelpers {
private PeripheralHelpers() {
}
/**
* Determine if two peripherals are equal. This is equivalent to {@link java.util.Objects#equals(Object, Object)},
* but using {@link IPeripheral#equals(IPeripheral)} instead.
*
* @param a The first peripheral.
* @param b The second peripheral.
* @return If the two peripherals are equal.
*/
public static boolean equals(@Nullable IPeripheral a, @Nullable IPeripheral b) {
return a == b || (a != null && b != null && a.equals(b));
}
}

View File

@ -1,3 +1,24 @@
# New features in CC: Tweaked 1.110.1
Several bug fixes:
* Fix computers not turning on after they're unloaded/not-ticked for a while.
* Fix networking cables sometimes not connecting on Forge.
# New features in CC: Tweaked 1.110.0
* Add a new `@c[...]` syntax for selecting computers in the `/computercraft` command.
* Remove custom breaking progress of modems on Forge.
Several bug fixes:
* Fix client and server DFPWM transcoders getting out of sync.
* Fix `turtle.suck` reporting incorrect error when failing to suck items.
* Fix pocket computers displaying state (blinking, modem light) for the wrong computer.
* Fix crash when wrapping an invalid BE as a generic peripheral.
* Chest peripherals now reattach when a chest is converted into a double chest.
* Fix `speaker` program not resolving files relative to the current directory.
* Skip main-thread tasks if the peripheral is detached.
* Fix internal Lua VM errors if yielding inside `__tostring`.
# New features in CC: Tweaked 1.109.7 # New features in CC: Tweaked 1.109.7
* Improve performance of removing and unloading wired cables/modems. * Improve performance of removing and unloading wired cables/modems.

View File

@ -1,11 +1,7 @@
New features in CC: Tweaked 1.109.7 New features in CC: Tweaked 1.110.1
* Improve performance of removing and unloading wired cables/modems.
Several bug fixes: Several bug fixes:
* Fix monitors sometimes not updating on the client when chunks are unloaded and reloaded. * Fix computers not turning on after they're unloaded/not-ticked for a while.
* `colour.toBlit` correctly errors on out-of-bounds values. * Fix networking cables sometimes not connecting on Forge.
* Round non-standard colours in `window`, like `term.native()` does.
* Fix the client monitor rendering both the current and outdated contents.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@ -89,7 +89,7 @@ end
-- --
-- @tparam table image An image, as returned from [`load`] or [`parse`]. -- @tparam table image An image, as returned from [`load`] or [`parse`].
-- @tparam number xPos The x position to start drawing at. -- @tparam number xPos The x position to start drawing at.
-- @tparam number xPos The y position to start drawing at. -- @tparam number yPos The y position to start drawing at.
-- @tparam[opt] term.Redirect target The terminal redirect to draw to. Defaults to the -- @tparam[opt] term.Redirect target The terminal redirect to draw to. Defaults to the
-- current terminal. -- current terminal.
local function draw(image, xPos, yPos, target) local function draw(image, xPos, yPos, target)

View File

@ -43,6 +43,10 @@ if cmd == "stop" then
for _, speaker in pairs(get_speakers(name)) do speaker.stop() end for _, speaker in pairs(get_speakers(name)) do speaker.stop() end
elseif cmd == "play" then elseif cmd == "play" then
local _, file, name = ... local _, file, name = ...
if not file then
error("Usage: speaker play <file or url> [speaker]", 0)
end
local speaker = get_speakers(name)[1] local speaker = get_speakers(name)[1]
if not file then if not file then
@ -54,7 +58,7 @@ elseif cmd == "play" then
print("Downloading...") print("Downloading...")
handle, err = http.get(file) handle, err = http.get(file)
else else
handle, err = fs.open(file, "r") handle, err = fs.open(shell.resolve(file), "r")
end end
if not handle then if not handle then

View File

@ -27,6 +27,7 @@
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
@ -45,7 +46,7 @@ public static void init() {
ClientRegistry.register(); ClientRegistry.register();
ClientRegistry.registerTurtleModellers(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller); ClientRegistry.registerTurtleModellers(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller);
ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register); ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register);
ClientRegistry.registerMainThread(); ClientRegistry.registerMainThread(ItemProperties::register);
PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> { PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> {
ClientRegistry.registerExtraModels(context::addModels); ClientRegistry.registerExtraModels(context::addModels);

View File

@ -13,6 +13,8 @@
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage; import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedSlottedStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage; import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
@ -42,6 +44,34 @@ public final class InventoryMethods extends AbstractInventoryMethods<InventoryMe
* @param storage The underlying storage * @param storage The underlying storage
*/ */
public record StorageWrapper(SlottedStorage<ItemVariant> storage) { public record StorageWrapper(SlottedStorage<ItemVariant> storage) {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof StorageWrapper other)) return false;
var otherStorage = other.storage;
/*
Equality for inventory storage isn't really defined, and most of the time falls back to reference
equality.
- Vanilla inventories are exposed via InventoryStorage - the creation of this is cached, so will be
the same object.
- Double chests are combined into a CombinedSlottedStorage. We check the parts are equal.
*/
if (
storage instanceof CombinedSlottedStorage<?, ?> cs && storage.getClass() == otherStorage.getClass()
&& cs.parts.equals(((CombinedStorage<?, ?>) otherStorage).parts)
) {
return true;
}
return storage.equals(otherStorage);
}
@Override
public int hashCode() {
return storage instanceof CombinedSlottedStorage<?, ?> cs ? cs.parts.hashCode() : storage.hashCode();
}
} }
@Override @Override

View File

@ -9,12 +9,11 @@
import net.fabricmc.fabric.api.transfer.v1.storage.Storage; import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil; import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView; import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
import javax.annotation.Nullable;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.function.Predicate;
@SuppressWarnings("UnstableApiUsage") @SuppressWarnings("UnstableApiUsage")
public class FabricContainerTransfer implements ContainerTransfer { public class FabricContainerTransfer implements ContainerTransfer {
@ -34,37 +33,31 @@ public static ContainerTransfer.Slotted of(SlottedStorage<ItemVariant> storage)
@Override @Override
public int moveTo(ContainerTransfer destination, int maxAmount) { public int moveTo(ContainerTransfer destination, int maxAmount) {
var predicate = new GatePredicate<ItemVariant>(); var hasItem = false;
var moved = StorageUtil.move(storage, ((FabricContainerTransfer) destination).storage, predicate, maxAmount, null); var destStorage = ((FabricContainerTransfer) destination).storage;
if (moved > 0) return moved > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) moved; for (var slot : storage.nonEmptyViews()) {
var resource = slot.getResource();
// Nasty hack here to check if move() actually found an item in the original inventory. Saves having to try (var transaction = Transaction.openOuter()) {
// iterate over the source twice. // Check how much can be extracted and inserted.
return predicate.hasItem() ? NO_SPACE : NO_ITEMS; var maxExtracted = StorageUtil.simulateExtract(slot, resource, maxAmount, transaction);
} if (maxExtracted == 0) continue;
/** hasItem = true;
* A predicate which accepts the first value it sees, and then only those matching that value.
*
* @param <T> The type of the object to accept.
*/
private static final class GatePredicate<T> implements Predicate<T> {
private @Nullable T instance = null;
@Override var accepted = destStorage.insert(resource, maxExtracted, transaction);
public boolean test(T o) { if (accepted == 0) continue;
if (instance == null) {
instance = o; // Extract or rollback.
return true; if (slot.extract(resource, accepted, transaction) == accepted) {
transaction.commit();
return (int) accepted;
}
} }
return instance.equals(o);
} }
boolean hasItem() { return hasItem ? NO_SPACE : NO_ITEMS;
return instance != null;
}
} }
private static final class SlottedImpl extends FabricContainerTransfer implements ContainerTransfer.Slotted { private static final class SlottedImpl extends FabricContainerTransfer implements ContainerTransfer.Slotted {

View File

@ -8,7 +8,6 @@ import net.minecraftforge.gradle.common.util.RunConfig
plugins { plugins {
id("cc-tweaked.forge") id("cc-tweaked.forge")
id("cc-tweaked.gametest") id("cc-tweaked.gametest")
alias(libs.plugins.mixinGradle)
id("cc-tweaked.mod-publishing") id("cc-tweaked.mod-publishing")
} }
@ -103,12 +102,6 @@ minecraft {
} }
} }
mixin {
add(sourceSets.client.get(), "client-computercraft.refmap.json")
config("computercraft-client.forge.mixins.json")
}
configurations { configurations {
minecraftLibrary { extendsFrom(minecraftEmbed.get()) } minecraftLibrary { extendsFrom(minecraftEmbed.get()) }
@ -121,9 +114,6 @@ configurations {
} }
dependencies { dependencies {
annotationProcessor("org.spongepowered:mixin:0.8.5-SQUID:processor")
clientAnnotationProcessor("org.spongepowered:mixin:0.8.5-SQUID:processor")
compileOnly(libs.jetbrainsAnnotations) compileOnly(libs.jetbrainsAnnotations)
annotationProcessorEverywhere(libs.autoService) annotationProcessorEverywhere(libs.autoService)
@ -246,12 +236,6 @@ modPublishing {
output.set(tasks.jarJar) output.set(tasks.jarJar)
} }
// Make sure configureReobfTaskForReobfJarJar runs after compilation
// see - https://github.com/SpongePowered/MixinGradle/pull/51
tasks.configureEach {
if (name == "configureReobfTaskForReobfJarJar") mustRunAfter(tasks.jarJar)
}
// Don't publish the slim jar // Don't publish the slim jar
for (cfg in listOf(configurations.apiElements, configurations.runtimeElements)) { for (cfg in listOf(configurations.apiElements, configurations.runtimeElements)) {
cfg.configure { artifacts.removeIf { it.classifier == "slim" } } cfg.configure { artifacts.removeIf { it.classifier == "slim" } }

View File

@ -8,6 +8,7 @@
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent; import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
import dan200.computercraft.client.model.turtle.TurtleModelLoader; import dan200.computercraft.client.model.turtle.TurtleModelLoader;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ModelEvent; import net.minecraftforge.client.event.ModelEvent;
import net.minecraftforge.client.event.RegisterClientReloadListenersEvent; import net.minecraftforge.client.event.RegisterClientReloadListenersEvent;
@ -82,6 +83,6 @@ public static void registerReloadListeners(RegisterClientReloadListenersEvent ev
@SubscribeEvent @SubscribeEvent
public static void setupClient(FMLClientSetupEvent event) { public static void setupClient(FMLClientSetupEvent event) {
ClientRegistry.register(); ClientRegistry.register();
event.enqueueWork(ClientRegistry::registerMainThread); event.enqueueWork(() -> ClientRegistry.registerMainThread(ItemProperties::register));
} }
} }

View File

@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.client.ClientHooks;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.renderer.block.ModelBlockRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.ModelData;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Provides custom block breaking progress for modems, so it only applies to the current part.
*
* @see BlockRenderDispatcher#renderBreakingTexture(BlockState, BlockPos, BlockAndTintGetter, PoseStack, VertexConsumer, ModelData)
*/
@Mixin(BlockRenderDispatcher.class)
public class BlockRenderDispatcherMixin {
@Shadow
@Final
private RandomSource random;
@Shadow
@Final
private BlockModelShaper blockModelShaper;
@Shadow
@Final
private ModelBlockRenderer modelRenderer;
@Inject(
method = "name=/^renderBreakingTexture/ desc=/ModelData;\\)V$/",
at = @At("HEAD"),
cancellable = true,
require = 0 // This isn't critical functionality, so don't worry if we can't apply it.
)
public void renderBlockDamage(
BlockState state, BlockPos pos, BlockAndTintGetter world, PoseStack pose, VertexConsumer buffers, ModelData modelData,
CallbackInfo info
) {
var newState = ClientHooks.getBlockBreakingState(state, pos);
if (newState != null) {
info.cancel();
var model = blockModelShaper.getBlockModel(newState);
modelRenderer.tesselateBlock(
world, model, newState, pos, pose, buffers, true, random, newState.getSeed(pos),
OverlayTexture.NO_OVERLAY, modelData, null
);
}
}
}

View File

@ -1,26 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import dan200.computercraft.shared.command.CommandComputerCraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraftforge.client.ClientCommandHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Allows triggering ComputerCraft's client commands from chat components events.
*/
@Mixin(ClientPacketListener.class)
class ClientPacketListenerMixin {
@Inject(method = "sendUnsignedCommand", at = @At("HEAD"), cancellable = true)
void commandUnsigned(String command, CallbackInfoReturnable<Boolean> ci) {
if (command.startsWith(CommandComputerCraft.CLIENT_OPEN_FOLDER) && ClientCommandHandler.runCommand(command)) {
ci.setReturnValue(true);
}
}
}

View File

@ -1,14 +0,0 @@
{
"required": true,
"package": "dan200.computercraft.mixin.client",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_17",
"injectors": {
"defaultRequire": 1
},
"client": [
"BlockRenderDispatcherMixin",
"ClientPacketListenerMixin"
],
"refmap": "client-computercraft.refmap.json"
}

View File

@ -8,7 +8,9 @@
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.platform.RegistryWrappers; import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.capabilities.ICapabilityProvider;
@ -53,7 +55,7 @@ public int pushFluid(
var location = computer.getAvailablePeripheral(toName); var location = computer.getAvailablePeripheral(toName);
if (location == null) throw new LuaException("Target '" + toName + "' does not exist"); if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
var to = extractHandler(location.getTarget()); var to = extractHandler(location);
if (to == null) throw new LuaException("Target '" + toName + "' is not an tank"); if (to == null) throw new LuaException("Target '" + toName + "' is not an tank");
int actualLimit = limit.orElse(Integer.MAX_VALUE); int actualLimit = limit.orElse(Integer.MAX_VALUE);
@ -78,7 +80,7 @@ public int pullFluid(
var location = computer.getAvailablePeripheral(fromName); var location = computer.getAvailablePeripheral(fromName);
if (location == null) throw new LuaException("Target '" + fromName + "' does not exist"); if (location == null) throw new LuaException("Target '" + fromName + "' does not exist");
var from = extractHandler(location.getTarget()); var from = extractHandler(location);
if (from == null) throw new LuaException("Target '" + fromName + "' is not an tank"); if (from == null) throw new LuaException("Target '" + fromName + "' is not an tank");
int actualLimit = limit.orElse(Integer.MAX_VALUE); int actualLimit = limit.orElse(Integer.MAX_VALUE);
@ -90,11 +92,14 @@ public int pullFluid(
} }
@Nullable @Nullable
private static IFluidHandler extractHandler(@Nullable Object object) { private static IFluidHandler extractHandler(IPeripheral peripheral) {
var object = peripheral.getTarget();
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
if (object instanceof BlockEntity blockEntity && blockEntity.isRemoved()) return null; if (object instanceof BlockEntity blockEntity && blockEntity.isRemoved()) return null;
if (object instanceof ICapabilityProvider provider) { if (object instanceof ICapabilityProvider provider) {
var cap = provider.getCapability(ForgeCapabilities.FLUID_HANDLER); var cap = CapabilityUtil.getCapability(provider, ForgeCapabilities.FLUID_HANDLER, direction);
if (cap.isPresent()) return cap.orElseThrow(NullPointerException::new); if (cap.isPresent()) return cap.orElseThrow(NullPointerException::new);
} }

View File

@ -8,7 +8,9 @@
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.platform.ForgeContainerTransfer; import dan200.computercraft.shared.platform.ForgeContainerTransfer;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.common.capabilities.ForgeCapabilities;
@ -73,7 +75,7 @@ public int pushItems(
var location = computer.getAvailablePeripheral(toName); var location = computer.getAvailablePeripheral(toName);
if (location == null) throw new LuaException("Target '" + toName + "' does not exist"); if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
var to = extractHandler(location.getTarget()); var to = extractHandler(location);
if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory"); if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory");
// Validate slots // Validate slots
@ -95,7 +97,7 @@ public int pullItems(
var location = computer.getAvailablePeripheral(fromName); var location = computer.getAvailablePeripheral(fromName);
if (location == null) throw new LuaException("Source '" + fromName + "' does not exist"); if (location == null) throw new LuaException("Source '" + fromName + "' does not exist");
var from = extractHandler(location.getTarget()); var from = extractHandler(location);
if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory"); if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory");
// Validate slots // Validate slots
@ -108,11 +110,14 @@ public int pullItems(
} }
@Nullable @Nullable
private static IItemHandler extractHandler(@Nullable Object object) { private static IItemHandler extractHandler(IPeripheral peripheral) {
var object = peripheral.getTarget();
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
if (object instanceof BlockEntity blockEntity && blockEntity.isRemoved()) return null; if (object instanceof BlockEntity blockEntity && blockEntity.isRemoved()) return null;
if (object instanceof ICapabilityProvider provider) { if (object instanceof ICapabilityProvider provider) {
var cap = provider.getCapability(ForgeCapabilities.ITEM_HANDLER); var cap = CapabilityUtil.getCapability(provider, ForgeCapabilities.ITEM_HANDLER, direction);
if (cap.isPresent()) return cap.orElseThrow(NullPointerException::new); if (cap.isPresent()) return cap.orElseThrow(NullPointerException::new);
} }

View File

@ -345,6 +345,11 @@ public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitRes
return event.getUseItem() == Event.Result.DENY ? InteractionResult.PASS : stack.useOn(context); return event.getUseItem() == Event.Result.DENY ? InteractionResult.PASS : stack.useOn(context);
} }
@Override
public boolean canClickRunClientCommand() {
return false;
}
private record RegistryWrapperImpl<T>( private record RegistryWrapperImpl<T>(
ResourceLocation name, ForgeRegistry<T> registry ResourceLocation name, ForgeRegistry<T> registry
) implements RegistryWrappers.RegistryWrapper<T> { ) implements RegistryWrappers.RegistryWrapper<T> {

View File

@ -50,8 +50,8 @@ public static <T> T unwrap(LazyOptional<T> p, InvalidateCallback invalidate) {
* @param <T> The type of the underlying capability. * @param <T> The type of the underlying capability.
* @return The extracted capability, if present. * @return The extracted capability, if present.
*/ */
public static <T> LazyOptional<T> getCapability(ICapabilityProvider provider, Capability<T> capability, Direction side) { public static <T> LazyOptional<T> getCapability(ICapabilityProvider provider, Capability<T> capability, @Nullable Direction side) {
var cap = provider.getCapability(capability); var cap = provider.getCapability(capability);
return cap.isPresent() ? cap : provider.getCapability(capability, side); return !cap.isPresent() && side != null ? provider.getCapability(capability, side) : cap;
} }
} }

View File

@ -105,7 +105,6 @@ private fun fromAnnotationStream(annotations: Stream<out AnnotationMirror>) =
companion object { companion object {
private val notClientPackages = listOf( private val notClientPackages = listOf(
// Ugly! But we do what we must. // Ugly! But we do what we must.
"net.fabricmc.fabric.api.client.itemgroup",
"dan200.computercraft.shared.network.client", "dan200.computercraft.shared.network.client",
) )

View File

@ -78,11 +78,11 @@ public class JavascriptConv {
* @return The wrapped array. * @return The wrapped array.
*/ */
public static byte[] asByteArray(ArrayBuffer view) { public static byte[] asByteArray(ArrayBuffer view) {
return asByteArray(Int8Array.create(view)); return asByteArray(new Int8Array(view));
} }
public static Int8Array toArray(ByteBuffer buffer) { public static Int8Array toArray(ByteBuffer buffer) {
var array = Int8Array.create(buffer.remaining()); var array = new Int8Array(buffer.remaining());
for (var i = 0; i < array.getLength(); i++) array.set(i, buffer.get(i)); for (var i = 0; i < array.getLength(); i++) array.set(i, buffer.get(i));
return array; return array;
} }

View File

@ -53,11 +53,13 @@ public void tick() {
} }
@LuaFunction @LuaFunction
@SuppressWarnings("DoNotCallSuggester")
public final boolean playNote(String instrumentA, Optional<Double> volumeA, Optional<Double> pitchA) throws LuaException { public final boolean playNote(String instrumentA, Optional<Double> volumeA, Optional<Double> pitchA) throws LuaException {
throw new LuaException("Cannot play notes outside of Minecraft"); throw new LuaException("Cannot play notes outside of Minecraft");
} }
@LuaFunction @LuaFunction
@SuppressWarnings("DoNotCallSuggester")
public final boolean playSound(String name, Optional<Double> volumeA, Optional<Double> pitchA) throws LuaException { public final boolean playSound(String name, Optional<Double> volumeA, Optional<Double> pitchA) throws LuaException {
throw new LuaException("Cannot play sounds outside of Minecraft"); throw new LuaException("Cannot play sounds outside of Minecraft");
} }
@ -70,7 +72,7 @@ public final boolean playAudio(LuaTable<?, ?> audio, Optional<Double> volume) th
if (length <= 0) throw new LuaException("Cannot play empty audio"); if (length <= 0) throw new LuaException("Cannot play empty audio");
if (length > 128 * 1024) throw new LuaException("Audio data is too large"); if (length > 128 * 1024) throw new LuaException("Audio data is too large");
if (audioContext == null) audioContext = AudioContext.create(); if (audioContext == null) audioContext = new AudioContext();
if (state == null || !state.isPlaying()) state = new AudioState(audioContext); if (state == null || !state.isPlaying()) state = new AudioState(audioContext);
return state.pushBuffer(audio, length, volume); return state.pushBuffer(audio, length, volume);

View File

@ -92,7 +92,7 @@ public void request(URI uri, HttpMethod method) {
if (isClosed()) return; if (isClosed()) return;
try { try {
var request = XMLHttpRequest.create(); var request = new XMLHttpRequest();
request.setOnReadyStateChange(() -> onResponseStateChange(request)); request.setOnReadyStateChange(() -> onResponseStateChange(request));
request.setResponseType("arraybuffer"); request.setResponseType("arraybuffer");
var address = uri.toASCIIString(); var address = uri.toASCIIString();

Some files were not shown because too many files have changed in this diff Show More