1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-21 06:26:55 +00:00

Merge branch 'mc-1.16.x' into mc-1.17.x

This commit is contained in:
Jonathan Coates 2021-10-06 18:10:45 +01:00
commit eba26dedab
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
36 changed files with 951 additions and 195 deletions

View File

@ -10,6 +10,7 @@ This event is normally handled by @{http.Websocket.receive}, but it can also be
1. @{string}: The event name.
2. @{string}: The URL of the WebSocket.
3. @{string}: The contents of the message.
4. @{boolean}: Whether this is a binary message.
## Example
Prints a message sent by a WebSocket:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -61,6 +61,8 @@
(table space)
(index no-space))
(allow-clarifying-parens true)
;; colours imports from colors, and we don't handle that right now.
;; keys is entirely dynamic, so we skip it.
(dynamic-modules colours keys _G)

View File

@ -43,6 +43,10 @@ public class NoTermComputerScreen<T extends ContainerComputerBase> extends Scree
@Override
protected void init()
{
this.passEvents = true; // to allow gui click events pass through mouseHelper protection (see MouseHelper.OnPres:105 code string)
minecraft.mouseHandler.grabMouse();
minecraft.screen = this;
super.init();
minecraft.keyboardHandler.setSendRepeatsToGui( true );
@ -66,6 +70,13 @@ public class NoTermComputerScreen<T extends ContainerComputerBase> extends Scree
terminal.update();
}
@Override
public boolean mouseScrolled( double pMouseX, double pMouseY, double pDelta )
{
minecraft.player.getInventory().swapPaint( pDelta );
return super.mouseScrolled( pMouseX, pMouseY, pDelta );
}
@Override
public void onClose()
{

View File

@ -127,8 +127,8 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
FixedWidthFontRenderer.drawBlocker(
transform.last().pose(), renderer,
(float) -TileMonitor.RENDER_MARGIN, (float) TileMonitor.RENDER_MARGIN,
(float) (xSize + 2 * TileMonitor.RENDER_MARGIN), (float) -(ySize + TileMonitor.RENDER_MARGIN * 2)
-MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
// Force a flush of the blocker. WorldRenderer.updateCameraAndRender will "finish" all the built-in

View File

@ -5,6 +5,7 @@
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.peripheral.IComputerAccess;
@ -30,6 +31,11 @@ public abstract class ComputerAccess implements IComputerAccess
public void unmountAll()
{
FileSystem fileSystem = environment.getFileSystem();
if( !mounts.isEmpty() )
{
ComputerCraft.log.warn( "Peripheral or API called mount but did not call unmount for {}", mounts );
}
for( String mount : mounts )
{
fileSystem.unmount( mount );

View File

@ -419,6 +419,7 @@ public class Terminal
{
if( c >= '0' && c <= '9' ) return c - '0';
if( c >= 'a' && c <= 'f' ) return c - 'a' + 10;
if( c >= 'A' && c <= 'F' ) return c - 'A' + 10;
return 15 - def.ordinal();
}
}

View File

@ -6,6 +6,7 @@
package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.*;
@ -15,6 +16,7 @@ import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.api.peripheral.NotAttachedException;
import dan200.computercraft.core.apis.PeripheralAPI;
import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
@ -23,10 +25,7 @@ import net.minecraft.world.level.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -310,6 +309,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
private final String type;
private final Map<String, PeripheralMethod> methodMap;
private volatile boolean attached;
private final Set<String> mounts = new HashSet<>();
RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name )
{
this.element = element;
@ -323,6 +325,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
public void attach()
{
attached = true;
peripheral.attach( this );
computer.queueEvent( "peripheral", getAttachmentName() );
}
@ -331,6 +334,18 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
{
peripheral.detach( this );
computer.queueEvent( "peripheral_detach", getAttachmentName() );
attached = false;
synchronized( this )
{
if( !mounts.isEmpty() )
{
ComputerCraft.log.warn( "Peripheral {} called mount but did not call unmount for {}", peripheral, mounts );
}
for( String mount : mounts ) computer.unmount( mount );
mounts.clear();
}
}
public String getType()
@ -353,44 +368,60 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
// IComputerAccess implementation
@Override
public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount )
public synchronized String mount( @Nonnull String desiredLocation, @Nonnull IMount mount )
{
return computer.mount( desiredLocation, mount, name );
if( !attached ) throw new NotAttachedException();
String mounted = computer.mount( desiredLocation, mount, name );
mounts.add( mounted );
return mounted;
}
@Override
public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName )
public synchronized String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName )
{
return computer.mount( desiredLocation, mount, driveName );
if( !attached ) throw new NotAttachedException();
String mounted = computer.mount( desiredLocation, mount, driveName );
mounts.add( mounted );
return mounted;
}
@Override
public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount )
public synchronized String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount )
{
return computer.mountWritable( desiredLocation, mount, name );
if( !attached ) throw new NotAttachedException();
String mounted = computer.mountWritable( desiredLocation, mount, name );
mounts.add( mounted );
return mounted;
}
@Override
public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName )
public synchronized String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
return computer.mountWritable( desiredLocation, mount, driveName );
if( !attached ) throw new NotAttachedException();
String mounted = computer.mountWritable( desiredLocation, mount, driveName );
mounts.add( mounted );
return mounted;
}
@Override
public void unmount( String location )
public synchronized void unmount( String location )
{
if( !attached ) throw new NotAttachedException();
computer.unmount( location );
mounts.remove( location );
}
@Override
public int getID()
{
if( !attached ) throw new NotAttachedException();
return computer.getID();
}
@Override
public void queueEvent( @Nonnull String event, Object... arguments )
{
if( !attached ) throw new NotAttachedException();
computer.queueEvent( event, arguments );
}
@ -398,6 +429,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
@Override
public IWorkMonitor getMainThreadMonitor()
{
if( !attached ) throw new NotAttachedException();
return computer.getMainThreadMonitor();
}
@ -405,6 +437,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
@Override
public String getAttachmentName()
{
if( !attached ) throw new NotAttachedException();
return name;
}
@ -412,6 +445,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
@Override
public Map<String, IPeripheral> getAvailablePeripherals()
{
if( !attached ) throw new NotAttachedException();
synchronized( element.getRemotePeripherals() )
{
return ImmutableMap.copyOf( element.getRemotePeripherals() );
@ -422,6 +456,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
if( !attached ) throw new NotAttachedException();
synchronized( element.getRemotePeripherals() )
{
return element.getRemotePeripherals().get( name );

View File

@ -5,3 +5,5 @@ public net.minecraft.client.renderer.ItemInHandRenderer m_109346_(Lcom/mojang/bl
# ClientTableFormatter
public net.minecraft.client.gui.components.ChatComponent m_93787_(Lnet/minecraft/network/chat/Component;I)V # addMessage
public net.minecraft.client.gui.components.ChatComponent m_93803_(I)V # removeById
# NoTermComputerScreen
public net.minecraft.client.Minecraft f_91080_ # screen

View File

@ -11,7 +11,7 @@ license="ComputerCraft Public License (https://raw.githubusercontent.com/dan200/
[[mods]]
modId="computercraft"
version="${version}"
version="1.2.3.4"
displayName="CC: Tweaked"
description='''
CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.

View File

@ -42,7 +42,7 @@
"commands.computercraft.synopsis": "Verschiedene Befehle um Computer zu kontrollieren.",
"commands.computercraft.desc": "Der /computercraft Befehl enthält verschiedene Werkzeuge um Computer zu debuggen, kontrollieren oder mit ihnen zu interagieren.",
"commands.computercraft.help.synopsis": "Zeigt die Hilfe für den angegebenen Befehl",
"commands.computercraft.help.desc": "Zeigt diese Hilfe",
"commands.computercraft.help.desc": "Zeigt diese Hilfe Nachricht",
"commands.computercraft.help.no_children": "%s hat keine Unterbefehle",
"commands.computercraft.help.no_command": "Unbekannter Befehl '%s'",
"commands.computercraft.dump.synopsis": "Zeigt den Status eines Computers.",

View File

@ -109,5 +109,23 @@
"tracking_field.computercraft.coroutines_dead.name": "Coroutines mortes",
"gui.computercraft.tooltip.copy": "Copier dans le Presse-Papiers",
"gui.computercraft.tooltip.computer_id": "ID d'ordinateur : %s",
"gui.computercraft.tooltip.disk_id": "ID de disque : %s"
"gui.computercraft.tooltip.disk_id": "ID de disque : %s",
"gui.computercraft.tooltip.turn_on.key": "Ternir Ctrl+R",
"gui.computercraft.tooltip.turn_off": "Éteindre cet ordinateur",
"gui.computercraft.tooltip.turn_off.key": "Tenir Ctrl+S",
"gui.computercraft.tooltip.terminate": "Arrêter le programme en cours d'éxecution",
"gui.computercraft.tooltip.terminate.key": "Tenir Ctrl+T",
"gui.computercraft.upload.overwrite": "Les fichiers seraient écrasés",
"gui.computercraft.upload.overwrite.detail": "Les fichiers suivants seront écrasés lors de l'envoie. Continuer ?%s",
"gui.computercraft.upload.overwrite_button": "Écraser",
"gui.computercraft.upload.success": "Envoie avec succès",
"gui.computercraft.upload.success.msg": "Le fichier %d est envoyé.",
"gui.computercraft.upload.failed": "Echec de l'envoie",
"gui.computercraft.upload.failed.out_of_space": "Il n'y a pas assez de place sur cet ordinateur pour ce fichier.",
"gui.computercraft.upload.failed.computer_off": "Vous devez allumer cet ordinateur avant d'envoyer ce fichier.",
"gui.computercraft.upload.failed.too_much": "Votre fichier est trop lourd pour être envoyé.",
"gui.computercraft.upload.failed.overwrite_dir": "%s ne peut pas être envoyé, il y a déjà un dossier avec le même nom.",
"gui.computercraft.upload.failed.generic": "Echec de l'envoie des fichiers(%s)",
"commands.computercraft.dump.open_path": "Voir les fichiers de cet ordinateur",
"gui.computercraft.tooltip.turn_on": "Allumer cet ordinateur"
}

View File

@ -25,9 +25,17 @@ CHANNEL_BROADCAST = 65535
--- The channel used by the Rednet API to repeat messages.
CHANNEL_REPEAT = 65533
--- The number of channels rednet reserves for computer IDs. Computers with IDs
-- greater or equal to this limit wrap around to 0.
MAX_ID_CHANNELS = 65500
local tReceivedMessages = {}
local tReceivedMessageTimeouts = {}
local tHostnames = {}
local nClearTimer
local function id_as_channel(id)
return (id or os.getComputerID()) % MAX_ID_CHANNELS
end
--[[- Opens a modem with the given @{peripheral} name, allowing it to send and
receive messages over rednet.
@ -47,7 +55,7 @@ function open(modem)
if peripheral.getType(modem) ~= "modem" then
error("No such modem: " .. modem, 2)
end
peripheral.call(modem, "open", os.getComputerID())
peripheral.call(modem, "open", id_as_channel())
peripheral.call(modem, "open", CHANNEL_BROADCAST)
end
@ -64,7 +72,7 @@ function close(modem)
if peripheral.getType(modem) ~= "modem" then
error("No such modem: " .. modem, 2)
end
peripheral.call(modem, "close", os.getComputerID())
peripheral.call(modem, "close", id_as_channel())
peripheral.call(modem, "close", CHANNEL_BROADCAST)
else
-- Close all modems
@ -87,7 +95,7 @@ function isOpen(modem)
if modem then
-- Check if a specific modem is open
if peripheral.getType(modem) == "modem" then
return peripheral.call(modem, "isOpen", os.getComputerID()) and peripheral.call(modem, "isOpen", CHANNEL_BROADCAST)
return peripheral.call(modem, "isOpen", id_as_channel()) and peripheral.call(modem, "isOpen", CHANNEL_BROADCAST)
end
else
-- Check if any modem is open
@ -130,14 +138,15 @@ function send(nRecipient, message, sProtocol)
-- We could do other things to guarantee uniqueness, but we really don't need to
-- Store it to ensure we don't get our own messages back
local nMessageID = math.random(1, 2147483647)
tReceivedMessages[nMessageID] = true
tReceivedMessageTimeouts[os.startTimer(30)] = nMessageID
tReceivedMessages[nMessageID] = os.clock() + 9.5
if not nClearTimer then nClearTimer = os.startTimer(10) end
-- Create the message
local nReplyChannel = os.getComputerID()
local nReplyChannel = id_as_channel()
local tMessage = {
nMessageID = nMessageID,
nRecipient = nRecipient,
nSender = os.getComputerID(),
message = message,
sProtocol = sProtocol,
}
@ -145,10 +154,14 @@ function send(nRecipient, message, sProtocol)
local sent = false
if nRecipient == os.getComputerID() then
-- Loopback to ourselves
os.queueEvent("rednet_message", nReplyChannel, message, sProtocol)
os.queueEvent("rednet_message", os.getComputerID(), message, sProtocol)
sent = true
else
-- Send on all open modems, to the target and to repeaters
if nRecipient ~= CHANNEL_BROADCAST then
nRecipient = id_as_channel(nRecipient)
end
for _, sModem in ipairs(peripheral.getNames()) do
if isOpen(sModem) then
peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage)
@ -390,13 +403,14 @@ function run()
if sEvent == "modem_message" then
-- 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 isOpen(sModem) and (nChannel == id_as_channel() or nChannel == CHANNEL_BROADCAST) then
if type(tMessage) == "table" and type(tMessage.nMessageID) == "number"
and tMessage.nMessageID == tMessage.nMessageID and not tReceivedMessages[tMessage.nMessageID]
and ((tMessage.nRecipient and tMessage.nRecipient == os.getComputerID()) or nChannel == CHANNEL_BROADCAST)
then
tReceivedMessages[tMessage.nMessageID] = true
tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
os.queueEvent("rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol)
tReceivedMessages[tMessage.nMessageID] = os.clock() + 9.5
if not nClearTimer then nClearTimer = os.startTimer(10) end
os.queueEvent("rednet_message", tMessage.nSender or nReplyChannel, tMessage.message, tMessage.sProtocol)
end
end
@ -414,14 +428,15 @@ function run()
end
end
elseif sEvent == "timer" then
elseif sEvent == "timer" and p1 == nClearTimer then
-- Got a timer event, use it to clear the event queue
local nTimer = p1
local nMessage = tReceivedMessageTimeouts[nTimer]
if nMessage then
tReceivedMessageTimeouts[nTimer] = nil
tReceivedMessages[nMessage] = nil
nClearTimer = nil
local nNow, bHasMore = os.clock(), nil
for nMessageID, nDeadline in pairs(tReceivedMessages) do
if nDeadline <= nNow then tReceivedMessages[nMessageID] = nil
else bHasMore = true end
end
nClearTimer = bHasMore and os.startTimer(10)
end
end
end

View File

@ -265,6 +265,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
if #sTextColor ~= #sText or #sBackgroundColor ~= #sText then
error("Arguments must be the same length", 2)
end
sTextColor = sTextColor:lower()
sBackgroundColor = sBackgroundColor:lower()
internalBlit(sText, sTextColor, sBackgroundColor)
end

View File

@ -14,6 +14,10 @@ else
print(#tModems .. " modems found.")
end
local function idAsChannel(id)
return (id or os.getComputerID()) % rednet.MAX_ID_CHANNELS
end
local function open(nChannel)
for n = 1, #tModems do
local sModem = tModems[n]
@ -53,7 +57,7 @@ local ok, error = pcall(function()
for n = 1, #tModems do
local sOtherModem = tModems[n]
peripheral.call(sOtherModem, "transmit", rednet.CHANNEL_REPEAT, nReplyChannel, tMessage)
peripheral.call(sOtherModem, "transmit", tMessage.nRecipient, nReplyChannel, tMessage)
peripheral.call(sOtherModem, "transmit", idAsChannel(tMessage.nRecipient), nReplyChannel, tMessage)
end
-- Log the event

View File

@ -97,7 +97,7 @@ public class ComputerTestDelegate
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." );
Terminal term = new Terminal( 78, 20 );
Terminal term = new Terminal( 80, 30 );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files

View File

@ -83,4 +83,144 @@ describe("The rednet library", function()
expect(rednet.lookup("a_protocol", "a_hostname")):eq(os.getComputerID())
end)
end)
describe("on fake computers", function()
local fake_computer = require "support.fake_computer"
local debugx = require "support.debug_ext"
local function computer_with_rednet(id, fn, options)
local computer = fake_computer.make_computer(id, function(env)
local fns = { env.rednet.run }
if options and options.rep then
fns[#fns + 1] = function() env.dofile("rom/programs/rednet/repeat.lua") end
end
if fn then
fns[#fns + 1] = function()
if options and options.open then
env.rednet.open("back")
env.os.queueEvent("x") env.os.pullEvent("x")
end
return fn(env.rednet, env)
end
end
return parallel.waitForAny(table.unpack(fns))
end)
local modem = fake_computer.add_modem(computer, "back")
fake_computer.add_api(computer, "rom/apis/rednet.lua")
return computer, modem
end
it("opens and closes channels", function()
local id = math.random(256)
local computer = computer_with_rednet(id, function(rednet)
expect(rednet.isOpen()):eq(false)
rednet.open("back")
rednet.open("front")
expect(rednet.isOpen()):eq(true)
expect(rednet.isOpen("back")):eq(true)
expect(rednet.isOpen("front")):eq(true)
rednet.close("back")
expect(rednet.isOpen("back")):eq(false)
expect(rednet.isOpen("front")):eq(true)
expect(rednet.isOpen()):eq(true)
rednet.close()
expect(rednet.isOpen("back")):eq(false)
expect(rednet.isOpen("front")):eq(false)
expect(rednet.isOpen()):eq(false)
end)
fake_computer.add_modem(computer, "front")
fake_computer.run_all { computer }
end)
it("sends and receives rednet messages", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet)
rednet.send(2, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, function(rednet)
local id, message = rednet.receive()
expect(id):eq(1)
expect(message):eq("Hello")
end, { open = true })
fake_computer.add_modem_edge(modem_1, modem_2)
fake_computer.run_all { computer_1, computer_2 }
end)
it("repeats messages between computers", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet)
rednet.send(3, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, nil, { open = true, rep = true })
local computer_3, modem_3 = computer_with_rednet(3, function(rednet)
local id, message = rednet.receive()
expect(id):eq(1)
expect(message):eq("Hello")
end, { open = true })
fake_computer.add_modem_edge(modem_1, modem_2)
fake_computer.add_modem_edge(modem_2, modem_3)
fake_computer.run_all({ computer_1, computer_2, computer_3 }, { computer_1, computer_3 })
end)
it("repeats messages between computers with massive ids", function()
local id_1, id_3 = 24283947, 93428798
local computer_1, modem_1 = computer_with_rednet(id_1, function(rednet)
rednet.send(id_3, "Hello")
local id, message = rednet.receive()
expect { id, message }:same { id_3, "World" }
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, nil, { open = true, rep = true })
local computer_3, modem_3 = computer_with_rednet(id_3, function(rednet)
rednet.send(id_1, "World")
local id, message = rednet.receive()
expect { id, message }:same { id_1, "Hello" }
end, { open = true })
fake_computer.add_modem_edge(modem_1, modem_2)
fake_computer.add_modem_edge(modem_2, modem_3)
fake_computer.run_all({ computer_1, computer_2, computer_3 }, { computer_1, computer_3 })
end)
it("ignores duplicate messages", function()
local computer_1, modem_1 = computer_with_rednet(1, function(rednet)
rednet.send(2, "Hello")
end, { open = true })
local computer_2, modem_2 = computer_with_rednet(2, function(rednet, env)
local id, message = rednet.receive()
expect { id, message }:same { 1, "Hello" }
local id = rednet.receive(nil, 1)
expect(id):eq(nil)
env.sleep(10)
-- Ensure our pending message store is empty. Bit ugly to prod internals, but there's no other way.
expect(debugx.getupvalue(rednet.run, "tReceivedMessages")):same({})
expect(debugx.getupvalue(rednet.run, "nClearTimer")):eq(nil)
end, { open = true })
local computer_3, modem_3 = computer_with_rednet(3, nil, { open = true, rep = true })
fake_computer.add_modem_edge(modem_1, modem_3)
fake_computer.add_modem_edge(modem_3, modem_2)
local computer_4, modem_4 = computer_with_rednet(4, nil, { open = true, rep = true })
fake_computer.add_modem_edge(modem_1, modem_4)
fake_computer.add_modem_edge(modem_4, modem_2)
local computers = { computer_1, computer_2, computer_3, computer_4 }
fake_computer.run_all(computers, false)
fake_computer.advance_all(computers, 1)
fake_computer.run_all(computers, { computer_1 })
fake_computer.advance_all(computers, 10)
fake_computer.run_all(computers, { computer_1, computer_2 })
end)
end)
end)

View File

@ -0,0 +1,9 @@
local function getupvalue(fn, name)
for i = 1, debug.getinfo(fn, "u").nups do
local up_name, value = debug.getupvalue(fn, i)
if up_name == name then return value end
end
error("Cannot find upvalue with name " .. name, 2)
end
return { getupvalue = getupvalue }

View File

@ -0,0 +1,180 @@
local function keys(tbl)
local keys = {}
for k in pairs(tbl) do keys[#keys + 1] = k end
return keys
end
local safe_globals = {
"assert", "bit32", "coroutine", "debug", "error", "fs", "getmetatable", "io", "ipairs", "math", "next", "pairs",
"pcall", "print", "printError", "rawequal", "rawget", "rawlen", "rawset", "select", "setmetatable", "string",
"table", "term", "textutils", "tonumber", "tostring", "type", "utf8", "xpcall",
}
--- Create a fake computer.
local function make_computer(id, fn)
local env = setmetatable({}, _G)
local peripherals = {}
local pending_timers, next_timer, clock = {}, 0, 0
local events = { { n = 1, env } }
local function queue_event(...) events[#events + 1] = table.pack(...) end
for _, k in pairs(safe_globals) do env[k] = _G[k] end
env.peripheral = {
getNames = function() return keys(peripherals) end,
isPresent = function(name) return peripherals[name] ~= nil end,
getType = function(name) return peripherals[name] and getmetatable(peripherals[name]).type end,
getMethods = function(name) return peripherals[name] and keys(peripherals[name]) end,
call = function(name, method, ...)
local p = peripherals[name]
if p then return p[method](...) end
return nil
end,
wrap = function(name) return peripherals[name] end,
}
env.os = {
getComputerID = function() return id end,
queueEvent = queue_event,
pullEventRaw = coroutine.yield,
pullEvent = function(filter)
local event_data = table.pack(coroutine.yield(filter))
if event_data[1] == "terminate" then error("Terminated", 0) end
return table.unpack(event_data, 1, event_data.n)
end,
startTimer = function(delay)
local t = next_timer
pending_timers[t], next_timer = clock + delay, next_timer + 1
return t
end,
clock = function() return clock end,
sleep = function(time)
local timer = env.os.startTimer(time or 0)
repeat local _, id = env.os.pullEvent("timer") until id == timer
end,
}
env.sleep = env.os.sleep
env.dofile = function(path)
local fn, err = loadfile(path, nil, env)
if fn then return fn() else error(err, 2) end
end
local co = coroutine.create(fn)
local filter = nil
local function step()
while true do
if #events == 0 or coroutine.status(co) == "dead" then return false end
local ev = table.remove(events, 1)
if filter == nil or ev[1] == filter or ev[1] == "terminated" then
local ok, result = coroutine.resume(co, table.unpack(ev, 1, ev.n))
if not ok then
if type(result) == "table" and result.trace == nil then result.trace = debug.traceback(co) end
error(result, 0)
end
filter = result
return true
end
end
end
local function advance(dt)
clock = clock + dt
for id, clk in pairs(pending_timers) do
if clk <= clock then
queue_event("timer", id)
pending_timers[id] = nil
end
end
end
return { env = env, peripherals = peripherals, queue_event = queue_event, step = step, co = co, advance = advance }
end
local function parse_channel(c)
if c < 0 or c > 65535 then error("Expected number in range 0-65535", 3) end
return c
end
--- Add a modem to a computer on a particular side
local function add_modem(owner, side)
local open, adjacent = {}, {}
local peripheral = setmetatable({
open = function(channel) open[parse_channel(channel)] = true end,
close = function(channel) open[parse_channel(channel)] = false end,
closeAll = function(channel) open = {} end,
isOpen = function(channel) return open[parse_channel(channel)] == true end,
transmit = function(channel, reply_channel, payload)
channel, reply_channel = parse_channel(channel), parse_channel(reply_channel)
for _, adjacent in pairs(adjacent) do
if adjacent.open[channel] then
adjacent.owner.queue_event("modem_message", adjacent.side, channel, reply_channel, payload, 123)
end
end
end,
}, { type = "modem" })
owner.peripherals[side] = peripheral
return { adjacent = adjacent, side = side, owner = owner, open = open }
end
local function add_modem_edge(modem1, modem2)
table.insert(modem1.adjacent, modem2)
table.insert(modem2.adjacent, modem1)
end
--- Load an API into the computer's environment.
local function add_api(computer, path)
local name = fs.getName(path)
if name:sub(-4) == ".lua" then name = name:sub(1, -5) end
local child_env = {}
setmetatable(child_env, { __index = computer.env })
assert(loadfile(path, nil, child_env))()
local api = {}
for k, v in pairs(child_env) do api[k] = v end
computer.env[name] = api
end
--- Step all computers forward by one event.
local function step_all(computers)
local any = false
for _, computer in pairs(computers) do
if computer.step() then any = true end
end
return any
end
--- Run all computers until their event queue is empty.
local function run_all(computers, require_done)
while step_all(computers) do end
if require_done ~= false then
if type(require_done) == "table" then
for _, v in ipairs(require_done) do require_done[v] = true end
end
for _, computer in pairs(computers) do
if coroutine.status(computer.co) ~= "dead" and (type(require_done) ~= "table" or require_done[computer]) then
error(debug.traceback(computer.co, ("Computer #%d did not shutdown"):format(computer.env.os.getComputerID())), 0)
end
end
end
end
--- Advance all computers by a given time.
local function advance_all(computers, dt)
for _, computer in pairs(computers) do computer.advance(dt) end
end
return {
make_computer = make_computer,
add_modem = add_modem,
add_modem_edge = add_modem_edge,
add_api = add_api,
step_all = step_all,
run_all = run_all,
advance_all = advance_all,
}

View File

@ -1,8 +1,11 @@
package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.*
import dan200.computercraft.ingame.api.Timeouts.CLIENT_TIMEOUT
import dan200.computercraft.shared.Capabilities
import dan200.computercraft.shared.Registry
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer
import dan200.computercraft.shared.peripheral.monitor.TileMonitor
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
@ -44,15 +47,14 @@ class Monitor_Test {
}
}
@GameTest(batch = "client:Monitor_Test.Looks_acceptable", timeoutTicks = 400)
fun Looks_acceptable(helper: GameTestHelper) = helper.sequence {
private fun looksAcceptable(helper: GameTestHelper, renderer: MonitorRenderer) = helper.sequence {
this
.thenExecute { helper.normaliseScene() }
.thenExecute {
ComputerCraft.monitorRenderer = renderer
helper.positionAtArmorStand()
// Get the monitor and peripheral. This forces us to create a server monitor at this location.
val monitor = helper.getBlockEntity(BlockPos(2, 2, 2)) as TileMonitor
val monitor = helper.getBlockEntity(BlockPos(2, 2, 3), Registry.ModBlockEntities.MONITOR_ADVANCED.get())
monitor.getCapability(Capabilities.CAPABILITY_PERIPHERAL)
val terminal = monitor.cachedServerMonitor.terminal
@ -64,4 +66,21 @@ class Monitor_Test {
}
.thenScreenshot()
}
@GameTest(batch = "client:Monitor_Test.Looks_acceptable", timeoutTicks = CLIENT_TIMEOUT, template = LOOKS_ACCEPTABLE)
fun Looks_acceptable(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.TBO)
@GameTest(batch = "client:Monitor_Test.Looks_acceptable_dark", timeoutTicks = CLIENT_TIMEOUT, template = LOOKS_ACCEPTABLE_DARK)
fun Looks_acceptable_dark(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.TBO)
@GameTest(batch = "client:Monitor_Test.Looks_acceptable_vbo", timeoutTicks = CLIENT_TIMEOUT, template = LOOKS_ACCEPTABLE)
fun Looks_acceptable_vbo(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.VBO)
@GameTest(batch = "client:Monitor_Test.Looks_acceptable_dark_vbo", timeoutTicks = CLIENT_TIMEOUT, template = LOOKS_ACCEPTABLE_DARK)
fun Looks_acceptable_dark_vbo(helper: GameTestHelper) = looksAcceptable(helper, renderer = MonitorRenderer.VBO)
private companion object {
const val LOOKS_ACCEPTABLE = "looks_acceptable"
const val LOOKS_ACCEPTABLE_DARK = "looks_acceptable_dark"
}
}

View File

@ -0,0 +1,17 @@
package dan200.computercraft.ingame
import dan200.computercraft.ingame.api.Timeouts
import dan200.computercraft.ingame.api.positionAtArmorStand
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenScreenshot
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
class PrintoutTest {
@GameTest(batch = "client:Printout_Test.In_frame_at_night", timeoutTicks = Timeouts.CLIENT_TIMEOUT)
fun In_frame_at_night(helper: GameTestHelper) = helper.sequence {
this
.thenExecute { helper.positionAtArmorStand() }
.thenScreenshot()
}
}

View File

@ -1,12 +1,13 @@
package dan200.computercraft.ingame
import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT
import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenComputerOk
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
class Turtle_Test {
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Unequip_refreshes_peripheral(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -14,7 +15,7 @@ class Turtle_Test {
*
* @see [#537](https://github.com/SquidDev-CC/CC-Tweaked/issues/537)
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Shears_sheep(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -22,7 +23,7 @@ class Turtle_Test {
*
* @see [#518](https://github.com/SquidDev-CC/CC-Tweaked/issues/518)
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Place_lava(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -30,7 +31,7 @@ class Turtle_Test {
*
* @see [#385](https://github.com/SquidDev-CC/CC-Tweaked/issues/385)
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Place_waterlogged(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -38,7 +39,7 @@ class Turtle_Test {
*
* @see [#297](https://github.com/SquidDev-CC/CC-Tweaked/issues/297)
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Gather_lava(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -46,7 +47,7 @@ class Turtle_Test {
*
* @see [#258](https://github.com/SquidDev-CC/CC-Tweaked/issues/258)
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Hoe_dirt(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -54,14 +55,14 @@ class Turtle_Test {
*
* @see [#691](https://github.com/SquidDev-CC/CC-Tweaked/issues/691)
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Place_monitor(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
* Checks turtles can place into compostors. These are non-typical inventories, so
* worth testing.
*/
@GameTest(timeoutTicks = TIMEOUT)
@GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Use_compostors(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/**
@ -71,8 +72,4 @@ class Turtle_Test {
*/
@GameTest(required = false)
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
companion object {
const val TIMEOUT = 200
}
}

View File

@ -7,19 +7,32 @@ import net.minecraft.client.Screenshot
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestAssertPosException
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.GameTestSequence
import net.minecraft.world.entity.decoration.ArmorStand
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.levelgen.Heightmap
import java.nio.file.Files
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Supplier
import javax.imageio.ImageIO
object Times {
const val NOON: Long = 6000
}
/**
* Custom timeouts for various test types.
*/
object Timeouts {
const val COMPUTER_TIMEOUT: Int = 200
const val CLIENT_TIMEOUT: Int = 400
}
/**
* Wait until a computer has finished running and check it is OK.
@ -58,25 +71,31 @@ fun GameTestSequence.thenScreenshot(name: String? = null): GameTestSequence {
val suffix = if (name == null) "" else "-$name"
val fullName = "${parent.testName}$suffix"
val counter = AtomicInteger()
var counter = 0
val hasScreenshot = AtomicBoolean()
return this
// Wait until all chunks have been rendered and we're idle for an extended period.
.thenExecute { counter.set(0) }
.thenExecute { counter = 0 }
.thenWaitUntil {
if (Minecraft.getInstance().levelRenderer.hasRenderedAllChunks()) {
val idleFor = counter.getAndIncrement()
val renderer = Minecraft.getInstance().levelRenderer
if (renderer.chunkRenderDispatcher != null && renderer.hasRenderedAllChunks()) {
val idleFor = ++counter
if (idleFor <= 20) throw GameTestAssertException("Only idle for $idleFor ticks")
} else {
counter.set(0)
counter = 0
throw GameTestAssertException("Waiting for client to finish rendering")
}
}
// Now disable the GUI, take a screenshot and reenable it. We sleep either side to give the client time to do
// its thing.
.thenExecute { Minecraft.getInstance().options.hideGui = true }
.thenExecute {
Minecraft.getInstance().options.hideGui = true
hasScreenshot.set(false)
}
.thenIdle(5) // Some delay before/after to ensure the render thread has caught up.
.thenOnClient { screenshot("$fullName.png") }
.thenIdle(2)
.thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } }
.thenWaitUntil { if (!hasScreenshot.get()) throw GameTestAssertException("Screenshot does not exist") }
.thenExecute {
Minecraft.getInstance().options.hideGui = false
@ -87,21 +106,22 @@ fun GameTestSequence.thenScreenshot(name: String? = null): GameTestSequence {
if (!Files.exists(originalPath)) throw GameTestAssertException("$fullName does not exist. Use `/cctest promote' to create it.");
val screenshot = ImageIO.read(screenshotPath.toFile())
?: throw GameTestAssertException("Error reading screenshot from $screenshotPath")
val original = ImageIO.read(originalPath.toFile())
if (screenshot.width != original.width || screenshot.height != original.height) {
throw GameTestAssertException("$fullName screenshot is ${screenshot.width}x${screenshot.height} but original is ${original.width}x${original.height}")
}
if (ImageUtils.areSame(screenshot, original)) return@thenExecute
ImageUtils.writeDifference(screenshotsPath.resolve("$fullName.diff.png"), screenshot, original)
throw GameTestAssertException("Images are different.")
if (!ImageUtils.areSame(screenshot, original)) throw GameTestAssertException("Images are different.")
}
}
val GameTestHelper.testName: String get() = testInfo.testName
val GameTestHelper.structureName: String get() = testInfo.structureName
/**
* Modify a block state within the test.
*/
@ -113,38 +133,30 @@ fun GameTestHelper.sequence(run: GameTestSequence.() -> GameTestSequence) {
run(startSequence()).thenSucceed()
}
fun <T : BlockEntity> GameTestHelper.getBlockEntity(pos: BlockPos, type: BlockEntityType<T>): T {
val tile = getBlockEntity(pos)
@Suppress("UNCHECKED_CAST")
return when {
tile == null -> throw GameTestAssertPosException("Expected ${type.registryName}, but no tile was there", absolutePos(pos), pos, 0)
tile.type != type -> throw GameTestAssertPosException("Expected ${type.registryName} but got ${tile.type.registryName}", absolutePos(pos), pos, 0)
else -> tile as T
}
}
/**
* Set a block within the test structure.
*/
fun GameTestHelper.setBlock(pos: BlockPos, state: BlockInput) = state.place(level, absolutePos(pos), 3)
/**
* "Normalise" the current world in preparation for screenshots.
*
* Basically removes any dirt and replaces it with concrete.
*/
fun GameTestHelper.normaliseScene() {
val y = level.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, absolutePos(BlockPos.ZERO))
for (x in -100..100) {
for (z in -100..100) {
val pos = y.offset(x, -3, z)
val block = level.getBlockState(pos).block
if (block == Blocks.DIRT || block == Blocks.GRASS_BLOCK) {
level.setBlock(pos, Blocks.WHITE_CONCRETE.defaultBlockState(), 3)
}
}
}
}
/**
* Position the player at an armor stand.
*/
fun GameTestHelper.positionAtArmorStand() {
val entities = level.getEntities(null, bounds) { it.name.string == testName }
if (entities.size <= 0 || entities[0] !is ArmorStand) throw IllegalStateException("Cannot find armor stand")
val entities = level.getEntities(null, bounds) { it.name.string == structureName }
if (entities.size <= 0 || entities[0] !is ArmorStand) throw GameTestAssertException("Cannot find armor stand")
val stand = entities[0] as ArmorStand
val player = level.randomPlayer ?: throw NullPointerException("Player does not exist")
val player = level.randomPlayer ?: throw GameTestAssertException("Player does not exist")
player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot)
}
@ -153,7 +165,10 @@ fun GameTestHelper.positionAtArmorStand() {
class ClientTestHelper {
val minecraft: Minecraft = Minecraft.getInstance()
fun screenshot(name: String) {
Screenshot.grab(minecraft.gameDirectory, name, minecraft.mainRenderTarget) { TestMod.log.info(it.string) }
fun screenshot(name: String, callback: () -> Unit = {}) {
Screenshot.grab(minecraft.gameDirectory, name, minecraft.mainRenderTarget) {
TestMod.log.info(it.string)
callback()
}
}
}

View File

@ -59,13 +59,6 @@ class CCTestCommand
}
return 0;
} ) )
.then( literal( "runall" ).executes( context -> {
GameTestRegistry.forgetFailedTests();
MultipleTestTracker result = TestHooks.runTests();
result.addListener( new Callback( context.getSource(), result ) );
result.addFailureListener( x -> GameTestRegistry.rememberFailedTest( x.getTestFunction() ) );
return 0;
} ) )
.then( literal( "promote" ).executes( context -> {
if( !FMLLoader.getDist().isClient() ) return error( context.getSource(), "Cannot run on server" );
@ -94,6 +87,7 @@ class CCTestCommand
armorStand.readAdditionalSaveData( nbt );
armorStand.copyPosition( player );
armorStand.setCustomName( new TextComponent( info.getTestName() ) );
player.getLevel().addFreshEntity( armorStand );
return 0;
} ) )
);

View File

@ -18,9 +18,9 @@ public final class ImageUtils
private static final Logger LOG = LogManager.getLogger( ImageUtils.class );
/**
* Allow 0.08% of pixels to fail. This allows for slight differences at the edges.
* Allow 0.3% of pixels to fail. This allows for slight differences at the edges.
*/
private static final double PIXEL_THRESHOLD = 0.0008;
private static final double PIXEL_THRESHOLD = 0.003;
/**
* Maximum possible distance between two colours. Floating point differences means we need some fuzziness here.

View File

@ -7,6 +7,7 @@ package dan200.computercraft.ingame.mod;
import net.minecraft.ChatFormatting;
import net.minecraft.SharedConstants;
import dan200.computercraft.ingame.api.Times;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
@ -29,7 +30,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.stream.Collectors;
@Mod.EventBusSubscriber( modid = TestMod.MOD_ID )
public class TestHooks
@ -58,7 +58,7 @@ public class TestHooks
rules.getRule( GameRules.RULE_DOMOBSPAWNING ).set( false, server );
ServerLevel world = event.getServer().getLevel( Level.OVERWORLD );
if( world != null ) world.setDayTime( 6000 );
if( world != null ) world.setDayTime( Times.NOON );
LOG.info( "Cleaning up after last run" );
CommandSourceStack source = server.createCommandSourceStack();
@ -86,16 +86,12 @@ public class TestHooks
{
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
CommandSourceStack source = server.createCommandSourceStack();
Collection<TestFunction> tests = GameTestRegistry.getAllTestFunctions()
.stream()
.filter( x -> FMLLoader.getDist().isClient() | !x.getBatchName().startsWith( "client" ) )
.collect( Collectors.toList() );
Collection<TestFunction> tests = GameTestRegistry.getAllTestFunctions();
LOG.info( "Running {} tests...", tests.size() );
Collection<GameTestBatch> batches = GameTestRunner.groupTestsIntoBatches( tests );
return new MultipleTestTracker( GameTestRunner.runTestBatches(
batches, getStart( source ), Rotation.NONE, source.getLevel(), GameTestTicker.SINGLETON, 8
return new MultipleTestTracker( GameTestRunner.runTests(
tests, getStart( source ), Rotation.NONE, source.getLevel(), GameTestTicker.SINGLETON, 8
) );
}

View File

@ -8,6 +8,7 @@ package dan200.computercraft.ingame.mod;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.objectweb.asm.Type;
@ -47,6 +48,9 @@ class TestLoader
throw new RuntimeException( e );
}
GameTest test = method.getAnnotation( GameTest.class );
if( test.batch().startsWith( "client" ) && !FMLLoader.getDist().isClient() ) return;
GameTestRegistry.register( method );
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -27,112 +27,114 @@
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 0], state: "minecraft:white_concrete"},
{pos: [0, 1, 1], state: "minecraft:shroomlight"},
{pos: [0, 1, 2], state: "minecraft:white_concrete"},
{pos: [0, 1, 3], state: "minecraft:white_concrete"},
{pos: [0, 1, 4], state: "minecraft:white_concrete"},
{pos: [1, 1, 0], state: "minecraft:white_concrete"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lu}", nbt: {Height: 2, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lu}", nbt: {Height: 2, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [1, 1, 4], state: "minecraft:white_concrete"},
{pos: [2, 1, 0], state: "minecraft:white_concrete"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lru}", nbt: {Height: 2, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lru}", nbt: {Height: 2, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [2, 1, 4], state: "minecraft:white_concrete"},
{pos: [3, 1, 0], state: "minecraft:white_concrete"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:ru}", nbt: {Height: 2, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:ru}", nbt: {Height: 2, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [3, 1, 4], state: "minecraft:white_concrete"},
{pos: [4, 1, 0], state: "minecraft:white_concrete"},
{pos: [4, 1, 1], state: "minecraft:shroomlight"},
{pos: [4, 1, 2], state: "minecraft:white_concrete"},
{pos: [4, 1, 3], state: "minecraft:white_concrete"},
{pos: [4, 1, 4], state: "minecraft:white_concrete"},
{pos: [0, 2, 0], state: "minecraft:white_concrete"},
{pos: [0, 2, 1], state: "minecraft:white_concrete"},
{pos: [0, 2, 2], state: "minecraft:white_concrete"},
{pos: [0, 2, 3], state: "minecraft:white_concrete"},
{pos: [0, 2, 4], state: "minecraft:white_concrete"},
{pos: [1, 2, 0], state: "minecraft:white_concrete"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:ld}", nbt: {Height: 2, Width: 3, XIndex: 2, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:ld}", nbt: {Height: 2, Width: 3, XIndex: 2, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [1, 2, 4], state: "minecraft:white_concrete"},
{pos: [2, 2, 0], state: "minecraft:white_concrete"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lrd}", nbt: {Height: 2, Width: 3, XIndex: 1, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lrd}", nbt: {Height: 2, Width: 3, XIndex: 1, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [2, 2, 4], state: "minecraft:white_concrete"},
{pos: [3, 2, 0], state: "minecraft:white_concrete"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:rd}", nbt: {Height: 2, Width: 3, XIndex: 0, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:rd}", nbt: {Height: 2, Width: 3, XIndex: 0, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [3, 2, 4], state: "minecraft:white_concrete"},
{pos: [4, 2, 0], state: "minecraft:white_concrete"},
{pos: [4, 2, 1], state: "minecraft:white_concrete"},
{pos: [4, 2, 2], state: "minecraft:white_concrete"},
{pos: [4, 2, 3], state: "minecraft:white_concrete"},
{pos: [4, 2, 4], state: "minecraft:white_concrete"},
{pos: [0, 3, 0], state: "minecraft:white_concrete"},
{pos: [0, 3, 1], state: "minecraft:white_concrete"},
{pos: [0, 3, 2], state: "minecraft:white_concrete"},
{pos: [0, 3, 3], state: "minecraft:white_concrete"},
{pos: [0, 3, 4], state: "minecraft:white_concrete"},
{pos: [1, 3, 0], state: "minecraft:white_concrete"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:white_concrete"},
{pos: [2, 3, 0], state: "minecraft:white_concrete"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:white_concrete"},
{pos: [3, 3, 0], state: "minecraft:white_concrete"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
{pos: [3, 3, 4], state: "minecraft:white_concrete"},
{pos: [4, 3, 0], state: "minecraft:white_concrete"},
{pos: [4, 3, 1], state: "minecraft:white_concrete"},
{pos: [4, 3, 2], state: "minecraft:white_concrete"},
{pos: [4, 3, 3], state: "minecraft:white_concrete"},
{pos: [4, 3, 4], state: "minecraft:white_concrete"},
{pos: [0, 4, 0], state: "minecraft:white_concrete"},
{pos: [0, 4, 1], state: "minecraft:white_concrete"},
{pos: [0, 4, 2], state: "minecraft:white_concrete"},
{pos: [0, 4, 3], state: "minecraft:white_concrete"},
{pos: [0, 4, 4], state: "minecraft:white_concrete"},
{pos: [1, 4, 0], state: "minecraft:white_concrete"},
{pos: [1, 4, 1], state: "minecraft:white_concrete"},
{pos: [1, 4, 2], state: "minecraft:white_concrete"},
{pos: [1, 4, 3], state: "minecraft:shroomlight"},
{pos: [1, 4, 4], state: "minecraft:white_concrete"},
{pos: [2, 4, 0], state: "minecraft:white_concrete"},
{pos: [2, 4, 1], state: "minecraft:white_concrete"},
{pos: [2, 4, 2], state: "minecraft:white_concrete"},
{pos: [2, 4, 3], state: "minecraft:white_concrete"},
{pos: [2, 4, 4], state: "minecraft:white_concrete"},
{pos: [3, 4, 0], state: "minecraft:white_concrete"},
{pos: [3, 4, 1], state: "minecraft:white_concrete"},
{pos: [3, 4, 2], state: "minecraft:white_concrete"},
{pos: [3, 4, 3], state: "minecraft:shroomlight"},
{pos: [3, 4, 4], state: "minecraft:white_concrete"},
{pos: [4, 4, 0], state: "minecraft:white_concrete"},
{pos: [4, 4, 1], state: "minecraft:white_concrete"},
{pos: [4, 4, 2], state: "minecraft:white_concrete"},
{pos: [4, 4, 3], state: "minecraft:white_concrete"},
{pos: [4, 4, 4], state: "minecraft:white_concrete"}
],
entities: [
{blockPos: [2, 1, 0], pos: [2.573085437203247d, 1.0d, 0.40392497295766816d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CanUpdate: 1b, CustomName: '{"text":"monitor_test.looks_acceptable"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: 0s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 1b, PortalCooldown: 0, Pos: [121.57308543720325d, 6.0d, 139.40392497295767d], Pose: {}, Rotation: [-0.2999616f, 16.965813f], ShowArms: 0b, Small: 0b, UUID: [I; -1245769654, -1089124211, -1971323071, 221540869], id: "minecraft:armor_stand"}}
{blockPos: [2, 1, 1], pos: [2.392713937302208d, 1.0d, 1.300000011920929d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CanUpdate: 1b, CustomName: '{"text":"monitor_test.looks_acceptable"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [21.392713937302208d, 6.0d, 35.30000001192093d], Pose: {}, Rotation: [0.15043798f, 15.347454f], ShowArms: 0b, Small: 0b, UUID: [I; 474729512, 2108312608, -1494837479, 630038770], id: "minecraft:armor_stand"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:white_concrete",
"minecraft:shroomlight",
"minecraft:air",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lu}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lru}",

View File

@ -0,0 +1,145 @@
{
DataVersion: 2730,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:white_concrete"},
{pos: [0, 1, 1], state: "minecraft:white_concrete"},
{pos: [0, 1, 2], state: "minecraft:white_concrete"},
{pos: [0, 1, 3], state: "minecraft:white_concrete"},
{pos: [0, 1, 4], state: "minecraft:white_concrete"},
{pos: [1, 1, 0], state: "minecraft:white_concrete"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lu}", nbt: {Height: 2, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [1, 1, 4], state: "minecraft:white_concrete"},
{pos: [2, 1, 0], state: "minecraft:white_concrete"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lru}", nbt: {Height: 2, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [2, 1, 4], state: "minecraft:white_concrete"},
{pos: [3, 1, 0], state: "minecraft:white_concrete"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:ru}", nbt: {Height: 2, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
{pos: [3, 1, 4], state: "minecraft:white_concrete"},
{pos: [4, 1, 0], state: "minecraft:white_concrete"},
{pos: [4, 1, 1], state: "minecraft:white_concrete"},
{pos: [4, 1, 2], state: "minecraft:white_concrete"},
{pos: [4, 1, 3], state: "minecraft:white_concrete"},
{pos: [4, 1, 4], state: "minecraft:white_concrete"},
{pos: [0, 2, 0], state: "minecraft:white_concrete"},
{pos: [0, 2, 1], state: "minecraft:white_concrete"},
{pos: [0, 2, 2], state: "minecraft:white_concrete"},
{pos: [0, 2, 3], state: "minecraft:white_concrete"},
{pos: [0, 2, 4], state: "minecraft:white_concrete"},
{pos: [1, 2, 0], state: "minecraft:white_concrete"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:ld}", nbt: {Height: 2, Width: 3, XIndex: 2, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [1, 2, 4], state: "minecraft:white_concrete"},
{pos: [2, 2, 0], state: "minecraft:white_concrete"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lrd}", nbt: {Height: 2, Width: 3, XIndex: 1, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [2, 2, 4], state: "minecraft:white_concrete"},
{pos: [3, 2, 0], state: "minecraft:white_concrete"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:rd}", nbt: {Height: 2, Width: 3, XIndex: 0, YIndex: 1, id: "computercraft:monitor_advanced"}},
{pos: [3, 2, 4], state: "minecraft:white_concrete"},
{pos: [4, 2, 0], state: "minecraft:white_concrete"},
{pos: [4, 2, 1], state: "minecraft:white_concrete"},
{pos: [4, 2, 2], state: "minecraft:white_concrete"},
{pos: [4, 2, 3], state: "minecraft:white_concrete"},
{pos: [4, 2, 4], state: "minecraft:white_concrete"},
{pos: [0, 3, 0], state: "minecraft:white_concrete"},
{pos: [0, 3, 1], state: "minecraft:white_concrete"},
{pos: [0, 3, 2], state: "minecraft:white_concrete"},
{pos: [0, 3, 3], state: "minecraft:white_concrete"},
{pos: [0, 3, 4], state: "minecraft:white_concrete"},
{pos: [1, 3, 0], state: "minecraft:white_concrete"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:white_concrete"},
{pos: [2, 3, 0], state: "minecraft:white_concrete"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:white_concrete"},
{pos: [3, 3, 0], state: "minecraft:white_concrete"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:white_concrete"},
{pos: [4, 3, 0], state: "minecraft:white_concrete"},
{pos: [4, 3, 1], state: "minecraft:white_concrete"},
{pos: [4, 3, 2], state: "minecraft:white_concrete"},
{pos: [4, 3, 3], state: "minecraft:white_concrete"},
{pos: [4, 3, 4], state: "minecraft:white_concrete"},
{pos: [0, 4, 0], state: "minecraft:white_concrete"},
{pos: [0, 4, 1], state: "minecraft:white_concrete"},
{pos: [0, 4, 2], state: "minecraft:white_concrete"},
{pos: [0, 4, 3], state: "minecraft:white_concrete"},
{pos: [0, 4, 4], state: "minecraft:white_concrete"},
{pos: [1, 4, 0], state: "minecraft:white_concrete"},
{pos: [1, 4, 1], state: "minecraft:white_concrete"},
{pos: [1, 4, 2], state: "minecraft:white_concrete"},
{pos: [1, 4, 3], state: "minecraft:white_concrete"},
{pos: [1, 4, 4], state: "minecraft:white_concrete"},
{pos: [2, 4, 0], state: "minecraft:white_concrete"},
{pos: [2, 4, 1], state: "minecraft:white_concrete"},
{pos: [2, 4, 2], state: "minecraft:white_concrete"},
{pos: [2, 4, 3], state: "minecraft:white_concrete"},
{pos: [2, 4, 4], state: "minecraft:white_concrete"},
{pos: [3, 4, 0], state: "minecraft:white_concrete"},
{pos: [3, 4, 1], state: "minecraft:white_concrete"},
{pos: [3, 4, 2], state: "minecraft:white_concrete"},
{pos: [3, 4, 3], state: "minecraft:white_concrete"},
{pos: [3, 4, 4], state: "minecraft:white_concrete"},
{pos: [4, 4, 0], state: "minecraft:white_concrete"},
{pos: [4, 4, 1], state: "minecraft:white_concrete"},
{pos: [4, 4, 2], state: "minecraft:white_concrete"},
{pos: [4, 4, 3], state: "minecraft:white_concrete"},
{pos: [4, 4, 4], state: "minecraft:white_concrete"}
],
entities: [
{blockPos: [2, 1, 1], pos: [2.3927139373022044d, 1.0d, 1.300000011920929d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CanUpdate: 1b, CustomName: '{"text":"monitor_test.looks_acceptable_dark"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [64.3927139373022d, 6.0d, 59.30000001192093d], Pose: {}, Rotation: [0.15043798f, 15.347454f], ShowArms: 0b, Small: 0b, UUID: [I; -1516632699, -1770765897, -1362337958, -475677268], id: "minecraft:armor_stand"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:white_concrete",
"minecraft:air",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lu}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lru}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:ru}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:ld}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lrd}",
"computercraft:monitor_advanced{facing:north,orientation:north,state:rd}"
]
}

View File

@ -0,0 +1,140 @@
{
DataVersion: 2730,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:white_concrete"},
{pos: [0, 1, 1], state: "minecraft:white_concrete"},
{pos: [0, 1, 2], state: "minecraft:white_concrete"},
{pos: [0, 1, 3], state: "minecraft:white_concrete"},
{pos: [0, 1, 4], state: "minecraft:white_concrete"},
{pos: [1, 1, 0], state: "minecraft:white_concrete"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:white_concrete"},
{pos: [2, 1, 0], state: "minecraft:white_concrete"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:white_concrete"},
{pos: [3, 1, 0], state: "minecraft:white_concrete"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:white_concrete"},
{pos: [4, 1, 0], state: "minecraft:white_concrete"},
{pos: [4, 1, 1], state: "minecraft:white_concrete"},
{pos: [4, 1, 2], state: "minecraft:white_concrete"},
{pos: [4, 1, 3], state: "minecraft:white_concrete"},
{pos: [4, 1, 4], state: "minecraft:white_concrete"},
{pos: [0, 2, 0], state: "minecraft:white_concrete"},
{pos: [0, 2, 1], state: "minecraft:white_concrete"},
{pos: [0, 2, 2], state: "minecraft:white_concrete"},
{pos: [0, 2, 3], state: "minecraft:white_concrete"},
{pos: [0, 2, 4], state: "minecraft:white_concrete"},
{pos: [1, 2, 0], state: "minecraft:white_concrete"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:white_concrete"},
{pos: [2, 2, 0], state: "minecraft:white_concrete"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:white_concrete"},
{pos: [3, 2, 0], state: "minecraft:white_concrete"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:white_concrete"},
{pos: [4, 2, 0], state: "minecraft:white_concrete"},
{pos: [4, 2, 1], state: "minecraft:white_concrete"},
{pos: [4, 2, 2], state: "minecraft:white_concrete"},
{pos: [4, 2, 3], state: "minecraft:white_concrete"},
{pos: [4, 2, 4], state: "minecraft:white_concrete"},
{pos: [0, 3, 0], state: "minecraft:white_concrete"},
{pos: [0, 3, 1], state: "minecraft:white_concrete"},
{pos: [0, 3, 2], state: "minecraft:white_concrete"},
{pos: [0, 3, 3], state: "minecraft:white_concrete"},
{pos: [0, 3, 4], state: "minecraft:white_concrete"},
{pos: [1, 3, 0], state: "minecraft:white_concrete"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:white_concrete"},
{pos: [2, 3, 0], state: "minecraft:white_concrete"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:white_concrete"},
{pos: [3, 3, 0], state: "minecraft:white_concrete"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:white_concrete"},
{pos: [4, 3, 0], state: "minecraft:white_concrete"},
{pos: [4, 3, 1], state: "minecraft:white_concrete"},
{pos: [4, 3, 2], state: "minecraft:white_concrete"},
{pos: [4, 3, 3], state: "minecraft:white_concrete"},
{pos: [4, 3, 4], state: "minecraft:white_concrete"},
{pos: [0, 4, 0], state: "minecraft:white_concrete"},
{pos: [0, 4, 1], state: "minecraft:white_concrete"},
{pos: [0, 4, 2], state: "minecraft:white_concrete"},
{pos: [0, 4, 3], state: "minecraft:white_concrete"},
{pos: [0, 4, 4], state: "minecraft:white_concrete"},
{pos: [1, 4, 0], state: "minecraft:white_concrete"},
{pos: [1, 4, 1], state: "minecraft:white_concrete"},
{pos: [1, 4, 2], state: "minecraft:white_concrete"},
{pos: [1, 4, 3], state: "minecraft:white_concrete"},
{pos: [1, 4, 4], state: "minecraft:white_concrete"},
{pos: [2, 4, 0], state: "minecraft:white_concrete"},
{pos: [2, 4, 1], state: "minecraft:white_concrete"},
{pos: [2, 4, 2], state: "minecraft:white_concrete"},
{pos: [2, 4, 3], state: "minecraft:white_concrete"},
{pos: [2, 4, 4], state: "minecraft:white_concrete"},
{pos: [3, 4, 0], state: "minecraft:white_concrete"},
{pos: [3, 4, 1], state: "minecraft:white_concrete"},
{pos: [3, 4, 2], state: "minecraft:white_concrete"},
{pos: [3, 4, 3], state: "minecraft:white_concrete"},
{pos: [3, 4, 4], state: "minecraft:white_concrete"},
{pos: [4, 4, 0], state: "minecraft:white_concrete"},
{pos: [4, 4, 1], state: "minecraft:white_concrete"},
{pos: [4, 4, 2], state: "minecraft:white_concrete"},
{pos: [4, 4, 3], state: "minecraft:white_concrete"},
{pos: [4, 4, 4], state: "minecraft:white_concrete"}
],
entities: [
{blockPos: [2, 2, 3], pos: [2.5d, 2.5d, 3.96875d], nbt: {Air: 300s, CanUpdate: 1b, Facing: 2b, FallDistance: 0.0f, Fire: -1s, Fixed: 0b, Invisible: 0b, Invulnerable: 0b, Item: {Count: 1b, id: "computercraft:printed_page", tag: {Color0: "eeeeeeeeeeeeeeeeeeeeeeeee", Color1: "eeeeeeeeeeeeeeeeeeeeeeeee", Color10: "eeeeeeeeeeeeeeeeeeeeeeeee", Color11: "eeeeeeeeeeeeeeeeeeeeeeeee", Color12: "eeeeeeeeeeeeeeeeeeeeeeeee", Color13: "eeeeeeeeeeeeeeeeeeeeeeeee", Color14: "eeeeeeeeeeeeeeeeeeeeeeeee", Color15: "eeeeeeeeeeeeeeeeeeeeeeeee", Color16: "eeeeeeeeeeeeeeeeeeeeeeeee", Color17: "eeeeeeeeeeeeeeeeeeeeeeeee", Color18: "eeeeeeeeeeeeeeeeeeeeeeeee", Color19: "eeeeeeeeeeeeeeeeeeeeeeeee", Color2: "eeeeeeeeeeeeeeeeeeeeeeeee", Color20: "eeeeeeeeeeeeeeeeeeeeeeeee", Color3: "eeeeeeeeeeeeeeeeeeeeeeeee", Color4: "eeeeeeeeeeeeeeeeeeeeeeeee", Color5: "eeeeeeeeeeeeeeeeeeeeeeeee", Color6: "eeeeeeeeeeeeeeeeeeeeeeeee", Color7: "eeeeeeeeeeeeeeeeeeeeeeeee", Color8: "eeeeeeeeeeeeeeeeeeeeeeeee", Color9: "eeeeeeeeeeeeeeeeeeeeeeeee", Pages: 1, Text0: "If you're reading this, ", Text1: "the test failed. ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: "a.lua"}}, ItemDropChance: 1.0f, ItemRotation: 0b, Motion: [0.0d, 0.0d, 0.0d], OnGround: 0b, PortalCooldown: 0, Pos: [10.5d, 7.5d, 44.96875d], Rotation: [180.0f, 0.0f], TileX: 10, TileY: 7, TileZ: 44, UUID: [I; 1043973837, -2076424529, -1762893135, -165665834], id: "minecraft:item_frame"}},
{blockPos: [2, 1, 2], pos: [2.583196949396914d, 1.0d, 2.6089749199596d], nbt: {AbsorptionAmount: 0.0f, Air: 300s, ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.699999988079071d, Name: "minecraft:generic.movement_speed"}], Brain: {memories: {}}, CanUpdate: 1b, CustomName: '{"text":"printouttest.in_frame_at_night"}', DeathTime: 0s, DisabledSlots: 0, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Invisible: 1b, Invulnerable: 0b, Marker: 1b, Motion: [0.0d, 0.0d, 0.0d], NoBasePlate: 0b, OnGround: 0b, PortalCooldown: 0, Pos: [10.583196949396914d, 6.0d, 43.6089749199596d], Pose: {}, Rotation: [1.3504658f, 6.7031174f], ShowArms: 0b, Small: 0b, UUID: [I; -1917933016, 1390888530, -2109873447, -2136052677], id: "minecraft:armor_stand"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:white_concrete",
"minecraft:air"
]
}

1
tools/convert-structure.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
import sys
import nbtlib
from nbtlib.tag import Compound, Int, List, String