diff --git a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
index 25960c580..1f6ffe08a 100644
--- a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
+++ b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
@@ -62,6 +62,7 @@ interface IPeripheralChangeListener
@Nullable
IPeripheral getPeripheral( ComputerSide side );
+ @Nullable
String getLabel();
void setLabel( @Nullable String label );
diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java
index 3e54be9a1..155540560 100644
--- a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java
+++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java
@@ -28,7 +28,6 @@
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
-import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import java.lang.ref.WeakReference;
@@ -160,7 +159,7 @@ protected void initChannel( SocketChannel ch )
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator( 8192 ),
- WebSocketClientCompressionHandler.INSTANCE,
+ WebsocketCompressionHandler.INSTANCE,
new WebsocketHandler( Websocket.this, handshaker, options )
);
}
diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java
new file mode 100644
index 000000000..e29a87837
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+package dan200.computercraft.core.apis.http.websocket;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.handler.codec.compression.ZlibCodecFactory;
+import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler;
+import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameClientExtensionHandshaker;
+import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateClientExtensionHandshaker;
+import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
+
+import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE;
+
+/**
+ * An alternative to {@link WebSocketClientCompressionHandler} which supports the {@literal client_no_context_takeover}
+ * extension. Makes CC slightly more flexible.
+ */
+@ChannelHandler.Sharable
+final class WebsocketCompressionHandler extends WebSocketClientExtensionHandler
+{
+ public static final WebsocketCompressionHandler INSTANCE = new WebsocketCompressionHandler();
+
+ private WebsocketCompressionHandler()
+ {
+ super(
+ new PerMessageDeflateClientExtensionHandshaker(
+ 6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE,
+ true, false
+ ),
+ new DeflateFrameClientExtensionHandshaker( false ),
+ new DeflateFrameClientExtensionHandshaker( true )
+ );
+
+ }
+}
diff --git a/src/main/java/dan200/computercraft/shared/command/ClientCommands.java b/src/main/java/dan200/computercraft/shared/command/ClientCommands.java
index c16b3fdd1..0477c46ad 100644
--- a/src/main/java/dan200/computercraft/shared/command/ClientCommands.java
+++ b/src/main/java/dan200/computercraft/shared/command/ClientCommands.java
@@ -7,11 +7,13 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.util.IDAssigner;
+import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Util;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.server.ServerLifecycleHooks;
import java.io.File;
@@ -35,6 +37,9 @@ public static void onClientSendMessage( ClientChatEvent event )
// Emulate the command on the client side
if( event.getMessage().startsWith( OPEN_COMPUTER ) )
{
+ MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
+ if( server == null || server.isDedicatedServer() ) return;
+
event.setCanceled( true );
String idStr = event.getMessage().substring( OPEN_COMPUTER.length() ).trim();
diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java
index dae1bf134..b10ef2f57 100644
--- a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java
+++ b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java
@@ -21,7 +21,6 @@
import net.minecraftforge.fml.network.NetworkRegistry;
import net.minecraftforge.fml.network.PacketDistributor;
import net.minecraftforge.fml.network.simple.SimpleChannel;
-import net.minecraftforge.fml.server.ServerLifecycleHooks;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -71,10 +70,7 @@ public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
public static void sendToAllPlayers( NetworkMessage packet )
{
- for( ServerPlayerEntity player : ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers() )
- {
- sendToPlayer( player, packet );
- }
+ network.send( PacketDistributor.ALL.noArg(), packet );
}
public static void sendToServer( NetworkMessage packet )
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java
new file mode 100644
index 000000000..791c425ad
--- /dev/null
+++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of ComputerCraft - http://www.computercraft.info
+ * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
+ * Send enquiries to dratcliffe@gmail.com
+ */
+package dan200.computercraft.shared.peripheral.monitor;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+final class MonitorState
+{
+ public static final MonitorState UNLOADED = new MonitorState( State.UNLOADED, null );
+ public static final MonitorState MISSING = new MonitorState( State.MISSING, null );
+
+ private final State state;
+ private final TileMonitor monitor;
+
+ private MonitorState( @Nonnull State state, @Nullable TileMonitor monitor )
+ {
+ this.state = state;
+ this.monitor = monitor;
+ }
+
+ public static MonitorState present( @Nonnull TileMonitor monitor )
+ {
+ return new MonitorState( State.PRESENT, monitor );
+ }
+
+ public boolean isPresent()
+ {
+ return state == State.PRESENT;
+ }
+
+ public boolean isMissing()
+ {
+ return state == State.MISSING;
+ }
+
+ @Nullable
+ public TileMonitor getMonitor()
+ {
+ return monitor;
+ }
+
+ enum State
+ {
+ UNLOADED,
+ MISSING,
+ PRESENT,
+ }
+}
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
index 4dc64a173..74e7c9926 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
@@ -56,6 +56,7 @@ public class TileMonitor extends TileGeneric
private final Set computers = new HashSet<>();
private boolean needsUpdate = false;
+ private boolean needsValidating = false;
private boolean destroyed = false;
private boolean visiting = false;
@@ -78,6 +79,7 @@ public TileMonitor( TileEntityType extends TileMonitor> type, boolean advanced
public void onLoad()
{
super.onLoad();
+ needsValidating = true;
TickScheduler.schedule( this );
}
@@ -149,6 +151,12 @@ public void load( @Nonnull BlockState state, @Nonnull CompoundNBT nbt )
@Override
public void blockTick()
{
+ if( needsValidating )
+ {
+ needsValidating = false;
+ validate();
+ }
+
if( needsUpdate )
{
needsUpdate = false;
@@ -165,7 +173,7 @@ public void blockTick()
{
for( int y = 0; y < height; y++ )
{
- TileMonitor monitor = getNeighbour( x, y );
+ TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor == null ) continue;
for( IComputerAccess computer : monitor.computers )
@@ -209,7 +217,7 @@ private ServerMonitor getServerMonitor()
{
if( serverMonitor != null ) return serverMonitor;
- TileMonitor origin = getOrigin();
+ TileMonitor origin = getOrigin().getMonitor();
if( origin == null ) return null;
return serverMonitor = origin.serverMonitor;
@@ -230,7 +238,7 @@ private ServerMonitor createServerMonitor()
{
for( int y = 0; y < height; y++ )
{
- TileMonitor monitor = getNeighbour( x, y );
+ TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor != null ) monitor.serverMonitor = serverMonitor;
}
}
@@ -375,24 +383,24 @@ public int getYIndex()
return yIndex;
}
- private TileMonitor getSimilarMonitorAt( BlockPos pos )
+ @Nonnull
+ private MonitorState getSimilarMonitorAt( BlockPos pos )
{
- if( pos.equals( getBlockPos() ) ) return this;
+ if( pos.equals( getBlockPos() ) ) return MonitorState.present( this );
- int y = pos.getY();
World world = getLevel();
- if( world == null || !world.isAreaLoaded( pos, 0 ) ) return null;
+ if( world == null || !world.isAreaLoaded( pos, 0 ) ) return MonitorState.UNLOADED;
TileEntity tile = world.getBlockEntity( pos );
- if( !(tile instanceof TileMonitor) ) return null;
+ if( !(tile instanceof TileMonitor) ) return MonitorState.MISSING;
TileMonitor monitor = (TileMonitor) tile;
return !monitor.visiting && !monitor.destroyed && advanced == monitor.advanced
&& getDirection() == monitor.getDirection() && getOrientation() == monitor.getOrientation()
- ? monitor : null;
+ ? MonitorState.present( monitor ) : MonitorState.MISSING;
}
- private TileMonitor getNeighbour( int x, int y )
+ private MonitorState getNeighbour( int x, int y )
{
BlockPos pos = getBlockPos();
Direction right = getRight();
@@ -402,7 +410,7 @@ private TileMonitor getNeighbour( int x, int y )
return getSimilarMonitorAt( pos.relative( right, xOffset ).relative( down, yOffset ) );
}
- private TileMonitor getOrigin()
+ private MonitorState getOrigin()
{
return getNeighbour( 0, 0 );
}
@@ -426,7 +434,7 @@ private void resize( int width, int height )
{
for( int y = 0; y < height; y++ )
{
- TileMonitor monitor = getNeighbour( x, y );
+ TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor != null && monitor.peripheral != null )
{
needsTerminal = true;
@@ -454,7 +462,7 @@ private void resize( int width, int height )
{
for( int y = 0; y < height; y++ )
{
- TileMonitor monitor = getNeighbour( x, y );
+ TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor == null ) continue;
monitor.xIndex = x;
@@ -470,13 +478,13 @@ private void resize( int width, int height )
private boolean mergeLeft()
{
- TileMonitor left = getNeighbour( -1, 0 );
+ TileMonitor left = getNeighbour( -1, 0 ).getMonitor();
if( left == null || left.yIndex != 0 || left.height != height ) return false;
int width = left.width + this.width;
if( width > ComputerCraft.monitorWidth ) return false;
- TileMonitor origin = left.getOrigin();
+ TileMonitor origin = left.getOrigin().getMonitor();
if( origin != null ) origin.resize( width, height );
left.expand();
return true;
@@ -484,13 +492,13 @@ private boolean mergeLeft()
private boolean mergeRight()
{
- TileMonitor right = getNeighbour( width, 0 );
+ TileMonitor right = getNeighbour( width, 0 ).getMonitor();
if( right == null || right.yIndex != 0 || right.height != height ) return false;
int width = this.width + right.width;
if( width > ComputerCraft.monitorWidth ) return false;
- TileMonitor origin = getOrigin();
+ TileMonitor origin = getOrigin().getMonitor();
if( origin != null ) origin.resize( width, height );
expand();
return true;
@@ -498,13 +506,13 @@ private boolean mergeRight()
private boolean mergeUp()
{
- TileMonitor above = getNeighbour( 0, height );
+ TileMonitor above = getNeighbour( 0, height ).getMonitor();
if( above == null || above.xIndex != 0 || above.width != width ) return false;
int height = above.height + this.height;
if( height > ComputerCraft.monitorHeight ) return false;
- TileMonitor origin = getOrigin();
+ TileMonitor origin = getOrigin().getMonitor();
if( origin != null ) origin.resize( width, height );
expand();
return true;
@@ -512,13 +520,13 @@ private boolean mergeUp()
private boolean mergeDown()
{
- TileMonitor below = getNeighbour( 0, -1 );
+ TileMonitor below = getNeighbour( 0, -1 ).getMonitor();
if( below == null || below.xIndex != 0 || below.width != width ) return false;
int height = this.height + below.height;
if( height > ComputerCraft.monitorHeight ) return false;
- TileMonitor origin = below.getOrigin();
+ TileMonitor origin = below.getOrigin().getMonitor();
if( origin != null ) origin.resize( width, height );
below.expand();
return true;
@@ -547,22 +555,22 @@ void contractNeighbours()
visiting = true;
if( xIndex > 0 )
{
- TileMonitor left = getNeighbour( xIndex - 1, yIndex );
+ TileMonitor left = getNeighbour( xIndex - 1, yIndex ).getMonitor();
if( left != null ) left.contract();
}
if( xIndex + 1 < width )
{
- TileMonitor right = getNeighbour( xIndex + 1, yIndex );
+ TileMonitor right = getNeighbour( xIndex + 1, yIndex ).getMonitor();
if( right != null ) right.contract();
}
if( yIndex > 0 )
{
- TileMonitor below = getNeighbour( xIndex, yIndex - 1 );
+ TileMonitor below = getNeighbour( xIndex, yIndex - 1 ).getMonitor();
if( below != null ) below.contract();
}
if( yIndex + 1 < height )
{
- TileMonitor above = getNeighbour( xIndex, yIndex + 1 );
+ TileMonitor above = getNeighbour( xIndex, yIndex + 1 ).getMonitor();
if( above != null ) above.contract();
}
visiting = false;
@@ -573,11 +581,11 @@ void contract()
int height = this.height;
int width = this.width;
- TileMonitor origin = getOrigin();
+ TileMonitor origin = getOrigin().getMonitor();
if( origin == null )
{
- TileMonitor right = width > 1 ? getNeighbour( 1, 0 ) : null;
- TileMonitor below = height > 1 ? getNeighbour( 0, 1 ) : null;
+ TileMonitor right = width > 1 ? getNeighbour( 1, 0 ).getMonitor() : null;
+ TileMonitor below = height > 1 ? getNeighbour( 0, 1 ).getMonitor() : null;
if( right != null ) right.resize( width - 1, 1 );
if( below != null ) below.resize( width, height - 1 );
@@ -591,7 +599,7 @@ void contract()
{
for( int x = 0; x < width; x++ )
{
- TileMonitor monitor = origin.getNeighbour( x, y );
+ TileMonitor monitor = origin.getNeighbour( x, y ).getMonitor();
if( monitor != null ) continue;
// Decompose
@@ -607,17 +615,17 @@ void contract()
}
if( x > 0 )
{
- left = origin.getNeighbour( 0, y );
+ left = origin.getNeighbour( 0, y ).getMonitor();
left.resize( x, 1 );
}
if( x + 1 < width )
{
- right = origin.getNeighbour( x + 1, y );
+ right = origin.getNeighbour( x + 1, y ).getMonitor();
right.resize( width - (x + 1), 1 );
}
if( y + 1 < height )
{
- below = origin.getNeighbour( 0, y + 1 );
+ below = origin.getNeighbour( 0, y + 1 ).getMonitor();
below.resize( width, height - (y + 1) );
}
@@ -631,6 +639,38 @@ void contract()
}
}
+ private boolean checkMonitorAt( int xIndex, int yIndex )
+ {
+ BlockPos pos = getBlockPos();
+ Direction right = getRight();
+ Direction down = getDown();
+
+ MonitorState state = getSimilarMonitorAt( pos.relative( right, xIndex ).relative( down, yIndex ) );
+
+ if( state.isMissing() ) return false;
+
+ TileMonitor monitor = state.getMonitor();
+ if( monitor == null ) return true;
+
+ return monitor.xIndex == xIndex && monitor.yIndex == yIndex && monitor.width == width && monitor.height == height;
+ }
+
+ private void validate()
+ {
+ if( xIndex == 0 && yIndex == 0 && width == 1 || height == 1 ) return;
+
+ if( checkMonitorAt( 0, 0 ) && checkMonitorAt( 0, height - 1 ) &&
+ checkMonitorAt( width - 1, 0 ) && checkMonitorAt( width - 1, height - 1 ) )
+ {
+ return;
+ }
+
+ // Something in our monitor is invalid. For now, let's just reset ourselves and then try to integrate ourselves
+ // later.
+ resize( 1, 1 );
+ needsUpdate = true;
+ }
+
private void monitorTouched( float xPos, float yPos, float zPos )
{
XYPair pair = XYPair
@@ -658,7 +698,7 @@ private void monitorTouched( float xPos, float yPos, float zPos )
{
for( int x = 0; x < width; x++ )
{
- TileMonitor monitor = getNeighbour( x, y );
+ TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor == null ) continue;
for( IComputerAccess computer : monitor.computers )
@@ -684,8 +724,8 @@ void removeComputer( IComputerAccess computer )
@Override
public AxisAlignedBB getRenderBoundingBox()
{
- TileMonitor start = getNeighbour( 0, 0 );
- TileMonitor end = getNeighbour( width - 1, height - 1 );
+ TileMonitor start = getNeighbour( 0, 0 ).getMonitor();
+ TileMonitor end = getNeighbour( width - 1, height - 1 ).getMonitor();
if( start != null && end != null )
{
BlockPos startPos = start.getBlockPos();
diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java
index 2578efc12..264619ae6 100644
--- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java
+++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java
@@ -47,7 +47,10 @@ public void tick()
public void setRemoved()
{
super.setRemoved();
- NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
+ if( level != null && !level.isClientSide )
+ {
+ NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
+ }
}
@Nonnull
diff --git a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua
index 6dd2438b1..c22393997 100644
--- a/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua
+++ b/src/main/resources/data/computercraft/lua/rom/apis/rednet.lua
@@ -382,12 +382,12 @@ function run()
-- Got a modem message, process it and add it to the rednet event queue
local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
if isOpen(sModem) and (nChannel == os.getComputerID() or nChannel == CHANNEL_BROADCAST) then
- if type(tMessage) == "table" and tMessage.nMessageID then
- if not tReceivedMessages[tMessage.nMessageID] then
- tReceivedMessages[tMessage.nMessageID] = true
- tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
- os.queueEvent("rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol)
- end
+ if type(tMessage) == "table" and type(tMessage.nMessageID) == "number"
+ and tMessage.nMessageID == tMessage.nMessageID and not tReceivedMessages[tMessage.nMessageID]
+ then
+ tReceivedMessages[tMessage.nMessageID] = true
+ tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
+ os.queueEvent("rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol)
end
end
diff --git a/src/main/resources/data/computercraft/lua/rom/programs/help.lua b/src/main/resources/data/computercraft/lua/rom/programs/help.lua
index 43ba258d4..9c7081ecc 100644
--- a/src/main/resources/data/computercraft/lua/rom/programs/help.lua
+++ b/src/main/resources/data/computercraft/lua/rom/programs/help.lua
@@ -265,7 +265,7 @@ while true do
local new_width, new_height = term.getSize()
if new_width ~= width then
- lines = word_wrap(contents, new_width)
+ lines, fg, bg = word_wrap(contents, new_width)
print_height = #lines
end
diff --git a/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt b/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt
new file mode 100644
index 000000000..936fb6c62
--- /dev/null
+++ b/src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt
@@ -0,0 +1,121 @@
+package dan200.computercraft.core.apis
+
+import dan200.computercraft.ComputerCraft
+import dan200.computercraft.api.lua.ILuaAPI
+import dan200.computercraft.api.lua.MethodResult
+import dan200.computercraft.api.peripheral.IPeripheral
+import dan200.computercraft.api.peripheral.IWorkMonitor
+import dan200.computercraft.core.computer.BasicEnvironment
+import dan200.computercraft.core.computer.ComputerSide
+import dan200.computercraft.core.computer.IComputerEnvironment
+import dan200.computercraft.core.filesystem.FileSystem
+import dan200.computercraft.core.terminal.Terminal
+import dan200.computercraft.core.tracking.TrackingField
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import kotlin.time.Duration
+import kotlin.time.ExperimentalTime
+import kotlin.time.seconds
+
+
+abstract class NullApiEnvironment : IAPIEnvironment {
+ private val computerEnv = BasicEnvironment()
+
+ override fun getComputerID(): Int = 0
+ override fun getComputerEnvironment(): IComputerEnvironment = computerEnv
+ override fun getMainThreadMonitor(): IWorkMonitor = throw IllegalStateException("Work monitor not available")
+ override fun getTerminal(): Terminal = throw IllegalStateException("Terminal not available")
+ override fun getFileSystem(): FileSystem = throw IllegalStateException("Terminal not available")
+ override fun shutdown() {}
+ override fun reboot() {}
+ override fun setOutput(side: ComputerSide?, output: Int) {}
+ override fun getOutput(side: ComputerSide?): Int = 0
+ override fun getInput(side: ComputerSide?): Int = 0
+ override fun setBundledOutput(side: ComputerSide?, output: Int) {}
+ override fun getBundledOutput(side: ComputerSide?): Int = 0
+ override fun getBundledInput(side: ComputerSide?): Int = 0
+ override fun setPeripheralChangeListener(listener: IAPIEnvironment.IPeripheralChangeListener?) {}
+ override fun getPeripheral(side: ComputerSide?): IPeripheral? = null
+ override fun getLabel(): String? = null
+ override fun setLabel(label: String?) {}
+ override fun startTimer(ticks: Long): Int = 0
+ override fun cancelTimer(id: Int) {}
+ override fun addTrackingChange(field: TrackingField, change: Long) {}
+}
+
+class EventResult(val name: String, val args: Array)
+
+class AsyncRunner : NullApiEnvironment() {
+ private val eventStream: Channel> = Channel(Int.MAX_VALUE)
+ private val apis: MutableList = mutableListOf()
+
+ override fun queueEvent(event: String?, vararg args: Any?) {
+ ComputerCraft.log.debug("Queue event $event ${args.contentToString()}")
+ if (!eventStream.offer(arrayOf(event, *args))) {
+ throw IllegalStateException("Queue is full")
+ }
+ }
+
+ override fun shutdown() {
+ super.shutdown()
+ eventStream.close()
+ apis.forEach { it.shutdown() }
+ }
+
+ fun addApi(api: T): T {
+ apis.add(api)
+ api.startup()
+ return api
+ }
+
+ suspend fun resultOf(toRun: MethodResult): Array {
+ var running = toRun
+ while (running.callback != null) running = runOnce(running)
+ return running.result ?: empty
+ }
+
+ private suspend fun runOnce(obj: MethodResult): MethodResult {
+ val callback = obj.callback ?: throw NullPointerException("Callback cannot be null")
+
+ val result = obj.result
+ val filter: String? = if (result.isNullOrEmpty() || result[0] !is String) {
+ null
+ } else {
+ result[0] as String
+ }
+
+ return callback.resume(pullEventImpl(filter))
+ }
+
+ private suspend fun pullEventImpl(filter: String?): Array {
+ for (event in eventStream) {
+ ComputerCraft.log.debug("Pulled event ${event.contentToString()}")
+ val eventName = event[0] as String
+ if (filter == null || eventName == filter || eventName == "terminate") return event
+ }
+
+ throw IllegalStateException("No more events")
+ }
+
+ suspend fun pullEvent(filter: String? = null): EventResult {
+ val result = pullEventImpl(filter)
+ return EventResult(result[0] as String, result.copyOfRange(1, result.size))
+ }
+
+ companion object {
+ private val empty: Array = arrayOf()
+
+ @OptIn(ExperimentalTime::class)
+ fun runTest(timeout: Duration = 5.seconds, fn: suspend AsyncRunner.() -> Unit) {
+ runBlocking {
+ val runner = AsyncRunner()
+ try {
+ withTimeout(timeout) { fn(runner) }
+ } finally {
+ runner.shutdown()
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/dan200/computercraft/core/apis/http/options/TestHttpApi.kt b/src/test/java/dan200/computercraft/core/apis/http/options/TestHttpApi.kt
new file mode 100644
index 000000000..206924cc0
--- /dev/null
+++ b/src/test/java/dan200/computercraft/core/apis/http/options/TestHttpApi.kt
@@ -0,0 +1,51 @@
+package dan200.computercraft.core.apis.http.options
+
+import dan200.computercraft.ComputerCraft
+import dan200.computercraft.core.apis.AsyncRunner
+import dan200.computercraft.core.apis.HTTPAPI
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Disabled
+import org.junit.jupiter.api.Test
+import java.util.*
+
+@Disabled("Requires some setup locally.")
+class TestHttpApi {
+ companion object {
+ private const val WS_ADDRESS = "ws://127.0.0.1:8080"
+
+ @JvmStatic
+ @BeforeAll
+ fun before() {
+ ComputerCraft.httpRules = listOf(AddressRule.parse("*", null, Action.ALLOW.toPartial()))
+ }
+
+ @JvmStatic
+ @AfterAll
+ fun after() {
+ ComputerCraft.httpRules = Collections.unmodifiableList(
+ listOf(
+ AddressRule.parse("\$private", null, Action.DENY.toPartial()),
+ AddressRule.parse("*", null, Action.ALLOW.toPartial())
+ )
+ )
+ }
+ }
+
+ @Test
+ fun `Connects to websocket`() {
+ AsyncRunner.runTest {
+ val httpApi = addApi(HTTPAPI(this))
+
+ val result = httpApi.websocket(WS_ADDRESS, Optional.empty())
+ assertArrayEquals(arrayOf(true), result, "Should have created websocket")
+
+ val event = pullEvent()
+ assertEquals("websocket_success", event.name) {
+ "Websocket failed to connect: ${event.args.contentToString()}"
+ }
+ }
+ }
+}
diff --git a/src/test/java/dan200/computercraft/ingame/CraftOsTest.kt b/src/test/java/dan200/computercraft/ingame/CraftOsTest.kt
new file mode 100644
index 000000000..8b3bef714
--- /dev/null
+++ b/src/test/java/dan200/computercraft/ingame/CraftOsTest.kt
@@ -0,0 +1,13 @@
+package dan200.computercraft.ingame
+
+import dan200.computercraft.ingame.api.GameTest
+import dan200.computercraft.ingame.api.TestContext
+import dan200.computercraft.ingame.api.checkComputerOk
+
+class CraftOsTest {
+ /**
+ * Sends a rednet message to another a computer and back again.
+ */
+ @GameTest
+ suspend fun `Sends basic rednet messages`(context: TestContext) = context.checkComputerOk(13)
+}
diff --git a/src/test/java/dan200/computercraft/ingame/MonitorTest.kt b/src/test/java/dan200/computercraft/ingame/MonitorTest.kt
new file mode 100644
index 000000000..6954f2522
--- /dev/null
+++ b/src/test/java/dan200/computercraft/ingame/MonitorTest.kt
@@ -0,0 +1,39 @@
+package dan200.computercraft.ingame
+
+import dan200.computercraft.ingame.api.*
+import dan200.computercraft.shared.Registry
+import dan200.computercraft.shared.peripheral.monitor.TileMonitor
+import net.minecraft.block.Blocks
+import net.minecraft.command.arguments.BlockStateInput
+import net.minecraft.nbt.CompoundNBT
+import net.minecraft.util.math.BlockPos
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.fail
+import java.util.*
+
+class MonitorTest {
+ @GameTest
+ suspend fun `Ensures valid on place`(context: TestContext) {
+ val pos = BlockPos(2, 0, 2)
+ val tag = CompoundNBT()
+ tag.putInt("Width", 2)
+ tag.putInt("Height", 2)
+
+ val toSet = BlockStateInput(
+ Registry.ModBlocks.MONITOR_ADVANCED.get().defaultBlockState(),
+ Collections.emptySet(),
+ tag
+ )
+
+ context.setBlock(pos, Blocks.AIR.defaultBlockState())
+ context.setBlock(pos, toSet)
+
+ context.sleep(2)
+
+ val tile = context.getTile(pos)
+ if (tile !is TileMonitor) fail("Expected tile to be monitor, is $tile")
+
+ assertEquals(1, tile.width, "Width should be 1")
+ assertEquals(1, tile.height, "Width should be 1")
+ }
+}
diff --git a/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt b/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt
index 5d285c346..1bf03ef3c 100644
--- a/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt
+++ b/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt
@@ -3,6 +3,7 @@
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import net.minecraft.block.BlockState
+import net.minecraft.command.arguments.BlockStateInput
import net.minecraft.entity.Entity
import net.minecraft.tileentity.TileEntity
import net.minecraft.util.math.AxisAlignedBB
@@ -54,6 +55,11 @@
tracker.level.setBlockAndUpdate(offset(pos), state)
}
+/**
+ * Set a block within the test structure.
+ */
+fun TestContext.setBlock(pos: BlockPos, state: BlockStateInput) = state.place(tracker.level, offset(pos), 3)
+
/**
* Modify a block state within the test.
*/
diff --git a/src/test/server-files/computers/computer/11/startup.lua b/src/test/server-files/computers/computer/11/startup.lua
index bcc6c8ee2..58cc42004 100644
--- a/src/test/server-files/computers/computer/11/startup.lua
+++ b/src/test/server-files/computers/computer/11/startup.lua
@@ -1,3 +1,5 @@
+-- TurtleTest.`Use compostors`
+
test.eq(true, turtle.dropDown(), "Drop items into compostor")
test.ok()
diff --git a/src/test/server-files/computers/computer/12/startup.lua b/src/test/server-files/computers/computer/12/startup.lua
index d84bc4bdc..94513b754 100644
--- a/src/test/server-files/computers/computer/12/startup.lua
+++ b/src/test/server-files/computers/computer/12/startup.lua
@@ -1,3 +1,5 @@
+-- TurtleTest.`Cleaned with cauldrons`
+
local old_details = turtle.getItemDetail(1, true)
test.assert(turtle.place(), "Dyed turtle")
diff --git a/src/test/server-files/computers/computer/13/startup.lua b/src/test/server-files/computers/computer/13/startup.lua
new file mode 100644
index 000000000..e8954964f
--- /dev/null
+++ b/src/test/server-files/computers/computer/13/startup.lua
@@ -0,0 +1,14 @@
+-- CraftOsTest.`Sends basic rednet messages`
+
+rednet.open("top")
+
+local id, msg
+repeat
+ rednet.send(14, "Test msg") -- Keep sending, as other computer may not have started yet.
+
+ id, msg = rednet.receive(nil, 1)
+ print(id, msg)
+until id == 14
+
+test.eq("Test msg", msg)
+test.ok()
diff --git a/src/test/server-files/computers/computer/14/startup.lua b/src/test/server-files/computers/computer/14/startup.lua
new file mode 100644
index 000000000..dd81c8ecb
--- /dev/null
+++ b/src/test/server-files/computers/computer/14/startup.lua
@@ -0,0 +1,7 @@
+-- CraftOsTest.`Sends basic rednet messages`
+
+rednet.open("top")
+while true do
+ local id, msg, protocol = rednet.receive()
+ rednet.send(id, msg, protocol)
+end
diff --git a/src/test/server-files/computers/ids.json b/src/test/server-files/computers/ids.json
index 6b4a94cbe..cbd91ed6c 100644
--- a/src/test/server-files/computers/ids.json
+++ b/src/test/server-files/computers/ids.json
@@ -1,3 +1,3 @@
{
- "computer": 12
+ "computer": 14
}
\ No newline at end of file
diff --git a/src/test/server-files/structures/craft_os_test.sends_basic_rednet_messages.snbt b/src/test/server-files/structures/craft_os_test.sends_basic_rednet_messages.snbt
new file mode 100644
index 000000000..a6ce86eba
--- /dev/null
+++ b/src/test/server-files/structures/craft_os_test.sends_basic_rednet_messages.snbt
@@ -0,0 +1,555 @@
+{
+ size: [5, 5, 5],
+ entities: [],
+ blocks: [
+ {
+ pos: [0, 0, 0],
+ state: 0
+ },
+ {
+ pos: [1, 0, 0],
+ state: 0
+ },
+ {
+ pos: [2, 0, 0],
+ state: 0
+ },
+ {
+ pos: [3, 0, 0],
+ state: 0
+ },
+ {
+ pos: [4, 0, 0],
+ state: 0
+ },
+ {
+ pos: [0, 0, 1],
+ state: 0
+ },
+ {
+ pos: [1, 0, 1],
+ state: 0
+ },
+ {
+ pos: [2, 0, 1],
+ state: 0
+ },
+ {
+ pos: [3, 0, 1],
+ state: 0
+ },
+ {
+ pos: [4, 0, 1],
+ state: 0
+ },
+ {
+ pos: [0, 0, 2],
+ state: 0
+ },
+ {
+ pos: [1, 0, 2],
+ state: 0
+ },
+ {
+ pos: [2, 0, 2],
+ state: 0
+ },
+ {
+ pos: [3, 0, 2],
+ state: 0
+ },
+ {
+ pos: [4, 0, 2],
+ state: 0
+ },
+ {
+ pos: [0, 0, 3],
+ state: 0
+ },
+ {
+ pos: [1, 0, 3],
+ state: 0
+ },
+ {
+ pos: [2, 0, 3],
+ state: 0
+ },
+ {
+ pos: [3, 0, 3],
+ state: 0
+ },
+ {
+ pos: [4, 0, 3],
+ state: 0
+ },
+ {
+ pos: [0, 0, 4],
+ state: 0
+ },
+ {
+ pos: [1, 0, 4],
+ state: 0
+ },
+ {
+ pos: [2, 0, 4],
+ state: 0
+ },
+ {
+ pos: [3, 0, 4],
+ state: 0
+ },
+ {
+ pos: [4, 0, 4],
+ state: 0
+ },
+ {
+ nbt: {
+ Label: "Echo",
+ id: "computercraft:computer_advanced",
+ ComputerId: 14,
+ On: 1b
+ },
+ pos: [1, 1, 2],
+ state: 1
+ },
+ {
+ nbt: {
+ Label: "Main",
+ id: "computercraft:computer_advanced",
+ ComputerId: 13,
+ On: 1b
+ },
+ pos: [3, 1, 2],
+ state: 2
+ },
+ {
+ nbt: {
+ id: "computercraft:wireless_modem_normal"
+ },
+ pos: [1, 2, 2],
+ state: 3
+ },
+ {
+ nbt: {
+ id: "computercraft:wireless_modem_normal"
+ },
+ pos: [3, 2, 2],
+ state: 3
+ },
+ {
+ pos: [0, 1, 0],
+ state: 4
+ },
+ {
+ pos: [1, 1, 0],
+ state: 4
+ },
+ {
+ pos: [2, 1, 0],
+ state: 4
+ },
+ {
+ pos: [3, 1, 0],
+ state: 4
+ },
+ {
+ pos: [4, 1, 0],
+ state: 4
+ },
+ {
+ pos: [0, 2, 0],
+ state: 4
+ },
+ {
+ pos: [1, 2, 0],
+ state: 4
+ },
+ {
+ pos: [2, 2, 0],
+ state: 4
+ },
+ {
+ pos: [3, 2, 0],
+ state: 4
+ },
+ {
+ pos: [4, 2, 0],
+ state: 4
+ },
+ {
+ pos: [0, 3, 0],
+ state: 4
+ },
+ {
+ pos: [1, 3, 0],
+ state: 4
+ },
+ {
+ pos: [2, 3, 0],
+ state: 4
+ },
+ {
+ pos: [3, 3, 0],
+ state: 4
+ },
+ {
+ pos: [4, 3, 0],
+ state: 4
+ },
+ {
+ pos: [0, 4, 0],
+ state: 4
+ },
+ {
+ pos: [1, 4, 0],
+ state: 4
+ },
+ {
+ pos: [2, 4, 0],
+ state: 4
+ },
+ {
+ pos: [3, 4, 0],
+ state: 4
+ },
+ {
+ pos: [4, 4, 0],
+ state: 4
+ },
+ {
+ pos: [0, 1, 1],
+ state: 4
+ },
+ {
+ pos: [1, 1, 1],
+ state: 4
+ },
+ {
+ pos: [2, 1, 1],
+ state: 4
+ },
+ {
+ pos: [3, 1, 1],
+ state: 4
+ },
+ {
+ pos: [4, 1, 1],
+ state: 4
+ },
+ {
+ pos: [0, 2, 1],
+ state: 4
+ },
+ {
+ pos: [1, 2, 1],
+ state: 4
+ },
+ {
+ pos: [2, 2, 1],
+ state: 4
+ },
+ {
+ pos: [3, 2, 1],
+ state: 4
+ },
+ {
+ pos: [4, 2, 1],
+ state: 4
+ },
+ {
+ pos: [0, 3, 1],
+ state: 4
+ },
+ {
+ pos: [1, 3, 1],
+ state: 4
+ },
+ {
+ pos: [2, 3, 1],
+ state: 4
+ },
+ {
+ pos: [3, 3, 1],
+ state: 4
+ },
+ {
+ pos: [4, 3, 1],
+ state: 4
+ },
+ {
+ pos: [0, 4, 1],
+ state: 4
+ },
+ {
+ pos: [1, 4, 1],
+ state: 4
+ },
+ {
+ pos: [2, 4, 1],
+ state: 4
+ },
+ {
+ pos: [3, 4, 1],
+ state: 4
+ },
+ {
+ pos: [4, 4, 1],
+ state: 4
+ },
+ {
+ pos: [0, 1, 2],
+ state: 4
+ },
+ {
+ pos: [2, 1, 2],
+ state: 4
+ },
+ {
+ pos: [4, 1, 2],
+ state: 4
+ },
+ {
+ pos: [0, 2, 2],
+ state: 4
+ },
+ {
+ pos: [2, 2, 2],
+ state: 4
+ },
+ {
+ pos: [4, 2, 2],
+ state: 4
+ },
+ {
+ pos: [0, 3, 2],
+ state: 4
+ },
+ {
+ pos: [1, 3, 2],
+ state: 4
+ },
+ {
+ pos: [2, 3, 2],
+ state: 4
+ },
+ {
+ pos: [3, 3, 2],
+ state: 4
+ },
+ {
+ pos: [4, 3, 2],
+ state: 4
+ },
+ {
+ pos: [0, 4, 2],
+ state: 4
+ },
+ {
+ pos: [1, 4, 2],
+ state: 4
+ },
+ {
+ pos: [2, 4, 2],
+ state: 4
+ },
+ {
+ pos: [3, 4, 2],
+ state: 4
+ },
+ {
+ pos: [4, 4, 2],
+ state: 4
+ },
+ {
+ pos: [0, 1, 3],
+ state: 4
+ },
+ {
+ pos: [1, 1, 3],
+ state: 4
+ },
+ {
+ pos: [2, 1, 3],
+ state: 4
+ },
+ {
+ pos: [3, 1, 3],
+ state: 4
+ },
+ {
+ pos: [4, 1, 3],
+ state: 4
+ },
+ {
+ pos: [0, 2, 3],
+ state: 4
+ },
+ {
+ pos: [1, 2, 3],
+ state: 4
+ },
+ {
+ pos: [2, 2, 3],
+ state: 4
+ },
+ {
+ pos: [3, 2, 3],
+ state: 4
+ },
+ {
+ pos: [4, 2, 3],
+ state: 4
+ },
+ {
+ pos: [0, 3, 3],
+ state: 4
+ },
+ {
+ pos: [1, 3, 3],
+ state: 4
+ },
+ {
+ pos: [2, 3, 3],
+ state: 4
+ },
+ {
+ pos: [3, 3, 3],
+ state: 4
+ },
+ {
+ pos: [4, 3, 3],
+ state: 4
+ },
+ {
+ pos: [0, 4, 3],
+ state: 4
+ },
+ {
+ pos: [1, 4, 3],
+ state: 4
+ },
+ {
+ pos: [2, 4, 3],
+ state: 4
+ },
+ {
+ pos: [3, 4, 3],
+ state: 4
+ },
+ {
+ pos: [4, 4, 3],
+ state: 4
+ },
+ {
+ pos: [0, 1, 4],
+ state: 4
+ },
+ {
+ pos: [1, 1, 4],
+ state: 4
+ },
+ {
+ pos: [2, 1, 4],
+ state: 4
+ },
+ {
+ pos: [3, 1, 4],
+ state: 4
+ },
+ {
+ pos: [4, 1, 4],
+ state: 4
+ },
+ {
+ pos: [0, 2, 4],
+ state: 4
+ },
+ {
+ pos: [1, 2, 4],
+ state: 4
+ },
+ {
+ pos: [2, 2, 4],
+ state: 4
+ },
+ {
+ pos: [3, 2, 4],
+ state: 4
+ },
+ {
+ pos: [4, 2, 4],
+ state: 4
+ },
+ {
+ pos: [0, 3, 4],
+ state: 4
+ },
+ {
+ pos: [1, 3, 4],
+ state: 4
+ },
+ {
+ pos: [2, 3, 4],
+ state: 4
+ },
+ {
+ pos: [3, 3, 4],
+ state: 4
+ },
+ {
+ pos: [4, 3, 4],
+ state: 4
+ },
+ {
+ pos: [0, 4, 4],
+ state: 4
+ },
+ {
+ pos: [1, 4, 4],
+ state: 4
+ },
+ {
+ pos: [2, 4, 4],
+ state: 4
+ },
+ {
+ pos: [3, 4, 4],
+ state: 4
+ },
+ {
+ pos: [4, 4, 4],
+ state: 4
+ }
+ ],
+ palette: [
+ {
+ Name: "minecraft:polished_andesite"
+ },
+ {
+ Properties: {
+ facing: "north",
+ state: "on"
+ },
+ Name: "computercraft:computer_advanced"
+ },
+ {
+ Properties: {
+ facing: "north",
+ state: "blinking"
+ },
+ Name: "computercraft:computer_advanced"
+ },
+ {
+ Properties: {
+ waterlogged: "false",
+ facing: "down",
+ on: "true"
+ },
+ Name: "computercraft:wireless_modem_normal"
+ },
+ {
+ Name: "minecraft:air"
+ }
+ ],
+ DataVersion: 2230
+}
diff --git a/src/test/server-files/structures/monitor_test.ensures_valid_on_place.snbt b/src/test/server-files/structures/monitor_test.ensures_valid_on_place.snbt
new file mode 100644
index 000000000..eef6627f5
--- /dev/null
+++ b/src/test/server-files/structures/monitor_test.ensures_valid_on_place.snbt
@@ -0,0 +1,515 @@
+{
+ size: [5, 5, 5],
+ entities: [],
+ blocks: [
+ {
+ pos: [0, 0, 0],
+ state: 0
+ },
+ {
+ pos: [1, 0, 0],
+ state: 0
+ },
+ {
+ pos: [2, 0, 0],
+ state: 0
+ },
+ {
+ pos: [3, 0, 0],
+ state: 0
+ },
+ {
+ pos: [4, 0, 0],
+ state: 0
+ },
+ {
+ pos: [0, 0, 1],
+ state: 0
+ },
+ {
+ pos: [1, 0, 1],
+ state: 0
+ },
+ {
+ pos: [2, 0, 1],
+ state: 0
+ },
+ {
+ pos: [3, 0, 1],
+ state: 0
+ },
+ {
+ pos: [4, 0, 1],
+ state: 0
+ },
+ {
+ pos: [0, 0, 2],
+ state: 0
+ },
+ {
+ pos: [1, 0, 2],
+ state: 0
+ },
+ {
+ pos: [2, 0, 2],
+ state: 0
+ },
+ {
+ pos: [3, 0, 2],
+ state: 0
+ },
+ {
+ pos: [4, 0, 2],
+ state: 0
+ },
+ {
+ pos: [0, 0, 3],
+ state: 0
+ },
+ {
+ pos: [1, 0, 3],
+ state: 0
+ },
+ {
+ pos: [2, 0, 3],
+ state: 0
+ },
+ {
+ pos: [3, 0, 3],
+ state: 0
+ },
+ {
+ pos: [4, 0, 3],
+ state: 0
+ },
+ {
+ pos: [0, 0, 4],
+ state: 0
+ },
+ {
+ pos: [1, 0, 4],
+ state: 0
+ },
+ {
+ pos: [2, 0, 4],
+ state: 0
+ },
+ {
+ pos: [3, 0, 4],
+ state: 0
+ },
+ {
+ pos: [4, 0, 4],
+ state: 0
+ },
+ {
+ pos: [0, 1, 0],
+ state: 1
+ },
+ {
+ pos: [1, 1, 0],
+ state: 1
+ },
+ {
+ pos: [2, 1, 0],
+ state: 1
+ },
+ {
+ pos: [3, 1, 0],
+ state: 1
+ },
+ {
+ pos: [4, 1, 0],
+ state: 1
+ },
+ {
+ pos: [0, 2, 0],
+ state: 1
+ },
+ {
+ pos: [1, 2, 0],
+ state: 1
+ },
+ {
+ pos: [2, 2, 0],
+ state: 1
+ },
+ {
+ pos: [3, 2, 0],
+ state: 1
+ },
+ {
+ pos: [4, 2, 0],
+ state: 1
+ },
+ {
+ pos: [0, 3, 0],
+ state: 1
+ },
+ {
+ pos: [1, 3, 0],
+ state: 1
+ },
+ {
+ pos: [2, 3, 0],
+ state: 1
+ },
+ {
+ pos: [3, 3, 0],
+ state: 1
+ },
+ {
+ pos: [4, 3, 0],
+ state: 1
+ },
+ {
+ pos: [0, 4, 0],
+ state: 1
+ },
+ {
+ pos: [1, 4, 0],
+ state: 1
+ },
+ {
+ pos: [2, 4, 0],
+ state: 1
+ },
+ {
+ pos: [3, 4, 0],
+ state: 1
+ },
+ {
+ pos: [4, 4, 0],
+ state: 1
+ },
+ {
+ pos: [0, 1, 1],
+ state: 1
+ },
+ {
+ pos: [1, 1, 1],
+ state: 1
+ },
+ {
+ pos: [2, 1, 1],
+ state: 1
+ },
+ {
+ pos: [3, 1, 1],
+ state: 1
+ },
+ {
+ pos: [4, 1, 1],
+ state: 1
+ },
+ {
+ pos: [0, 2, 1],
+ state: 1
+ },
+ {
+ pos: [1, 2, 1],
+ state: 1
+ },
+ {
+ pos: [2, 2, 1],
+ state: 1
+ },
+ {
+ pos: [3, 2, 1],
+ state: 1
+ },
+ {
+ pos: [4, 2, 1],
+ state: 1
+ },
+ {
+ pos: [0, 3, 1],
+ state: 1
+ },
+ {
+ pos: [1, 3, 1],
+ state: 1
+ },
+ {
+ pos: [2, 3, 1],
+ state: 1
+ },
+ {
+ pos: [3, 3, 1],
+ state: 1
+ },
+ {
+ pos: [4, 3, 1],
+ state: 1
+ },
+ {
+ pos: [0, 4, 1],
+ state: 1
+ },
+ {
+ pos: [1, 4, 1],
+ state: 1
+ },
+ {
+ pos: [2, 4, 1],
+ state: 1
+ },
+ {
+ pos: [3, 4, 1],
+ state: 1
+ },
+ {
+ pos: [4, 4, 1],
+ state: 1
+ },
+ {
+ pos: [0, 1, 2],
+ state: 1
+ },
+ {
+ pos: [1, 1, 2],
+ state: 1
+ },
+ {
+ pos: [2, 1, 2],
+ state: 1
+ },
+ {
+ pos: [3, 1, 2],
+ state: 1
+ },
+ {
+ pos: [4, 1, 2],
+ state: 1
+ },
+ {
+ pos: [0, 2, 2],
+ state: 1
+ },
+ {
+ pos: [1, 2, 2],
+ state: 1
+ },
+ {
+ pos: [2, 2, 2],
+ state: 1
+ },
+ {
+ pos: [3, 2, 2],
+ state: 1
+ },
+ {
+ pos: [4, 2, 2],
+ state: 1
+ },
+ {
+ pos: [0, 3, 2],
+ state: 1
+ },
+ {
+ pos: [1, 3, 2],
+ state: 1
+ },
+ {
+ pos: [2, 3, 2],
+ state: 1
+ },
+ {
+ pos: [3, 3, 2],
+ state: 1
+ },
+ {
+ pos: [4, 3, 2],
+ state: 1
+ },
+ {
+ pos: [0, 4, 2],
+ state: 1
+ },
+ {
+ pos: [1, 4, 2],
+ state: 1
+ },
+ {
+ pos: [2, 4, 2],
+ state: 1
+ },
+ {
+ pos: [3, 4, 2],
+ state: 1
+ },
+ {
+ pos: [4, 4, 2],
+ state: 1
+ },
+ {
+ pos: [0, 1, 3],
+ state: 1
+ },
+ {
+ pos: [1, 1, 3],
+ state: 1
+ },
+ {
+ pos: [2, 1, 3],
+ state: 1
+ },
+ {
+ pos: [3, 1, 3],
+ state: 1
+ },
+ {
+ pos: [4, 1, 3],
+ state: 1
+ },
+ {
+ pos: [0, 2, 3],
+ state: 1
+ },
+ {
+ pos: [1, 2, 3],
+ state: 1
+ },
+ {
+ pos: [2, 2, 3],
+ state: 1
+ },
+ {
+ pos: [3, 2, 3],
+ state: 1
+ },
+ {
+ pos: [4, 2, 3],
+ state: 1
+ },
+ {
+ pos: [0, 3, 3],
+ state: 1
+ },
+ {
+ pos: [1, 3, 3],
+ state: 1
+ },
+ {
+ pos: [2, 3, 3],
+ state: 1
+ },
+ {
+ pos: [3, 3, 3],
+ state: 1
+ },
+ {
+ pos: [4, 3, 3],
+ state: 1
+ },
+ {
+ pos: [0, 4, 3],
+ state: 1
+ },
+ {
+ pos: [1, 4, 3],
+ state: 1
+ },
+ {
+ pos: [2, 4, 3],
+ state: 1
+ },
+ {
+ pos: [3, 4, 3],
+ state: 1
+ },
+ {
+ pos: [4, 4, 3],
+ state: 1
+ },
+ {
+ pos: [0, 1, 4],
+ state: 1
+ },
+ {
+ pos: [1, 1, 4],
+ state: 1
+ },
+ {
+ pos: [2, 1, 4],
+ state: 1
+ },
+ {
+ pos: [3, 1, 4],
+ state: 1
+ },
+ {
+ pos: [4, 1, 4],
+ state: 1
+ },
+ {
+ pos: [0, 2, 4],
+ state: 1
+ },
+ {
+ pos: [1, 2, 4],
+ state: 1
+ },
+ {
+ pos: [2, 2, 4],
+ state: 1
+ },
+ {
+ pos: [3, 2, 4],
+ state: 1
+ },
+ {
+ pos: [4, 2, 4],
+ state: 1
+ },
+ {
+ pos: [0, 3, 4],
+ state: 1
+ },
+ {
+ pos: [1, 3, 4],
+ state: 1
+ },
+ {
+ pos: [2, 3, 4],
+ state: 1
+ },
+ {
+ pos: [3, 3, 4],
+ state: 1
+ },
+ {
+ pos: [4, 3, 4],
+ state: 1
+ },
+ {
+ pos: [0, 4, 4],
+ state: 1
+ },
+ {
+ pos: [1, 4, 4],
+ state: 1
+ },
+ {
+ pos: [2, 4, 4],
+ state: 1
+ },
+ {
+ pos: [3, 4, 4],
+ state: 1
+ },
+ {
+ pos: [4, 4, 4],
+ state: 1
+ }
+ ],
+ palette: [
+ {
+ Name: "minecraft:polished_andesite"
+ },
+ {
+ Name: "minecraft:air"
+ }
+ ],
+ DataVersion: 2230
+}