1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-02-01 11:49:12 +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. 1. @{string}: The event name.
2. @{string}: The URL of the WebSocket. 2. @{string}: The URL of the WebSocket.
3. @{string}: The contents of the message. 3. @{string}: The contents of the message.
4. @{boolean}: Whether this is a binary message.
## Example ## Example
Prints a message sent by a WebSocket: 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) (table space)
(index no-space)) (index no-space))
(allow-clarifying-parens true)
;; colours imports from colors, and we don't handle that right now. ;; colours imports from colors, and we don't handle that right now.
;; keys is entirely dynamic, so we skip it. ;; keys is entirely dynamic, so we skip it.
(dynamic-modules colours keys _G) (dynamic-modules colours keys _G)

View File

@ -43,6 +43,10 @@ public class NoTermComputerScreen<T extends ContainerComputerBase> extends Scree
@Override @Override
protected void init() 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(); super.init();
minecraft.keyboardHandler.setSendRepeatsToGui( true ); minecraft.keyboardHandler.setSendRepeatsToGui( true );
@ -66,6 +70,13 @@ public class NoTermComputerScreen<T extends ContainerComputerBase> extends Scree
terminal.update(); terminal.update();
} }
@Override
public boolean mouseScrolled( double pMouseX, double pMouseY, double pDelta )
{
minecraft.player.getInventory().swapPaint( pDelta );
return super.mouseScrolled( pMouseX, pMouseY, pDelta );
}
@Override @Override
public void onClose() public void onClose()
{ {

View File

@ -127,8 +127,8 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
FixedWidthFontRenderer.drawBlocker( FixedWidthFontRenderer.drawBlocker(
transform.last().pose(), renderer, transform.last().pose(), renderer,
(float) -TileMonitor.RENDER_MARGIN, (float) TileMonitor.RENDER_MARGIN, -MARGIN, MARGIN,
(float) (xSize + 2 * TileMonitor.RENDER_MARGIN), (float) -(ySize + TileMonitor.RENDER_MARGIN * 2) (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
); );
// Force a flush of the blocker. WorldRenderer.updateCameraAndRender will "finish" all the built-in // 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; package dan200.computercraft.core.apis;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
@ -30,6 +31,11 @@ public abstract class ComputerAccess implements IComputerAccess
public void unmountAll() public void unmountAll()
{ {
FileSystem fileSystem = environment.getFileSystem(); 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 ) for( String mount : mounts )
{ {
fileSystem.unmount( mount ); fileSystem.unmount( mount );

View File

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

View File

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

View File

@ -5,3 +5,5 @@ public net.minecraft.client.renderer.ItemInHandRenderer m_109346_(Lcom/mojang/bl
# ClientTableFormatter # 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_93787_(Lnet/minecraft/network/chat/Component;I)V # addMessage
public net.minecraft.client.gui.components.ChatComponent m_93803_(I)V # removeById 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]] [[mods]]
modId="computercraft" modId="computercraft"
version="${version}" version="1.2.3.4"
displayName="CC: Tweaked" displayName="CC: Tweaked"
description=''' description='''
CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft. 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.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.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.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_children": "%s hat keine Unterbefehle",
"commands.computercraft.help.no_command": "Unbekannter Befehl '%s'", "commands.computercraft.help.no_command": "Unbekannter Befehl '%s'",
"commands.computercraft.dump.synopsis": "Zeigt den Status eines Computers.", "commands.computercraft.dump.synopsis": "Zeigt den Status eines Computers.",

View File

@ -109,5 +109,23 @@
"tracking_field.computercraft.coroutines_dead.name": "Coroutines mortes", "tracking_field.computercraft.coroutines_dead.name": "Coroutines mortes",
"gui.computercraft.tooltip.copy": "Copier dans le Presse-Papiers", "gui.computercraft.tooltip.copy": "Copier dans le Presse-Papiers",
"gui.computercraft.tooltip.computer_id": "ID d'ordinateur : %s", "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. --- The channel used by the Rednet API to repeat messages.
CHANNEL_REPEAT = 65533 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 tReceivedMessages = {}
local tReceivedMessageTimeouts = {}
local tHostnames = {} 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 --[[- Opens a modem with the given @{peripheral} name, allowing it to send and
receive messages over rednet. receive messages over rednet.
@ -47,7 +55,7 @@ function open(modem)
if peripheral.getType(modem) ~= "modem" then if peripheral.getType(modem) ~= "modem" then
error("No such modem: " .. modem, 2) error("No such modem: " .. modem, 2)
end end
peripheral.call(modem, "open", os.getComputerID()) peripheral.call(modem, "open", id_as_channel())
peripheral.call(modem, "open", CHANNEL_BROADCAST) peripheral.call(modem, "open", CHANNEL_BROADCAST)
end end
@ -64,7 +72,7 @@ function close(modem)
if peripheral.getType(modem) ~= "modem" then if peripheral.getType(modem) ~= "modem" then
error("No such modem: " .. modem, 2) error("No such modem: " .. modem, 2)
end end
peripheral.call(modem, "close", os.getComputerID()) peripheral.call(modem, "close", id_as_channel())
peripheral.call(modem, "close", CHANNEL_BROADCAST) peripheral.call(modem, "close", CHANNEL_BROADCAST)
else else
-- Close all modems -- Close all modems
@ -87,7 +95,7 @@ function isOpen(modem)
if modem then if modem then
-- Check if a specific modem is open -- Check if a specific modem is open
if peripheral.getType(modem) == "modem" then 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 end
else else
-- Check if any modem is open -- 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 -- 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 -- Store it to ensure we don't get our own messages back
local nMessageID = math.random(1, 2147483647) local nMessageID = math.random(1, 2147483647)
tReceivedMessages[nMessageID] = true tReceivedMessages[nMessageID] = os.clock() + 9.5
tReceivedMessageTimeouts[os.startTimer(30)] = nMessageID if not nClearTimer then nClearTimer = os.startTimer(10) end
-- Create the message -- Create the message
local nReplyChannel = os.getComputerID() local nReplyChannel = id_as_channel()
local tMessage = { local tMessage = {
nMessageID = nMessageID, nMessageID = nMessageID,
nRecipient = nRecipient, nRecipient = nRecipient,
nSender = os.getComputerID(),
message = message, message = message,
sProtocol = sProtocol, sProtocol = sProtocol,
} }
@ -145,10 +154,14 @@ function send(nRecipient, message, sProtocol)
local sent = false local sent = false
if nRecipient == os.getComputerID() then if nRecipient == os.getComputerID() then
-- Loopback to ourselves -- Loopback to ourselves
os.queueEvent("rednet_message", nReplyChannel, message, sProtocol) os.queueEvent("rednet_message", os.getComputerID(), message, sProtocol)
sent = true sent = true
else else
-- Send on all open modems, to the target and to repeaters -- 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 for _, sModem in ipairs(peripheral.getNames()) do
if isOpen(sModem) then if isOpen(sModem) then
peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage) peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage)
@ -390,13 +403,14 @@ function run()
if sEvent == "modem_message" then if sEvent == "modem_message" then
-- Got a modem message, process it and add it to the rednet event queue -- Got a modem message, process it and add it to the rednet event queue
local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4 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" if type(tMessage) == "table" and type(tMessage.nMessageID) == "number"
and tMessage.nMessageID == tMessage.nMessageID and not tReceivedMessages[tMessage.nMessageID] and tMessage.nMessageID == tMessage.nMessageID and not tReceivedMessages[tMessage.nMessageID]
and ((tMessage.nRecipient and tMessage.nRecipient == os.getComputerID()) or nChannel == CHANNEL_BROADCAST)
then then
tReceivedMessages[tMessage.nMessageID] = true tReceivedMessages[tMessage.nMessageID] = os.clock() + 9.5
tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID if not nClearTimer then nClearTimer = os.startTimer(10) end
os.queueEvent("rednet_message", nReplyChannel, tMessage.message, tMessage.sProtocol) os.queueEvent("rednet_message", tMessage.nSender or nReplyChannel, tMessage.message, tMessage.sProtocol)
end end
end end
@ -414,14 +428,15 @@ function run()
end end
end end
elseif sEvent == "timer" then elseif sEvent == "timer" and p1 == nClearTimer then
-- Got a timer event, use it to clear the event queue -- Got a timer event, use it to clear the event queue
local nTimer = p1 nClearTimer = nil
local nMessage = tReceivedMessageTimeouts[nTimer] local nNow, bHasMore = os.clock(), nil
if nMessage then for nMessageID, nDeadline in pairs(tReceivedMessages) do
tReceivedMessageTimeouts[nTimer] = nil if nDeadline <= nNow then tReceivedMessages[nMessageID] = nil
tReceivedMessages[nMessage] = nil else bHasMore = true end
end end
nClearTimer = bHasMore and os.startTimer(10)
end end
end end
end end

View File

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

View File

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

View File

@ -97,7 +97,7 @@ public class ComputerTestDelegate
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." ); 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 ); IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files // 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()) expect(rednet.lookup("a_protocol", "a_hostname")):eq(os.getComputerID())
end) end)
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) 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 package dan200.computercraft.ingame
import dan200.computercraft.ComputerCraft
import dan200.computercraft.ingame.api.* import dan200.computercraft.ingame.api.*
import dan200.computercraft.ingame.api.Timeouts.CLIENT_TIMEOUT
import dan200.computercraft.shared.Capabilities import dan200.computercraft.shared.Capabilities
import dan200.computercraft.shared.Registry import dan200.computercraft.shared.Registry
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer
import dan200.computercraft.shared.peripheral.monitor.TileMonitor import dan200.computercraft.shared.peripheral.monitor.TileMonitor
import net.minecraft.commands.arguments.blocks.BlockInput import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
@ -44,15 +47,14 @@ class Monitor_Test {
} }
} }
@GameTest(batch = "client:Monitor_Test.Looks_acceptable", timeoutTicks = 400) private fun looksAcceptable(helper: GameTestHelper, renderer: MonitorRenderer) = helper.sequence {
fun Looks_acceptable(helper: GameTestHelper) = helper.sequence {
this this
.thenExecute { helper.normaliseScene() }
.thenExecute { .thenExecute {
ComputerCraft.monitorRenderer = renderer
helper.positionAtArmorStand() helper.positionAtArmorStand()
// Get the monitor and peripheral. This forces us to create a server monitor at this location. // 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) monitor.getCapability(Capabilities.CAPABILITY_PERIPHERAL)
val terminal = monitor.cachedServerMonitor.terminal val terminal = monitor.cachedServerMonitor.terminal
@ -64,4 +66,21 @@ class Monitor_Test {
} }
.thenScreenshot() .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 package dan200.computercraft.ingame
import dan200.computercraft.ingame.api.Timeouts.COMPUTER_TIMEOUT
import dan200.computercraft.ingame.api.sequence import dan200.computercraft.ingame.api.sequence
import dan200.computercraft.ingame.api.thenComputerOk import dan200.computercraft.ingame.api.thenComputerOk
import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestHelper
class Turtle_Test { class Turtle_Test {
@GameTest(timeoutTicks = TIMEOUT) @GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Unequip_refreshes_peripheral(helper: GameTestHelper) = helper.sequence { thenComputerOk() } 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) * @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() } 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) * @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() } 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) * @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() } 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) * @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() } 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) * @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() } 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) * @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() } fun Place_monitor(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/** /**
* Checks turtles can place into compostors. These are non-typical inventories, so * Checks turtles can place into compostors. These are non-typical inventories, so
* worth testing. * worth testing.
*/ */
@GameTest(timeoutTicks = TIMEOUT) @GameTest(timeoutTicks = COMPUTER_TIMEOUT)
fun Use_compostors(helper: GameTestHelper) = helper.sequence { thenComputerOk() } fun Use_compostors(helper: GameTestHelper) = helper.sequence { thenComputerOk() }
/** /**
@ -71,8 +72,4 @@ class Turtle_Test {
*/ */
@GameTest(required = false) @GameTest(required = false)
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence { thenComputerOk() } 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.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.GameTestAssertException import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestAssertPosException
import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.GameTestSequence import net.minecraft.gametest.framework.GameTestSequence
import net.minecraft.world.entity.decoration.ArmorStand 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.block.state.BlockState
import net.minecraft.world.level.levelgen.Heightmap
import java.nio.file.Files import java.nio.file.Files
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Supplier import java.util.function.Supplier
import javax.imageio.ImageIO 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. * 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 suffix = if (name == null) "" else "-$name"
val fullName = "${parent.testName}$suffix" val fullName = "${parent.testName}$suffix"
val counter = AtomicInteger() var counter = 0
val hasScreenshot = AtomicBoolean()
return this return this
// Wait until all chunks have been rendered and we're idle for an extended period. // Wait until all chunks have been rendered and we're idle for an extended period.
.thenExecute { counter.set(0) } .thenExecute { counter = 0 }
.thenWaitUntil { .thenWaitUntil {
if (Minecraft.getInstance().levelRenderer.hasRenderedAllChunks()) { val renderer = Minecraft.getInstance().levelRenderer
val idleFor = counter.getAndIncrement() if (renderer.chunkRenderDispatcher != null && renderer.hasRenderedAllChunks()) {
val idleFor = ++counter
if (idleFor <= 20) throw GameTestAssertException("Only idle for $idleFor ticks") if (idleFor <= 20) throw GameTestAssertException("Only idle for $idleFor ticks")
} else { } else {
counter.set(0) counter = 0
throw GameTestAssertException("Waiting for client to finish rendering") 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 // Now disable the GUI, take a screenshot and reenable it. We sleep either side to give the client time to do
// its thing. // 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. .thenIdle(5) // Some delay before/after to ensure the render thread has caught up.
.thenOnClient { screenshot("$fullName.png") } .thenOnClient { screenshot("$fullName.png") { hasScreenshot.set(true) } }
.thenIdle(2) .thenWaitUntil { if (!hasScreenshot.get()) throw GameTestAssertException("Screenshot does not exist") }
.thenExecute { .thenExecute {
Minecraft.getInstance().options.hideGui = false 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."); if (!Files.exists(originalPath)) throw GameTestAssertException("$fullName does not exist. Use `/cctest promote' to create it.");
val screenshot = ImageIO.read(screenshotPath.toFile()) val screenshot = ImageIO.read(screenshotPath.toFile())
?: throw GameTestAssertException("Error reading screenshot from $screenshotPath")
val original = ImageIO.read(originalPath.toFile()) val original = ImageIO.read(originalPath.toFile())
if (screenshot.width != original.width || screenshot.height != original.height) { 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}") 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) 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.testName: String get() = testInfo.testName
val GameTestHelper.structureName: String get() = testInfo.structureName
/** /**
* Modify a block state within the test. * Modify a block state within the test.
*/ */
@ -113,38 +133,30 @@ fun GameTestHelper.sequence(run: GameTestSequence.() -> GameTestSequence) {
run(startSequence()).thenSucceed() 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. * Set a block within the test structure.
*/ */
fun GameTestHelper.setBlock(pos: BlockPos, state: BlockInput) = state.place(level, absolutePos(pos), 3) 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. * Position the player at an armor stand.
*/ */
fun GameTestHelper.positionAtArmorStand() { fun GameTestHelper.positionAtArmorStand() {
val entities = level.getEntities(null, bounds) { it.name.string == testName } val entities = level.getEntities(null, bounds) { it.name.string == structureName }
if (entities.size <= 0 || entities[0] !is ArmorStand) throw IllegalStateException("Cannot find armor stand") if (entities.size <= 0 || entities[0] !is ArmorStand) throw GameTestAssertException("Cannot find armor stand")
val stand = entities[0] as ArmorStand 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) player.connection.teleport(stand.x, stand.y, stand.z, stand.yRot, stand.xRot)
} }
@ -153,7 +165,10 @@ fun GameTestHelper.positionAtArmorStand() {
class ClientTestHelper { class ClientTestHelper {
val minecraft: Minecraft = Minecraft.getInstance() val minecraft: Minecraft = Minecraft.getInstance()
fun screenshot(name: String) { fun screenshot(name: String, callback: () -> Unit = {}) {
Screenshot.grab(minecraft.gameDirectory, name, minecraft.mainRenderTarget) { TestMod.log.info(it.string) } Screenshot.grab(minecraft.gameDirectory, name, minecraft.mainRenderTarget) {
TestMod.log.info(it.string)
callback()
}
} }
} }

View File

@ -59,13 +59,6 @@ class CCTestCommand
} }
return 0; 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 -> { .then( literal( "promote" ).executes( context -> {
if( !FMLLoader.getDist().isClient() ) return error( context.getSource(), "Cannot run on server" ); if( !FMLLoader.getDist().isClient() ) return error( context.getSource(), "Cannot run on server" );
@ -94,6 +87,7 @@ class CCTestCommand
armorStand.readAdditionalSaveData( nbt ); armorStand.readAdditionalSaveData( nbt );
armorStand.copyPosition( player ); armorStand.copyPosition( player );
armorStand.setCustomName( new TextComponent( info.getTestName() ) ); armorStand.setCustomName( new TextComponent( info.getTestName() ) );
player.getLevel().addFreshEntity( armorStand );
return 0; return 0;
} ) ) } ) )
); );

View File

@ -18,9 +18,9 @@ public final class ImageUtils
private static final Logger LOG = LogManager.getLogger( ImageUtils.class ); 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. * 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.ChatFormatting;
import net.minecraft.SharedConstants; import net.minecraft.SharedConstants;
import dan200.computercraft.ingame.api.Times;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -29,7 +30,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors;
@Mod.EventBusSubscriber( modid = TestMod.MOD_ID ) @Mod.EventBusSubscriber( modid = TestMod.MOD_ID )
public class TestHooks public class TestHooks
@ -58,7 +58,7 @@ public class TestHooks
rules.getRule( GameRules.RULE_DOMOBSPAWNING ).set( false, server ); rules.getRule( GameRules.RULE_DOMOBSPAWNING ).set( false, server );
ServerLevel world = event.getServer().getLevel( Level.OVERWORLD ); 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" ); LOG.info( "Cleaning up after last run" );
CommandSourceStack source = server.createCommandSourceStack(); CommandSourceStack source = server.createCommandSourceStack();
@ -86,16 +86,12 @@ public class TestHooks
{ {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
CommandSourceStack source = server.createCommandSourceStack(); CommandSourceStack source = server.createCommandSourceStack();
Collection<TestFunction> tests = GameTestRegistry.getAllTestFunctions() Collection<TestFunction> tests = GameTestRegistry.getAllTestFunctions();
.stream()
.filter( x -> FMLLoader.getDist().isClient() | !x.getBatchName().startsWith( "client" ) )
.collect( Collectors.toList() );
LOG.info( "Running {} tests...", tests.size() ); LOG.info( "Running {} tests...", tests.size() );
Collection<GameTestBatch> batches = GameTestRunner.groupTestsIntoBatches( tests ); return new MultipleTestTracker( GameTestRunner.runTests(
return new MultipleTestTracker( GameTestRunner.runTestBatches( tests, getStart( source ), Rotation.NONE, source.getLevel(), GameTestTicker.SINGLETON, 8
batches, 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.GameTest;
import net.minecraft.gametest.framework.GameTestRegistry; import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forgespi.language.ModFileScanData; import net.minecraftforge.forgespi.language.ModFileScanData;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
@ -47,6 +48,9 @@ class TestLoader
throw new RuntimeException( e ); throw new RuntimeException( e );
} }
GameTest test = method.getAnnotation( GameTest.class );
if( test.batch().startsWith( "client" ) && !FMLLoader.getDist().isClient() ) return;
GameTestRegistry.register( method ); 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, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"}, {pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"}, {pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"}, {pos: [0, 1, 0], state: "minecraft:white_concrete"},
{pos: [0, 1, 1], state: "minecraft:air"}, {pos: [0, 1, 1], state: "minecraft:shroomlight"},
{pos: [0, 1, 2], state: "minecraft:air"}, {pos: [0, 1, 2], state: "minecraft:white_concrete"},
{pos: [0, 1, 3], state: "minecraft:air"}, {pos: [0, 1, 3], state: "minecraft:white_concrete"},
{pos: [0, 1, 4], state: "minecraft:air"}, {pos: [0, 1, 4], state: "minecraft:white_concrete"},
{pos: [1, 1, 0], state: "minecraft:air"}, {pos: [1, 1, 0], state: "minecraft:white_concrete"},
{pos: [1, 1, 1], state: "minecraft:air"}, {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, 2], state: "minecraft:air"},
{pos: [1, 1, 3], 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:air"}, {pos: [1, 1, 4], state: "minecraft:white_concrete"},
{pos: [2, 1, 0], state: "minecraft:air"}, {pos: [2, 1, 0], state: "minecraft:white_concrete"},
{pos: [2, 1, 1], state: "minecraft:air"}, {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, 2], state: "minecraft:air"},
{pos: [2, 1, 3], 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:air"}, {pos: [2, 1, 4], state: "minecraft:white_concrete"},
{pos: [3, 1, 0], state: "minecraft:air"}, {pos: [3, 1, 0], state: "minecraft:white_concrete"},
{pos: [3, 1, 1], state: "minecraft:air"}, {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, 2], state: "minecraft:air"},
{pos: [3, 1, 3], 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:air"}, {pos: [3, 1, 4], state: "minecraft:white_concrete"},
{pos: [4, 1, 0], state: "minecraft:air"}, {pos: [4, 1, 0], state: "minecraft:white_concrete"},
{pos: [4, 1, 1], state: "minecraft:air"}, {pos: [4, 1, 1], state: "minecraft:shroomlight"},
{pos: [4, 1, 2], state: "minecraft:air"}, {pos: [4, 1, 2], state: "minecraft:white_concrete"},
{pos: [4, 1, 3], state: "minecraft:air"}, {pos: [4, 1, 3], state: "minecraft:white_concrete"},
{pos: [4, 1, 4], state: "minecraft:air"}, {pos: [4, 1, 4], state: "minecraft:white_concrete"},
{pos: [0, 2, 0], state: "minecraft:air"}, {pos: [0, 2, 0], state: "minecraft:white_concrete"},
{pos: [0, 2, 1], state: "minecraft:air"}, {pos: [0, 2, 1], state: "minecraft:white_concrete"},
{pos: [0, 2, 2], state: "minecraft:air"}, {pos: [0, 2, 2], state: "minecraft:white_concrete"},
{pos: [0, 2, 3], state: "minecraft:air"}, {pos: [0, 2, 3], state: "minecraft:white_concrete"},
{pos: [0, 2, 4], state: "minecraft:air"}, {pos: [0, 2, 4], state: "minecraft:white_concrete"},
{pos: [1, 2, 0], state: "minecraft:air"}, {pos: [1, 2, 0], state: "minecraft:white_concrete"},
{pos: [1, 2, 1], state: "minecraft:air"}, {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, 2], state: "minecraft:air"},
{pos: [1, 2, 3], 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:air"}, {pos: [1, 2, 4], state: "minecraft:white_concrete"},
{pos: [2, 2, 0], state: "minecraft:air"}, {pos: [2, 2, 0], state: "minecraft:white_concrete"},
{pos: [2, 2, 1], state: "minecraft:air"}, {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, 2], state: "minecraft:air"},
{pos: [2, 2, 3], 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:air"}, {pos: [2, 2, 4], state: "minecraft:white_concrete"},
{pos: [3, 2, 0], state: "minecraft:air"}, {pos: [3, 2, 0], state: "minecraft:white_concrete"},
{pos: [3, 2, 1], state: "minecraft:air"}, {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, 2], state: "minecraft:air"},
{pos: [3, 2, 3], 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:air"}, {pos: [3, 2, 4], state: "minecraft:white_concrete"},
{pos: [4, 2, 0], state: "minecraft:air"}, {pos: [4, 2, 0], state: "minecraft:white_concrete"},
{pos: [4, 2, 1], state: "minecraft:air"}, {pos: [4, 2, 1], state: "minecraft:white_concrete"},
{pos: [4, 2, 2], state: "minecraft:air"}, {pos: [4, 2, 2], state: "minecraft:white_concrete"},
{pos: [4, 2, 3], state: "minecraft:air"}, {pos: [4, 2, 3], state: "minecraft:white_concrete"},
{pos: [4, 2, 4], state: "minecraft:air"}, {pos: [4, 2, 4], state: "minecraft:white_concrete"},
{pos: [0, 3, 0], state: "minecraft:air"}, {pos: [0, 3, 0], state: "minecraft:white_concrete"},
{pos: [0, 3, 1], state: "minecraft:air"}, {pos: [0, 3, 1], state: "minecraft:white_concrete"},
{pos: [0, 3, 2], state: "minecraft:air"}, {pos: [0, 3, 2], state: "minecraft:white_concrete"},
{pos: [0, 3, 3], state: "minecraft:air"}, {pos: [0, 3, 3], state: "minecraft:white_concrete"},
{pos: [0, 3, 4], state: "minecraft:air"}, {pos: [0, 3, 4], state: "minecraft:white_concrete"},
{pos: [1, 3, 0], state: "minecraft:air"}, {pos: [1, 3, 0], state: "minecraft:white_concrete"},
{pos: [1, 3, 1], state: "minecraft:air"}, {pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"}, {pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"}, {pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"}, {pos: [1, 3, 4], state: "minecraft:white_concrete"},
{pos: [2, 3, 0], state: "minecraft:air"}, {pos: [2, 3, 0], state: "minecraft:white_concrete"},
{pos: [2, 3, 1], state: "minecraft:air"}, {pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"}, {pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"}, {pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"}, {pos: [2, 3, 4], state: "minecraft:white_concrete"},
{pos: [3, 3, 0], state: "minecraft:air"}, {pos: [3, 3, 0], state: "minecraft:white_concrete"},
{pos: [3, 3, 1], state: "minecraft:air"}, {pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"}, {pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"}, {pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"}, {pos: [3, 3, 4], state: "minecraft:white_concrete"},
{pos: [4, 3, 0], state: "minecraft:air"}, {pos: [4, 3, 0], state: "minecraft:white_concrete"},
{pos: [4, 3, 1], state: "minecraft:air"}, {pos: [4, 3, 1], state: "minecraft:white_concrete"},
{pos: [4, 3, 2], state: "minecraft:air"}, {pos: [4, 3, 2], state: "minecraft:white_concrete"},
{pos: [4, 3, 3], state: "minecraft:air"}, {pos: [4, 3, 3], state: "minecraft:white_concrete"},
{pos: [4, 3, 4], state: "minecraft:air"}, {pos: [4, 3, 4], state: "minecraft:white_concrete"},
{pos: [0, 4, 0], state: "minecraft:air"}, {pos: [0, 4, 0], state: "minecraft:white_concrete"},
{pos: [0, 4, 1], state: "minecraft:air"}, {pos: [0, 4, 1], state: "minecraft:white_concrete"},
{pos: [0, 4, 2], state: "minecraft:air"}, {pos: [0, 4, 2], state: "minecraft:white_concrete"},
{pos: [0, 4, 3], state: "minecraft:air"}, {pos: [0, 4, 3], state: "minecraft:white_concrete"},
{pos: [0, 4, 4], state: "minecraft:air"}, {pos: [0, 4, 4], state: "minecraft:white_concrete"},
{pos: [1, 4, 0], state: "minecraft:air"}, {pos: [1, 4, 0], state: "minecraft:white_concrete"},
{pos: [1, 4, 1], state: "minecraft:air"}, {pos: [1, 4, 1], state: "minecraft:white_concrete"},
{pos: [1, 4, 2], state: "minecraft:air"}, {pos: [1, 4, 2], state: "minecraft:white_concrete"},
{pos: [1, 4, 3], state: "minecraft:air"}, {pos: [1, 4, 3], state: "minecraft:shroomlight"},
{pos: [1, 4, 4], state: "minecraft:air"}, {pos: [1, 4, 4], state: "minecraft:white_concrete"},
{pos: [2, 4, 0], state: "minecraft:air"}, {pos: [2, 4, 0], state: "minecraft:white_concrete"},
{pos: [2, 4, 1], state: "minecraft:air"}, {pos: [2, 4, 1], state: "minecraft:white_concrete"},
{pos: [2, 4, 2], state: "minecraft:air"}, {pos: [2, 4, 2], state: "minecraft:white_concrete"},
{pos: [2, 4, 3], state: "minecraft:air"}, {pos: [2, 4, 3], state: "minecraft:white_concrete"},
{pos: [2, 4, 4], state: "minecraft:air"}, {pos: [2, 4, 4], state: "minecraft:white_concrete"},
{pos: [3, 4, 0], state: "minecraft:air"}, {pos: [3, 4, 0], state: "minecraft:white_concrete"},
{pos: [3, 4, 1], state: "minecraft:air"}, {pos: [3, 4, 1], state: "minecraft:white_concrete"},
{pos: [3, 4, 2], state: "minecraft:air"}, {pos: [3, 4, 2], state: "minecraft:white_concrete"},
{pos: [3, 4, 3], state: "minecraft:air"}, {pos: [3, 4, 3], state: "minecraft:shroomlight"},
{pos: [3, 4, 4], state: "minecraft:air"}, {pos: [3, 4, 4], state: "minecraft:white_concrete"},
{pos: [4, 4, 0], state: "minecraft:air"}, {pos: [4, 4, 0], state: "minecraft:white_concrete"},
{pos: [4, 4, 1], state: "minecraft:air"}, {pos: [4, 4, 1], state: "minecraft:white_concrete"},
{pos: [4, 4, 2], state: "minecraft:air"}, {pos: [4, 4, 2], state: "minecraft:white_concrete"},
{pos: [4, 4, 3], state: "minecraft:air"}, {pos: [4, 4, 3], state: "minecraft:white_concrete"},
{pos: [4, 4, 4], state: "minecraft:air"} {pos: [4, 4, 4], state: "minecraft:white_concrete"}
], ],
entities: [ 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: [ palette: [
"minecraft:polished_andesite", "minecraft:polished_andesite",
"minecraft:white_concrete",
"minecraft:shroomlight",
"minecraft:air", "minecraft:air",
"computercraft:monitor_advanced{facing:north,orientation:north,state:lu}", "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: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 sys
import nbtlib import nbtlib
from nbtlib.tag import Compound, Int, List, String from nbtlib.tag import Compound, Int, List, String