1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-02 22:53:15 +00:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Jonathan Coates
10a3a223a0 Bump version to 1.97.1
Plan here is to release 1.98 for 1.16.x and 1.17.x and 1.97.1 for
1.15.x. However, will let this sit for a few days while I sort out 1.98
and the 1.17 port just in case any more bugs pop up.
2021-07-28 16:15:32 +01:00
Jonathan Coates
2dc970a8bb Don't error when given a malformed URL
Sometimes the pattern fails to match and so the file name ends up being
nil.
2021-07-28 16:00:31 +01:00
Jonathan Coates
f74c4cc83c Add config options for a global bandwidth limit
This uses Netty's global traffic shaping handlers to limit the rate at
which packets can be sent and received. If the bandwidth limit is hit,
we'll start dropping packets, which will mean remote servers send
traffic to us at a much slower pace.

This isn't perfect, as there is only a global limit, and not a
per-computer one. As a result, its possible for one computer to use
all/most bandwidth, and thus slow down other computers.

This would be something to improve on in the future. However, I've spent
a lot of time reading the netty source code and docs, and the
implementation for that is significantly more complex, and one I'm not
comfortable working on right now.

For the time being, this satisfies the issues in #33 and hopefully
alleviates server owner's concerns about the http API. Remaining
problems can either be solved by moderation (with help of the
//computercraft track` command) or future updates.

Closes #33
2021-07-28 15:53:22 +01:00
Jonathan Coates
227b444d81 Accept client_no_context_takeover in websockets
Doesn't fix #695, but Good Enough(TM).
2021-07-25 16:40:27 +01:00
Jonathan Coates
d50db8a6f3 Add a fancy test system for async methods
Written in order to ~avoid working on~ test #695. Sadly, no luck there.
2021-07-25 16:25:59 +01:00
Jonathan Coates
3a80b51a9f Ensure monitors are well-formed when placed
Closes #36
2021-07-25 14:18:07 +01:00
Jonathan Coates
03396cf07a Fix help crashing on terminal resize
Closes #870. Woops.
2021-07-24 22:58:23 +01:00
Jonathan Coates
0568c86628 Hopefully fix flakiness in rednet test 2021-07-24 00:00:37 +01:00
Jonathan Coates
b31e66686d Make rednet.run a little more strict
Also add a test for rednet message sending. Hopefully gives some of the
modem and networking code a little bit of coverage (which is clearly the
same as being right :p).
2021-07-23 23:53:40 +01:00
Jonathan Coates
924b8ef30f Do not access the current server from the client
Just missing some guards in a few places.

Fixes #867
2021-07-23 23:35:13 +01:00
Jonathan Coates
82ca19c296 Add type check for http.checkURL 2021-07-15 13:04:44 +01:00
Jonathan Coates
56d8a5d585 Don't update block states when there is no world
Fixes #856
2021-07-15 12:13:38 +01:00
Jonathan Coates
aa5fbb2980 Use heap buffers rather than direct ones
Closes #855, even if it doesn't really fix it.
2021-07-15 11:55:52 +01:00
Jonathan Coates
db0bb071f5 Add motd for file uploading 2021-07-15 10:58:52 +01:00
Jonathan Coates
ab702e2ba1 Correctly render turtles' selected slot
We had the wrong texture bound, which meant nothing was being rendered!

Fixes #852
2021-07-15 10:58:40 +01:00
SkyTheCodeMaster
d4efacd40a Turn a nil to a string
Makes a `nil` a string inside of an expect in `define`, allowing `nil` to be passed.
2021-07-15 10:54:30 +01:00
Jonathan Coates
c2316ef256 Publish docs from 1.16 instead 2021-07-02 21:49:42 +01:00
Jonathan Coates
51dde077fe And there was me thinking I understood Gradle 2021-06-29 09:08:45 +00:00
Jonathan Coates
31d0b7afcd Some further fixes to previous commit
Did NOT mean to push that
2021-06-28 23:20:54 +01:00
Jonathan Coates
95b0d950aa Couple of gradle fixes 2021-06-28 23:07:36 +01:00
40 changed files with 1602 additions and 94 deletions

View File

@@ -3,7 +3,7 @@ name: Build documentation
on:
push:
branches:
- mc-1.15.x
- mc-1.16.x
jobs:
make_doc:

View File

@@ -472,11 +472,12 @@ tasks.register('publishModrinth', TaskModrinthUpload.class).configure {
project.hasProperty('modrinthApiKey')
}
token = project.hasProperty('curseForgeApiKey')
token = project.hasProperty('modrinthApiKey') ? project.getProperty('modrinthApiKey') : ''
projectId = 'gu7yAYhd'
versionNumber = project.mod_version
versionNumber = "${project.mc_version}-${project.mod_version}"
uploadFile = jar
addGameVersion(project.mc_version)
changelog = "Release notes can be found on the [GitHub repository](https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
addLoader('forge')
}
@@ -546,7 +547,7 @@ githubRelease {
tagName "v${mc_version}-${mod_version}"
releaseName "[${mc_version}] ${mod_version}"
body.set(project.provider({
"## " + new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt")
"## " + new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != 'Type "help changelog" to see the full version history.' }
.join("\n").trim()

View File

@@ -1,5 +1,5 @@
# Mod properties
mod_version=1.97.0
mod_version=1.97.1
# Minecraft properties (update mods.toml when changing)
mc_version=1.15.2

View File

@@ -52,6 +52,8 @@ public final class ComputerCraft
public static int httpMaxRequests = 16;
public static int httpMaxWebsockets = 4;
public static int httpDownloadBandwidth = 32 * 1024 * 1024;
public static int httpUploadBandwidth = 32 * 1024 * 1024;
public static boolean enableCommandBlock = false;
public static int modemRange = 64;

View File

@@ -55,9 +55,6 @@ public class GuiTurtle extends ComputerScreenBase<ContainerTurtle>
minecraft.getTextureManager().bind( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
blit( leftPos + ComputerSidebar.WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT );
minecraft.getTextureManager().bind( advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL );
ComputerSidebar.renderBackground( leftPos, topPos + sidebarYOffset );
int slot = getMenu().getSelectedSlot();
if( slot >= 0 )
{
@@ -69,5 +66,8 @@ public class GuiTurtle extends ComputerScreenBase<ContainerTurtle>
0, 217, 24, 24
);
}
minecraft.getTextureManager().bind( advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL );
ComputerSidebar.renderBackground( leftPos, topPos + sidebarYOffset );
}
}

View File

@@ -62,6 +62,7 @@ public interface IAPIEnvironment
@Nullable
IPeripheral getPeripheral( ComputerSide side );
@Nullable
String getLabel();
void setLabel( @Nullable String label );

View File

@@ -20,6 +20,8 @@ import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.traffic.AbstractTrafficShapingHandler;
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import javax.annotation.Nonnull;
import javax.net.ssl.SSLException;
@@ -28,9 +30,7 @@ import javax.net.ssl.TrustManagerFactory;
import java.net.InetSocketAddress;
import java.net.URI;
import java.security.KeyStore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
@@ -38,10 +38,8 @@ import java.util.concurrent.TimeUnit;
*/
public final class NetworkUtils
{
public static final ExecutorService EXECUTOR = new ThreadPoolExecutor(
4, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
public static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(
4,
ThreadUtils.builder( "Network" )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.build()
@@ -52,6 +50,15 @@ public final class NetworkUtils
.build()
);
public static final AbstractTrafficShapingHandler SHAPING_HANDLER = new GlobalTrafficShapingHandler(
EXECUTOR, ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth
);
static
{
EXECUTOR.setKeepAliveTime( 60, TimeUnit.SECONDS );
}
private NetworkUtils()
{
}
@@ -107,6 +114,16 @@ public final class NetworkUtils
}
}
public static void reloadConfig()
{
SHAPING_HANDLER.configure( ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth );
}
public static void reset()
{
SHAPING_HANDLER.trafficCounter().resetCumulativeTime();
}
/**
* Create a {@link InetSocketAddress} from a {@link java.net.URI}.
*

View File

@@ -167,6 +167,7 @@ public class HttpRequest extends Resource<HttpRequest>
}
ChannelPipeline p = ch.pipeline();
p.addLast( NetworkUtils.SHAPING_HANDLER );
if( sslContext != null )
{
p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) );

View File

@@ -28,7 +28,6 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
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;
@@ -146,6 +145,7 @@ public class Websocket extends Resource<Websocket>
protected void initChannel( SocketChannel ch )
{
ChannelPipeline p = ch.pipeline();
p.addLast( NetworkUtils.SHAPING_HANDLER );
if( sslContext != null )
{
p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) );
@@ -160,7 +160,7 @@ public class Websocket extends Resource<Websocket>
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator( 8192 ),
WebSocketClientCompressionHandler.INSTANCE,
WebsocketCompressionHandler.INSTANCE,
new WebsocketHandler( Websocket.this, handshaker, options )
);
}

View File

@@ -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 <em>slightly</em> 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 )
);
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
@@ -83,6 +84,7 @@ public final class CommonHooks
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
NetworkUtils.reset();
}
@SubscribeEvent
@@ -91,6 +93,7 @@ public final class CommonHooks
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
NetworkUtils.reset();
}
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation( ComputerCraft.MOD_ID, "treasure_disk" );

View File

@@ -12,6 +12,7 @@ import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.options.Action;
import dan200.computercraft.core.apis.http.options.AddressRuleConfig;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
@@ -56,6 +57,9 @@ public final class Config
private static final ConfigValue<Integer> httpMaxRequests;
private static final ConfigValue<Integer> httpMaxWebsockets;
private static final ConfigValue<Integer> httpDownloadBandwidth;
private static final ConfigValue<Integer> httpUploadBandwidth;
private static final ConfigValue<Boolean> commandBlockEnabled;
private static final ConfigValue<Integer> modemRange;
private static final ConfigValue<Integer> modemHighAltitudeRange;
@@ -187,6 +191,20 @@ public final class Config
.comment( "The number of websockets a computer can have open at one time. Set to 0 for unlimited." )
.defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE );
builder
.comment( "Limits bandwidth used by computers" )
.push( "bandwidth" );
httpDownloadBandwidth = builder
.comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)" )
.defineInRange( "global_download", ComputerCraft.httpDownloadBandwidth, 1, Integer.MAX_VALUE );
httpUploadBandwidth = builder
.comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)" )
.defineInRange( "global_upload", ComputerCraft.httpUploadBandwidth, 1, Integer.MAX_VALUE );
builder.pop();
builder.pop();
}
@@ -329,6 +347,8 @@ public final class Config
ComputerCraft.httpMaxRequests = httpMaxRequests.get();
ComputerCraft.httpMaxWebsockets = httpMaxWebsockets.get();
ComputerCraft.httpDownloadBandwidth = httpDownloadBandwidth.get();
NetworkUtils.reloadConfig();
// Peripheral
ComputerCraft.enableCommandBlock = commandBlockEnabled.get();

View File

@@ -7,11 +7,13 @@ package dan200.computercraft.shared.command;
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 final class ClientCommands
// 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();

View File

@@ -21,7 +21,6 @@ import net.minecraftforge.fml.network.NetworkEvent;
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;
@@ -68,10 +67,7 @@ public final class NetworkHandler
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 )

View File

@@ -126,7 +126,7 @@ public class TerminalState
if( !compress ) return buffer;
if( compressed != null ) return compressed;
ByteBuf compressed = Unpooled.directBuffer();
ByteBuf compressed = Unpooled.buffer();
OutputStream stream = null;
try
{

View File

@@ -442,7 +442,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
private void updateBlockState()
{
if( remove ) return;
if( remove || level == null ) return;
if( !diskStack.isEmpty() )
{

View File

@@ -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,
}
}

View File

@@ -56,6 +56,7 @@ public class TileMonitor extends TileGeneric
private final Set<IComputerAccess> computers = new HashSet<>();
private boolean needsUpdate = false;
private boolean needsValidating = false;
private boolean destroyed = false;
private boolean visiting = false;
@@ -78,6 +79,7 @@ public class TileMonitor extends TileGeneric
public void onLoad()
{
super.onLoad();
needsValidating = true;
TickScheduler.schedule( this );
}
@@ -148,6 +150,12 @@ public class TileMonitor extends TileGeneric
@Override
public void blockTick()
{
if( needsValidating )
{
needsValidating = false;
validate();
}
if( needsUpdate )
{
needsUpdate = false;
@@ -164,7 +172,7 @@ public class TileMonitor extends TileGeneric
{
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 )
@@ -208,7 +216,7 @@ public class TileMonitor extends TileGeneric
{
if( serverMonitor != null ) return serverMonitor;
TileMonitor origin = getOrigin();
TileMonitor origin = getOrigin().getMonitor();
if( origin == null ) return null;
return serverMonitor = origin.serverMonitor;
@@ -229,7 +237,7 @@ public class TileMonitor extends TileGeneric
{
for( int y = 0; y < height; y++ )
{
TileMonitor monitor = getNeighbour( x, y );
TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor != null ) monitor.serverMonitor = serverMonitor;
}
}
@@ -374,24 +382,24 @@ public class TileMonitor extends TileGeneric
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();
@@ -401,7 +409,7 @@ public class TileMonitor extends TileGeneric
return getSimilarMonitorAt( pos.relative( right, xOffset ).relative( down, yOffset ) );
}
private TileMonitor getOrigin()
private MonitorState getOrigin()
{
return getNeighbour( 0, 0 );
}
@@ -425,7 +433,7 @@ public class TileMonitor extends TileGeneric
{
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;
@@ -453,7 +461,7 @@ public class TileMonitor extends TileGeneric
{
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;
@@ -469,13 +477,13 @@ public class TileMonitor extends TileGeneric
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;
@@ -483,13 +491,13 @@ public class TileMonitor extends TileGeneric
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;
@@ -497,13 +505,13 @@ public class TileMonitor extends TileGeneric
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;
@@ -511,13 +519,13 @@ public class TileMonitor extends TileGeneric
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;
@@ -546,22 +554,22 @@ public class TileMonitor extends TileGeneric
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;
@@ -572,11 +580,11 @@ public class TileMonitor extends TileGeneric
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 );
@@ -590,7 +598,7 @@ public class TileMonitor extends TileGeneric
{
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
@@ -606,17 +614,17 @@ public class TileMonitor extends TileGeneric
}
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) );
}
@@ -630,6 +638,38 @@ public class TileMonitor extends TileGeneric
}
}
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
@@ -657,7 +697,7 @@ public class TileMonitor extends TileGeneric
{
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 )
@@ -683,8 +723,8 @@ public class TileMonitor extends TileGeneric
@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();

View File

@@ -438,7 +438,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
private void updateBlockState( boolean top, boolean bottom )
{
if( remove ) return;
if( remove || level == null ) return;
BlockState state = getBlockState();
if( state.getValue( BlockPrinter.TOP ) == top & state.getValue( BlockPrinter.BOTTOM ) == bottom ) return;

View File

@@ -47,7 +47,10 @@ public class TileSpeaker extends TileGeneric implements ITickableTileEntity
public void setRemoved()
{
super.setRemoved();
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
if( level != null && !level.isClientSide )
{
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
}
}
@Nonnull

View File

@@ -681,6 +681,7 @@ if http then
local nativeCheckURL = http.checkURL
http.checkURLAsync = nativeCheckURL
http.checkURL = function(_url)
expect(1, _url, "string")
local ok, err = nativeCheckURL(_url)
if not ok then return ok, err end

View File

@@ -351,12 +351,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

View File

@@ -42,7 +42,7 @@ for _, v in ipairs(valid_types) do valid_types[v] = true end
-- will error.
function define(name, options)
expect(1, name, "string")
expect(2, options, "table", nil)
expect(2, options, "table", "nil")
if options then
options = {

View File

@@ -1,3 +1,17 @@
# New features in CC: Tweaked 1.97.1
* Add config options to limit total bandwidth used by the HTTP API.
And several bug fixes:
* Fix selected slot indicator not appearing in turtle interface.
* Fix crash when printers are placed as part of world generation.
* Fix crash when breaking a speaker on a multiplayer world.
* Prevent issue in rednet when the message ID is NaN.
* Fix `help` program crashing when terminal changes width.
* Ensure monitors are well-formed when placed, preventing graphical glitches
when using Carry On or Quark.
* Accept several more extensions in the websocket client.
* Prevent `wget` crashing when given an invalid URL and no filename.
# New features in CC: Tweaked 1.97.0
* Update several translations (Anavrins, Jummit, Naheulf).

View File

@@ -1,27 +1,15 @@
New features in CC: Tweaked 1.97.0
* Update several translations (Anavrins, Jummit, Naheulf).
* Add button to view a computer's folder to `/computercraft dump`.
* Allow cleaning dyed turtles in a cauldron.
* Add scale subcommand to `monitor` program (MCJack123).
* Add option to make `textutils.serialize` not write an indent (magiczocker10).
* Allow comparing vectors using `==` (fatboychummy).
* Improve HTTP error messages for SSL failures.
* Allow `craft` program to craft unlimited items (fatboychummy).
* Impose some limits on various command queues.
* Add buttons to shutdown and terminate to computer GUIs.
* Add program subcompletion to several programs (Wojbie).
* Update the `help` program to accept and (partially) highlight markdown files.
* Remove config option for the debug API.
* Allow setting the subprotocol header for websockets.
New features in CC: Tweaked 1.97.1
* Add config options to limit total bandwidth used by the HTTP API.
And several bug fixes:
* Fix NPE when using a treasure disk when no treasure disks are available.
* Prevent command computers discarding command ouput when certain game rules are off.
* Fix turtles not updating peripherals when upgrades are unequipped (Ronan-H).
* Fix computers not shutting down on fatal errors within the Lua VM.
* Speakers now correctly stop playing when broken, and sound follows noisy turtles and pocket computers.
* Update the `wget` to be more resiliant in the face of user-errors.
* Fix exiting `paint` typing "e" in the shell.
* Fix selected slot indicator not appearing in turtle interface.
* Fix crash when printers are placed as part of world generation.
* Fix crash when breaking a speaker on a multiplayer world.
* Prevent issue in rednet when the message ID is NaN.
* Fix `help` program crashing when terminal changes width.
* Ensure monitors are well-formed when placed, preventing graphical glitches
when using Carry On or Quark.
* Accept several more extensions in the websocket client.
* Prevent `wget` crashing when given an invalid URL and no filename.
Type "help changelog" to see the full version history.

View File

@@ -21,3 +21,4 @@ The "equip" programs let you add upgrades to a turtle without crafting.
You can change the color of a disk by crafting or right clicking it with dye.
You can print on a printed page again to get multiple colors.
Holding the Ctrl and T keys terminates the running program.
You can drag and drop files onto an open computer to upload them.

View File

@@ -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

View File

@@ -69,7 +69,7 @@ if run then
printError(err)
end
else
local sFile = tArgs[1] or getFilename(url)
local sFile = tArgs[1] or getFilename(url) or url
local sPath = shell.resolve(sFile)
if fs.exists(sPath) then
print("File already exists")

View File

@@ -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<Any?>)
class AsyncRunner : NullApiEnvironment() {
private val eventStream: Channel<Array<Any?>> = Channel(Int.MAX_VALUE)
private val apis: MutableList<ILuaAPI> = 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 <T : ILuaAPI> addApi(api: T): T {
apis.add(api)
api.startup()
return api
}
suspend fun resultOf(toRun: MethodResult): Array<Any?> {
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<Any?> {
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<Any?> = 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()
}
}
}
}
}

View File

@@ -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()}"
}
}
}
}

View File

@@ -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)
}

View File

@@ -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")
}
}

View File

@@ -3,6 +3,7 @@ package dan200.computercraft.ingame.api
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 @@ fun TestContext.setBlock(pos: BlockPos, state: BlockState) {
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.
*/

View File

@@ -1,3 +1,5 @@
-- TurtleTest.`Use compostors`
test.eq(true, turtle.dropDown(), "Drop items into compostor")
test.ok()

View File

@@ -1,3 +1,5 @@
-- TurtleTest.`Cleaned with cauldrons`
local old_details = turtle.getItemDetail(1, true)
test.assert(turtle.place(), "Dyed turtle")

View File

@@ -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()

View File

@@ -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

View File

@@ -1,3 +1,3 @@
{
"computer": 12
"computer": 14
}

View File

@@ -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
}

View File

@@ -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
}