Merge branch 'mc-1.16.x' into mc-1.17.x
@ -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:
|
||||
|
BIN
doc/logo.png
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.5 KiB |
@ -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)
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.",
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
9
src/test/resources/test-rom/spec/support/debug_ext.lua
Normal 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 }
|
180
src/test/resources/test-rom/spec/support/fake_computer.lua
Normal 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,
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
17
src/testMod/java/dan200/computercraft/ingame/PrintoutTest.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
} ) )
|
||||
);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
) );
|
||||
}
|
||||
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 44 KiB |
@ -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}",
|
||||
|
@ -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}"
|
||||
]
|
||||
}
|
@ -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
@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import nbtlib
|
||||
from nbtlib.tag import Compound, Int, List, String
|
||||
|