mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-04 23:40:00 +00:00
Merge branch 'mc-1.14.x' into mc-1.15.x
# Conflicts: # gradle.properties # src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java # src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java # src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java # src/main/java/dan200/computercraft/shared/Config.java # src/main/java/dan200/computercraft/shared/network/NetworkHandler.java # src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java # src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java # src/main/resources/data/computercraft/lua/rom/help/changelog.txt # src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt
This commit is contained in:
commit
2b077554f7
@ -1,5 +1,5 @@
|
||||
# Mod properties
|
||||
mod_version=1.88.1
|
||||
mod_version=1.89.0
|
||||
|
||||
# Minecraft properties (update mods.toml when changing)
|
||||
mc_version=1.15.2
|
||||
|
@ -99,6 +99,7 @@ public final class ComputerCraft
|
||||
public static int modemHighAltitudeRangeDuringStorm = 384;
|
||||
public static int maxNotesPerTick = 8;
|
||||
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
|
||||
public static long monitorBandwidth = 1_000_000;
|
||||
|
||||
public static boolean turtlesNeedFuel = true;
|
||||
public static int turtleFuelLimit = 20000;
|
||||
|
@ -15,7 +15,6 @@ import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.renderer.Matrix4f;
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
@ -30,7 +29,6 @@ class MonitorTextureBufferShader
|
||||
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
|
||||
|
||||
private static int uniformMv;
|
||||
private static int uniformP;
|
||||
|
||||
private static int uniformFont;
|
||||
private static int uniformWidth;
|
||||
@ -49,12 +47,6 @@ class MonitorTextureBufferShader
|
||||
MATRIX_BUFFER.rewind();
|
||||
RenderSystem.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER );
|
||||
|
||||
// TODO: Cache this?
|
||||
MATRIX_BUFFER.rewind();
|
||||
GL11.glGetFloatv( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER );
|
||||
MATRIX_BUFFER.rewind();
|
||||
RenderSystem.glUniformMatrix4( uniformP, false, MATRIX_BUFFER );
|
||||
|
||||
RenderSystem.glUniform1i( uniformWidth, width );
|
||||
RenderSystem.glUniform1i( uniformHeight, height );
|
||||
|
||||
@ -125,8 +117,6 @@ class MonitorTextureBufferShader
|
||||
if( !ok ) return false;
|
||||
|
||||
uniformMv = getUniformLocation( program, "u_mv" );
|
||||
uniformP = getUniformLocation( program, "u_p" );
|
||||
|
||||
uniformFont = getUniformLocation( program, "u_font" );
|
||||
uniformWidth = getUniformLocation( program, "u_width" );
|
||||
uniformHeight = getUniformLocation( program, "u_height" );
|
||||
|
@ -41,6 +41,7 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
* the monitor frame and contents.
|
||||
*/
|
||||
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
|
||||
private static ByteBuffer tboContents;
|
||||
|
||||
private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix();
|
||||
|
||||
@ -153,6 +154,60 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
|
||||
switch( renderType )
|
||||
{
|
||||
case TBO:
|
||||
{
|
||||
if( !MonitorTextureBufferShader.use() ) return;
|
||||
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
|
||||
if( redraw )
|
||||
{
|
||||
int size = width * height * 3;
|
||||
if( tboContents == null || tboContents.capacity() < size )
|
||||
{
|
||||
tboContents = GLAllocation.createDirectByteBuffer( size );
|
||||
}
|
||||
|
||||
ByteBuffer monitorBuffer = tboContents;
|
||||
monitorBuffer.position( 0 );
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
|
||||
for( int x = 0; x < width; x++ )
|
||||
{
|
||||
monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) );
|
||||
monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.WHITE ) );
|
||||
monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.BLACK ) );
|
||||
}
|
||||
}
|
||||
monitorBuffer.flip();
|
||||
|
||||
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
|
||||
GlStateManager.bufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL20.GL_STATIC_DRAW );
|
||||
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||
}
|
||||
|
||||
// Nobody knows what they're doing!
|
||||
GlStateManager.activeTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
|
||||
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
|
||||
GlStateManager.activeTexture( GL13.GL_TEXTURE0 );
|
||||
|
||||
MonitorTextureBufferShader.setupUniform( matrix, width, height, terminal.getPalette(), !monitor.isColour() );
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
GlStateManager.useProgram( 0 );
|
||||
break;
|
||||
}
|
||||
|
||||
case VBO:
|
||||
{
|
||||
VertexBuffer vbo = monitor.buffer;
|
||||
@ -177,53 +232,6 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
|
||||
FixedWidthFontRenderer.TYPE.getVertexFormat().clearBufferState();
|
||||
break;
|
||||
}
|
||||
|
||||
case TBO:
|
||||
{
|
||||
if( !MonitorTextureBufferShader.use() ) return;
|
||||
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
|
||||
if( redraw )
|
||||
{
|
||||
ByteBuffer buffer = GLAllocation.createDirectByteBuffer( width * height * 3 );
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
|
||||
for( int x = 0; x < width; x++ )
|
||||
{
|
||||
buffer.put( (byte) (text.charAt( x ) & 0xFF) );
|
||||
buffer.put( (byte) getColour( textColour.charAt( x ), Colour.WHITE ) );
|
||||
buffer.put( (byte) getColour( background.charAt( x ), Colour.BLACK ) );
|
||||
}
|
||||
}
|
||||
buffer.flip();
|
||||
|
||||
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
|
||||
GlStateManager.bufferData( GL31.GL_TEXTURE_BUFFER, buffer, GL20.GL_STATIC_DRAW );
|
||||
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
|
||||
}
|
||||
|
||||
// Nobody knows what they're doing!
|
||||
GlStateManager.activeTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
|
||||
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
|
||||
GlStateManager.activeTexture( GL13.GL_TEXTURE0 );
|
||||
|
||||
MonitorTextureBufferShader.setupUniform( matrix, width, height, terminal.getPalette(), !monitor.isColour() );
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
|
||||
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
|
||||
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
|
||||
tessellator.draw();
|
||||
|
||||
GlStateManager.useProgram( 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,12 +35,8 @@ public abstract class HandleGeneric
|
||||
{
|
||||
open = false;
|
||||
|
||||
Closeable closeable = closable;
|
||||
if( closeable != null )
|
||||
{
|
||||
IoUtil.closeQuietly( closeable );
|
||||
closable = null;
|
||||
}
|
||||
IoUtil.closeQuietly( closable );
|
||||
closable = null;
|
||||
}
|
||||
|
||||
@LuaFunction( "close" )
|
||||
|
@ -106,7 +106,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
|
||||
|
||||
protected static <T extends Closeable> T closeCloseable( T closeable )
|
||||
{
|
||||
if( closeable != null ) IoUtil.closeQuietly( closeable );
|
||||
IoUtil.closeQuietly( closeable );
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ public class Websocket extends Resource<Websocket>
|
||||
|
||||
WeakReference<WebsocketHandle> websocketHandleRef = websocketHandle;
|
||||
WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get();
|
||||
if( websocketHandle != null ) IoUtil.closeQuietly( websocketHandle );
|
||||
IoUtil.closeQuietly( websocketHandle );
|
||||
this.websocketHandle = null;
|
||||
}
|
||||
|
||||
|
@ -366,8 +366,7 @@ public class FileSystem
|
||||
Reference<?> ref;
|
||||
while( (ref = m_openFileQueue.poll()) != null )
|
||||
{
|
||||
Closeable file = m_openFiles.remove( ref );
|
||||
if( file != null ) IoUtil.closeQuietly( file );
|
||||
IoUtil.closeQuietly( m_openFiles.remove( ref ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ public final class Config
|
||||
private static final ConfigValue<Integer> modemRangeDuringStorm;
|
||||
private static final ConfigValue<Integer> modemHighAltitudeRangeDuringStorm;
|
||||
private static final ConfigValue<Integer> maxNotesPerTick;
|
||||
private static final ConfigValue<Integer> monitorBandwidth;
|
||||
|
||||
private static final ConfigValue<Boolean> turtlesNeedFuel;
|
||||
private static final ConfigValue<Integer> turtleFuelLimit;
|
||||
@ -209,6 +210,16 @@ public final class Config
|
||||
.comment( "Maximum amount of notes a speaker can play at once" )
|
||||
.defineInRange( "max_notes_per_tick", ComputerCraft.maxNotesPerTick, 1, Integer.MAX_VALUE );
|
||||
|
||||
monitorBandwidth = builder
|
||||
.comment( "The limit to how much monitor data can be sent *per tick*. Note:\n" +
|
||||
" - Bandwidth is measured before compression, so the data sent to the client is smaller.\n" +
|
||||
" - This ignores the number of players a packet is sent to. Updating a monitor for one player consumes " +
|
||||
"the same bandwidth limit as sending to 20.\n" +
|
||||
" - A full sized monitor sends ~25kb of data. So the default (1MB) allows for ~40 monitors to be updated " +
|
||||
"in a single tick. \n" +
|
||||
"Set to 0 to disable." )
|
||||
.defineInRange( "monitor_bandwidth", (int) ComputerCraft.monitorBandwidth, 0, Integer.MAX_VALUE );
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
@ -292,6 +303,7 @@ public final class Config
|
||||
ComputerCraft.modemHighAltitudeRange = modemHighAltitudeRange.get();
|
||||
ComputerCraft.modemRangeDuringStorm = modemRangeDuringStorm.get();
|
||||
ComputerCraft.modemHighAltitudeRangeDuringStorm = modemHighAltitudeRangeDuringStorm.get();
|
||||
ComputerCraft.monitorBandwidth = monitorBandwidth.get();
|
||||
|
||||
// Turtles
|
||||
ComputerCraft.turtlesNeedFuel = turtlesNeedFuel.get();
|
||||
|
@ -6,9 +6,7 @@
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
|
||||
public class ClientTerminal implements ITerminal
|
||||
{
|
||||
@ -48,14 +46,13 @@ public class ClientTerminal implements ITerminal
|
||||
return m_colour;
|
||||
}
|
||||
|
||||
public void readDescription( CompoundNBT nbt )
|
||||
public void read( TerminalState state )
|
||||
{
|
||||
m_colour = nbt.getBoolean( "colour" );
|
||||
if( nbt.contains( "terminal" ) )
|
||||
m_colour = state.colour;
|
||||
if( state.hasTerminal() )
|
||||
{
|
||||
CompoundNBT terminal = nbt.getCompound( "terminal" );
|
||||
resizeTerminal( terminal.getInt( "term_width" ), terminal.getInt( "term_height" ) );
|
||||
m_terminal.read( new PacketBuffer( Unpooled.wrappedBuffer( terminal.getByteArray( "term_contents" ) ) ) );
|
||||
resizeTerminal( state.width, state.height );
|
||||
state.apply( m_terminal );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -5,12 +5,8 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@ -73,8 +69,6 @@ public class ServerTerminal implements ITerminal
|
||||
return m_terminalChangedLastFrame;
|
||||
}
|
||||
|
||||
// ITerminal implementation
|
||||
|
||||
@Override
|
||||
public Terminal getTerminal()
|
||||
{
|
||||
@ -87,29 +81,8 @@ public class ServerTerminal implements ITerminal
|
||||
return m_colour;
|
||||
}
|
||||
|
||||
public void writeDescription( CompoundNBT nbt )
|
||||
public TerminalState write()
|
||||
{
|
||||
nbt.putBoolean( "colour", m_colour );
|
||||
if( m_terminal != null )
|
||||
{
|
||||
// We have a 10 byte header (2 integer positions, then blinking and current colours), followed by the
|
||||
// contents and palette.
|
||||
// Yes, this serialisation code is terrible, but we need to serialise to NBT in order to work with monitors
|
||||
// (or rather tile entity serialisation).
|
||||
final int length = 10 + (2 * m_terminal.getWidth() * m_terminal.getHeight()) + (16 * 3);
|
||||
ByteBuf buffer = Unpooled.buffer( length );
|
||||
m_terminal.write( new PacketBuffer( buffer ) );
|
||||
|
||||
if( buffer.writableBytes() != 0 )
|
||||
{
|
||||
ComputerCraft.log.warn( "Should have written {} bytes, but have {} ({} remaining).", length, buffer.writerIndex(), buffer.writableBytes() );
|
||||
}
|
||||
|
||||
CompoundNBT terminal = new CompoundNBT();
|
||||
terminal.putInt( "term_width", m_terminal.getWidth() );
|
||||
terminal.putInt( "term_height", m_terminal.getHeight() );
|
||||
terminal.putByteArray( "term_contents", buffer.array() );
|
||||
nbt.put( "terminal", terminal );
|
||||
}
|
||||
return new TerminalState( m_colour, m_terminal );
|
||||
}
|
||||
}
|
||||
|
@ -154,9 +154,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
|
||||
|
||||
protected NetworkMessage createTerminalPacket()
|
||||
{
|
||||
CompoundNBT tagCompound = new CompoundNBT();
|
||||
writeDescription( tagCompound );
|
||||
return new ComputerTerminalClientMessage( getInstanceID(), tagCompound );
|
||||
return new ComputerTerminalClientMessage( getInstanceID(), write() );
|
||||
}
|
||||
|
||||
public void broadcastState( boolean force )
|
||||
|
@ -14,9 +14,11 @@ import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraftforge.fml.network.NetworkDirection;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
import net.minecraftforge.fml.network.NetworkRegistry;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
import net.minecraftforge.fml.network.simple.SimpleChannel;
|
||||
import net.minecraftforge.fml.server.ServerLifecycleHooks;
|
||||
|
||||
@ -52,6 +54,7 @@ public final class NetworkHandler
|
||||
registerMainThread( 12, ComputerDeletedClientMessage::new );
|
||||
registerMainThread( 13, ComputerTerminalClientMessage::new );
|
||||
registerMainThread( 14, PlayRecordClientMessage.class, PlayRecordClientMessage::new );
|
||||
registerMainThread( 15, MonitorClientMessage.class, MonitorClientMessage::new );
|
||||
}
|
||||
|
||||
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
|
||||
@ -74,13 +77,13 @@ public final class NetworkHandler
|
||||
|
||||
public static void sendToAllAround( NetworkMessage packet, World world, Vec3d pos, double range )
|
||||
{
|
||||
for( ServerPlayerEntity player : ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers() )
|
||||
{
|
||||
if( player.getEntityWorld() == world && player.getDistanceSq( pos ) < range * range )
|
||||
{
|
||||
sendToPlayer( player, packet );
|
||||
}
|
||||
}
|
||||
PacketDistributor.TargetPoint target = new PacketDistributor.TargetPoint( pos.x, pos.y, pos.z, range, world.getDimension().getType() );
|
||||
network.send( PacketDistributor.NEAR.with( () -> target ), packet );
|
||||
}
|
||||
|
||||
public static void sendToAllTracking( NetworkMessage packet, Chunk chunk )
|
||||
{
|
||||
network.send( PacketDistributor.TRACKING_CHUNK.with( () -> chunk ), packet );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,6 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
@ -13,12 +12,12 @@ import javax.annotation.Nonnull;
|
||||
|
||||
public class ComputerTerminalClientMessage extends ComputerClientMessage
|
||||
{
|
||||
private CompoundNBT tag;
|
||||
private TerminalState state;
|
||||
|
||||
public ComputerTerminalClientMessage( int instanceId, CompoundNBT tag )
|
||||
public ComputerTerminalClientMessage( int instanceId, TerminalState state )
|
||||
{
|
||||
super( instanceId );
|
||||
this.tag = tag;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public ComputerTerminalClientMessage()
|
||||
@ -29,19 +28,19 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
super.toBytes( buf );
|
||||
buf.writeCompoundTag( tag ); // TODO: Do we need to compress this?
|
||||
state.write( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
super.fromBytes( buf );
|
||||
tag = buf.readCompoundTag();
|
||||
state = new TerminalState( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
getComputer().readDescription( tag );
|
||||
getComputer().read( state );
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.player.ClientPlayerEntity;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class MonitorClientMessage implements NetworkMessage
|
||||
{
|
||||
private final BlockPos pos;
|
||||
private final TerminalState state;
|
||||
|
||||
public MonitorClientMessage( BlockPos pos, TerminalState state )
|
||||
{
|
||||
this.pos = pos;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public MonitorClientMessage( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
pos = buf.readBlockPos();
|
||||
state = new TerminalState( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
buf.writeBlockPos( pos );
|
||||
state.write( buf );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
ClientPlayerEntity player = Minecraft.getInstance().player;
|
||||
if( player == null || player.world == null ) return;
|
||||
|
||||
TileEntity te = player.world.getTileEntity( pos );
|
||||
if( !(te instanceof TileMonitor) ) return;
|
||||
|
||||
((TileMonitor) te).read( state );
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.IoUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* A snapshot of a terminal's state.
|
||||
*
|
||||
* This is somewhat memory inefficient (we build a buffer, only to write it elsewhere), however it means we get a
|
||||
* complete and accurate description of a terminal, which avoids a lot of complexities with resizing terminals, dirty
|
||||
* states, etc...
|
||||
*/
|
||||
public class TerminalState
|
||||
{
|
||||
public final boolean colour;
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
|
||||
private final boolean compress;
|
||||
|
||||
@Nullable
|
||||
private final ByteBuf buffer;
|
||||
|
||||
private ByteBuf compressed;
|
||||
|
||||
public TerminalState( boolean colour, @Nullable Terminal terminal )
|
||||
{
|
||||
this( colour, terminal, true );
|
||||
}
|
||||
|
||||
public TerminalState( boolean colour, @Nullable Terminal terminal, boolean compress )
|
||||
{
|
||||
this.colour = colour;
|
||||
this.compress = compress;
|
||||
|
||||
if( terminal == null )
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = terminal.getWidth();
|
||||
this.height = terminal.getHeight();
|
||||
|
||||
ByteBuf buf = this.buffer = Unpooled.buffer();
|
||||
terminal.write( new PacketBuffer( buf ) );
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalState( PacketBuffer buf )
|
||||
{
|
||||
this.colour = buf.readBoolean();
|
||||
this.compress = buf.readBoolean();
|
||||
|
||||
if( buf.readBoolean() )
|
||||
{
|
||||
this.width = buf.readVarInt();
|
||||
this.height = buf.readVarInt();
|
||||
|
||||
int length = buf.readVarInt();
|
||||
this.buffer = readCompressed( buf, length, compress );
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void write( PacketBuffer buf )
|
||||
{
|
||||
buf.writeBoolean( colour );
|
||||
buf.writeBoolean( compress );
|
||||
|
||||
buf.writeBoolean( buffer != null );
|
||||
if( buffer != null )
|
||||
{
|
||||
buf.writeVarInt( width );
|
||||
buf.writeVarInt( height );
|
||||
|
||||
ByteBuf sendBuffer = getCompressed();
|
||||
buf.writeVarInt( sendBuffer.readableBytes() );
|
||||
buf.writeBytes( sendBuffer, sendBuffer.readerIndex(), sendBuffer.readableBytes() );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTerminal()
|
||||
{
|
||||
return buffer != null;
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return buffer == null ? 0 : buffer.readableBytes();
|
||||
}
|
||||
|
||||
public void apply( Terminal terminal )
|
||||
{
|
||||
if( buffer == null ) throw new NullPointerException( "buffer" );
|
||||
terminal.read( new PacketBuffer( buffer ) );
|
||||
}
|
||||
|
||||
private ByteBuf getCompressed()
|
||||
{
|
||||
if( buffer == null ) throw new NullPointerException( "buffer" );
|
||||
if( !compress ) return buffer;
|
||||
if( compressed != null ) return compressed;
|
||||
|
||||
ByteBuf compressed = Unpooled.directBuffer();
|
||||
OutputStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new GZIPOutputStream( new ByteBufOutputStream( compressed ) );
|
||||
stream.write( buffer.array(), buffer.arrayOffset(), buffer.readableBytes() );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new UncheckedIOException( e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
IoUtil.closeQuietly( stream );
|
||||
}
|
||||
|
||||
return this.compressed = compressed;
|
||||
}
|
||||
|
||||
private static ByteBuf readCompressed( ByteBuf buf, int length, boolean compress )
|
||||
{
|
||||
if( compress )
|
||||
{
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
InputStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new GZIPInputStream( new ByteBufInputStream( buf, length ) );
|
||||
byte[] swap = new byte[8192];
|
||||
while( true )
|
||||
{
|
||||
int bytes = stream.read( swap );
|
||||
if( bytes == -1 ) break;
|
||||
buffer.writeBytes( swap, 0, bytes );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new UncheckedIOException( e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
IoUtil.closeQuietly( stream );
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuf buffer = Unpooled.buffer( length );
|
||||
buf.readBytes( buffer, length );
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.peripheral.monitor;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.MonitorClientMessage;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.world.ChunkWatchEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
|
||||
public final class MonitorWatcher
|
||||
{
|
||||
private static final Queue<TileMonitor> watching = new ArrayDeque<>();
|
||||
|
||||
private MonitorWatcher()
|
||||
{
|
||||
}
|
||||
|
||||
static void enqueue( TileMonitor monitor )
|
||||
{
|
||||
if( monitor.enqueued ) return;
|
||||
|
||||
monitor.enqueued = true;
|
||||
monitor.cached = null;
|
||||
watching.add( monitor );
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onWatch( ChunkWatchEvent.Watch event )
|
||||
{
|
||||
ChunkPos chunkPos = event.getPos();
|
||||
Chunk chunk = event.getWorld().getChunk( chunkPos.x, chunkPos.z );
|
||||
if( chunk == null ) return;
|
||||
|
||||
for( TileEntity te : chunk.getTileEntityMap().values() )
|
||||
{
|
||||
// Find all origin monitors who are not already on the queue.
|
||||
if( !(te instanceof TileMonitor) ) continue;
|
||||
|
||||
TileMonitor monitor = (TileMonitor) te;
|
||||
ServerMonitor serverMonitor = getMonitor( monitor );
|
||||
if( serverMonitor == null || monitor.enqueued ) continue;
|
||||
|
||||
// We use the cached terminal state if available - this is guaranteed to
|
||||
TerminalState state = monitor.cached;
|
||||
if( state == null ) state = monitor.cached = serverMonitor.write();
|
||||
NetworkHandler.sendToPlayer( event.getPlayer(), new MonitorClientMessage( monitor.getPos(), state ) );
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onTick( TickEvent.ServerTickEvent event )
|
||||
{
|
||||
if( event.phase != TickEvent.Phase.END ) return;
|
||||
|
||||
long limit = ComputerCraft.monitorBandwidth;
|
||||
boolean obeyLimit = limit > 0;
|
||||
|
||||
TileMonitor tile;
|
||||
while( (!obeyLimit || limit > 0) && (tile = watching.poll()) != null )
|
||||
{
|
||||
tile.enqueued = false;
|
||||
ServerMonitor monitor = getMonitor( tile );
|
||||
if( monitor == null ) continue;
|
||||
|
||||
BlockPos pos = tile.getPos();
|
||||
World world = tile.getWorld();
|
||||
if( !(world instanceof ServerWorld) ) continue;
|
||||
|
||||
Chunk chunk = world.getChunkAt( pos );
|
||||
if( !((ServerWorld) world).getChunkProvider().chunkManager.getTrackingPlayers( chunk.getPos(), false ).findAny().isPresent() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TerminalState state = tile.cached = monitor.write();
|
||||
NetworkHandler.sendToAllTracking( new MonitorClientMessage( pos, state ), chunk );
|
||||
|
||||
limit -= state.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static ServerMonitor getMonitor( TileMonitor monitor )
|
||||
{
|
||||
return !monitor.isRemoved() && monitor.getXIndex() == 0 && monitor.getYIndex() == 0 ? monitor.getCachedServerMonitor() : null;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.common.ServerTerminal;
|
||||
import dan200.computercraft.shared.common.TileGeneric;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import dan200.computercraft.shared.network.client.TerminalState;
|
||||
import dan200.computercraft.shared.util.NamedTileEntityType;
|
||||
import dan200.computercraft.shared.util.TickScheduler;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
@ -71,6 +72,10 @@ public class TileMonitor extends TileGeneric
|
||||
private boolean m_destroyed = false;
|
||||
private boolean visiting = false;
|
||||
|
||||
// MonitorWatcher state.
|
||||
boolean enqueued;
|
||||
TerminalState cached;
|
||||
|
||||
private int m_width = 1;
|
||||
private int m_height = 1;
|
||||
private int m_xIndex = 0;
|
||||
@ -177,7 +182,7 @@ public class TileMonitor extends TileGeneric
|
||||
}
|
||||
}
|
||||
|
||||
if( m_serverMonitor.pollTerminalChanged() ) updateBlock();
|
||||
if( m_serverMonitor.pollTerminalChanged() ) MonitorWatcher.enqueue( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -267,16 +272,10 @@ public class TileMonitor extends TileGeneric
|
||||
protected void writeDescription( @Nonnull CompoundNBT nbt )
|
||||
{
|
||||
super.writeDescription( nbt );
|
||||
|
||||
nbt.putInt( NBT_X, m_xIndex );
|
||||
nbt.putInt( NBT_Y, m_yIndex );
|
||||
nbt.putInt( NBT_WIDTH, m_width );
|
||||
nbt.putInt( NBT_HEIGHT, m_height );
|
||||
|
||||
if( m_xIndex == 0 && m_yIndex == 0 && m_serverMonitor != null )
|
||||
{
|
||||
m_serverMonitor.writeDescription( nbt );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -304,9 +303,8 @@ public class TileMonitor extends TileGeneric
|
||||
|
||||
if( m_xIndex == 0 && m_yIndex == 0 )
|
||||
{
|
||||
// If we're the origin terminal then read the description
|
||||
// If we're the origin terminal then create it.
|
||||
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( advanced, this );
|
||||
m_clientMonitor.readDescription( nbt );
|
||||
}
|
||||
|
||||
if( oldXIndex != m_xIndex || oldYIndex != m_yIndex ||
|
||||
@ -317,6 +315,20 @@ public class TileMonitor extends TileGeneric
|
||||
}
|
||||
}
|
||||
|
||||
public final void read( TerminalState state )
|
||||
{
|
||||
if( m_xIndex != 0 || m_yIndex != 0 )
|
||||
{
|
||||
ComputerCraft.log.warn( "Receiving monitor state for non-origin terminal at {}", getPos() );
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( advanced, this );
|
||||
m_clientMonitor.read( state );
|
||||
}
|
||||
|
||||
// Sizing and placement stuff
|
||||
|
||||
private void updateBlockState()
|
||||
{
|
||||
getWorld().setBlockState( getPos(), getBlockState()
|
||||
|
@ -17,6 +17,7 @@ import dan200.computercraft.core.tracking.TrackingField;
|
||||
import dan200.computercraft.shared.turtle.core.*;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
@ -325,6 +326,10 @@ public class TurtleAPI implements ILuaAPI
|
||||
table.put( "name", name );
|
||||
table.put( "count", count );
|
||||
|
||||
Map<String, Boolean> tags = new HashMap<>();
|
||||
for( ResourceLocation location : item.getTags() ) tags.put( location.toString(), true );
|
||||
table.put( "tags", tags );
|
||||
|
||||
TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table );
|
||||
if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() };
|
||||
|
||||
|
@ -14,6 +14,7 @@ import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.state.IProperty;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
@ -64,6 +65,10 @@ public class TurtleInspectCommand implements ITurtleCommand
|
||||
}
|
||||
table.put( "state", stateTable );
|
||||
|
||||
Map<String, Boolean> tags = new HashMap<>();
|
||||
for( ResourceLocation location : block.getTags() ) tags.put( location.toString(), true );
|
||||
table.put( "tags", tags );
|
||||
|
||||
// Fire the event, exiting if it is cancelled
|
||||
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction );
|
||||
TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table );
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
@ -12,11 +13,11 @@ public final class IoUtil
|
||||
{
|
||||
private IoUtil() {}
|
||||
|
||||
public static void closeQuietly( Closeable closeable )
|
||||
public static void closeQuietly( @Nullable Closeable closeable )
|
||||
{
|
||||
try
|
||||
{
|
||||
closeable.close();
|
||||
if( closeable != null ) closeable.close();
|
||||
}
|
||||
catch( IOException ignored )
|
||||
{
|
||||
|
@ -35,6 +35,6 @@ void main() {
|
||||
int bg = int(texelFetch(u_tbo, index + 2).r * 255.0);
|
||||
|
||||
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
|
||||
vec4 img = texture2D(u_font, (texture_corner(character) + pos) / 256.0);
|
||||
vec4 img = texture(u_font, (texture_corner(character) + pos) / 256.0);
|
||||
colour = vec4(mix(u_palette[bg], img.rgb * u_palette[fg], img.a * mult), 1.0);
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
#version 140
|
||||
#version 130
|
||||
|
||||
uniform mat4 u_mv;
|
||||
uniform mat4 u_p;
|
||||
|
||||
in vec3 v_pos;
|
||||
|
||||
out vec2 f_pos;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_p * u_mv * vec4(v_pos.x, v_pos.y, 0, 1);
|
||||
gl_Position = gl_ProjectionMatrix * u_mv * vec4(v_pos.x, v_pos.y, 0, 1);
|
||||
f_pos = v_pos.xy;
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ function load(sPath)
|
||||
end
|
||||
|
||||
for k, v in pairs(tFile) do
|
||||
local ty_v = type(k)
|
||||
local ty_v = type(v)
|
||||
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
|
||||
local opt = details[k]
|
||||
if not opt or not opt.type or ty_v == opt.type then
|
||||
|
@ -1,3 +1,14 @@
|
||||
# New features in CC: Tweaked 1.89.0
|
||||
|
||||
* Compress monitor data, reducing network traffic by a significant amount.
|
||||
* Allow limiting the bandwidth monitor updates use.
|
||||
* Several optimisations to monitor rendering (@Lignum).
|
||||
* Expose block and item tags to turtle.inspect and turtle.getItemDetail.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix settings.load failing on defined settings.
|
||||
* Fix name of the `ejectDisk` peripheral method.
|
||||
|
||||
# New features in CC: Tweaked 1.88.1
|
||||
|
||||
* Fix error on objects with too many methods.
|
||||
|
@ -1,5 +1,12 @@
|
||||
New features in CC: Tweaked 1.88.1
|
||||
New features in CC: Tweaked 1.89.0
|
||||
|
||||
* Fix error on objects with too many methods.
|
||||
* Compress monitor data, reducing network traffic by a significant amount.
|
||||
* Allow limiting the bandwidth monitor updates use.
|
||||
* Several optimisations to monitor rendering (@Lignum).
|
||||
* Expose block and item tags to turtle.inspect and turtle.getItemDetail.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix settings.load failing on defined settings.
|
||||
* Fix name of the `ejectDisk` peripheral method.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests {@link TerminalState} round tripping works as expected.
|
||||
*/
|
||||
public class TerminalStateTest
|
||||
{
|
||||
@RepeatedTest( 5 )
|
||||
public void testCompressed()
|
||||
{
|
||||
Terminal terminal = randomTerminal();
|
||||
|
||||
PacketBuffer buffer = new PacketBuffer( Unpooled.directBuffer() );
|
||||
new TerminalState( true, terminal, true ).write( buffer );
|
||||
|
||||
checkEqual( terminal, read( buffer ) );
|
||||
assertEquals( 0, buffer.readableBytes() );
|
||||
}
|
||||
|
||||
@RepeatedTest( 5 )
|
||||
public void testUncompressed()
|
||||
{
|
||||
Terminal terminal = randomTerminal();
|
||||
|
||||
PacketBuffer buffer = new PacketBuffer( Unpooled.directBuffer() );
|
||||
new TerminalState( true, terminal, false ).write( buffer );
|
||||
|
||||
checkEqual( terminal, read( buffer ) );
|
||||
assertEquals( 0, buffer.readableBytes() );
|
||||
}
|
||||
|
||||
private static Terminal randomTerminal()
|
||||
{
|
||||
Random random = new Random();
|
||||
Terminal terminal = new Terminal( 10, 5 );
|
||||
for( int y = 0; y < terminal.getHeight(); y++ )
|
||||
{
|
||||
TextBuffer buffer = terminal.getLine( y );
|
||||
for( int x = 0; x < buffer.length(); x++ ) buffer.setChar( x, (char) (random.nextInt( 26 ) + 65) );
|
||||
}
|
||||
|
||||
return terminal;
|
||||
}
|
||||
|
||||
private static void checkEqual( Terminal expected, Terminal actual )
|
||||
{
|
||||
assertNotNull( expected, "Expected cannot be null" );
|
||||
assertNotNull( actual, "Actual cannot be null" );
|
||||
assertEquals( expected.getHeight(), actual.getHeight(), "Heights must match" );
|
||||
assertEquals( expected.getWidth(), actual.getWidth(), "Widths must match" );
|
||||
|
||||
for( int y = 0; y < expected.getHeight(); y++ )
|
||||
{
|
||||
assertEquals( expected.getLine( y ).toString(), actual.getLine( y ).toString() );
|
||||
}
|
||||
}
|
||||
|
||||
private static Terminal read( PacketBuffer buffer )
|
||||
{
|
||||
TerminalState state = new TerminalState( buffer );
|
||||
assertTrue( state.colour );
|
||||
|
||||
if( !state.hasTerminal() ) return null;
|
||||
|
||||
Terminal other = new Terminal( state.width, state.height );
|
||||
state.apply( other );
|
||||
return other;
|
||||
}
|
||||
}
|
@ -153,11 +153,50 @@ describe("The settings library", function()
|
||||
expect.error(settings.load, 1):eq("bad argument #1 (expected string, got number)")
|
||||
end)
|
||||
|
||||
local function setup_with(contents)
|
||||
settings.clear()
|
||||
local h = fs.open("/test-files/.settings", "w")
|
||||
h.write(contents)
|
||||
h.close()
|
||||
|
||||
return settings.load("/test-files/.settings")
|
||||
end
|
||||
|
||||
local function setup(contents)
|
||||
return setup_with(textutils.serialize(contents))
|
||||
end
|
||||
|
||||
it("defaults to .settings", function()
|
||||
local s = stub(fs, "open")
|
||||
settings.load()
|
||||
expect(s):called_with(".settings", "r")
|
||||
end)
|
||||
|
||||
it("loads undefined settings", function()
|
||||
expect(setup { ["test"] = 1 }):eq(true)
|
||||
expect(settings.get("test")):eq(1)
|
||||
end)
|
||||
|
||||
it("loads defined settings", function()
|
||||
settings.define("test.defined", { type = "number" })
|
||||
expect(setup { ["test.defined"] = 1 }):eq(true)
|
||||
expect(settings.get("test.defined")):eq(1)
|
||||
end)
|
||||
|
||||
it("skips defined settings with incorrect types", function()
|
||||
settings.define("test.defined", { type = "number" })
|
||||
expect(setup { ["test.defined"] = "abc" }):eq(true)
|
||||
expect(settings.get("test.defined")):eq(nil)
|
||||
end)
|
||||
|
||||
it("skips unserializable values", function()
|
||||
expect(setup_with "{ test = function() end }"):eq(true)
|
||||
expect(settings.get("test")):eq(nil)
|
||||
end)
|
||||
|
||||
it("skips non-table files", function()
|
||||
expect(setup "not a table"):eq(false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("settings.save", function()
|
||||
|
Loading…
Reference in New Issue
Block a user