mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-30 21:23:00 +00:00 
			
		
		
		
	Merge branch 'master' into mc-1.14.x
This commit is contained in:
		| @@ -1,5 +1,5 @@ | ||||
| # Mod properties | ||||
| mod_version=1.88.0 | ||||
| mod_version=1.89.0 | ||||
|  | ||||
| # Minecraft properties (update mods.toml when changing) | ||||
| mc_version=1.14.4 | ||||
|   | ||||
| @@ -91,6 +91,7 @@ public final class ComputerCraft | ||||
|     public static int modem_highAltitudeRangeDuringStorm = 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; | ||||
|   | ||||
| @@ -13,7 +13,6 @@ import dan200.computercraft.ComputerCraft; | ||||
| import dan200.computercraft.client.gui.FixedWidthFontRenderer; | ||||
| import dan200.computercraft.shared.util.Palette; | ||||
| import org.lwjgl.BufferUtils; | ||||
| import org.lwjgl.opengl.GL11; | ||||
| import org.lwjgl.opengl.GL13; | ||||
| import org.lwjgl.opengl.GL20; | ||||
|  | ||||
| @@ -25,12 +24,8 @@ class MonitorTextureBufferShader | ||||
| { | ||||
|     static final int TEXTURE_INDEX = GL13.GL_TEXTURE3; | ||||
|  | ||||
|     private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 ); | ||||
|     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; | ||||
|     private static int uniformHeight; | ||||
| @@ -43,16 +38,6 @@ class MonitorTextureBufferShader | ||||
|  | ||||
|     static void setupUniform( int width, int height, Palette palette, boolean greyscale ) | ||||
|     { | ||||
|         MATRIX_BUFFER.rewind(); | ||||
|         GL11.glGetFloatv( GL11.GL_MODELVIEW_MATRIX, MATRIX_BUFFER ); | ||||
|         MATRIX_BUFFER.rewind(); | ||||
|         GLX.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER ); | ||||
|  | ||||
|         MATRIX_BUFFER.rewind(); | ||||
|         GL11.glGetFloatv( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER ); | ||||
|         MATRIX_BUFFER.rewind(); | ||||
|         GLX.glUniformMatrix4( uniformP, false, MATRIX_BUFFER ); | ||||
|  | ||||
|         GLX.glUniform1i( uniformWidth, width ); | ||||
|         GLX.glUniform1i( uniformHeight, height ); | ||||
|  | ||||
| @@ -121,9 +106,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" ); | ||||
|   | ||||
| @@ -39,6 +39,7 @@ import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_ | ||||
| public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor> | ||||
| { | ||||
|     private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1); | ||||
|     private static ByteBuffer tboContents; | ||||
|  | ||||
|     @Override | ||||
|     public void render( @Nonnull TileMonitor monitor, double posX, double posY, double posZ, float f, int i ) | ||||
| @@ -161,7 +162,14 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor> | ||||
|  | ||||
|                 if( redraw ) | ||||
|                 { | ||||
|                     ByteBuffer monitorBuffer = GLAllocation.createDirectByteBuffer( width * height * 3 ); | ||||
|                     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 ); | ||||
|   | ||||
| @@ -37,12 +37,8 @@ public abstract class HandleGeneric implements ILuaObject | ||||
|     { | ||||
|         m_open = false; | ||||
|  | ||||
|         Closeable closeable = m_closable; | ||||
|         if( closeable != null ) | ||||
|         { | ||||
|             IoUtil.closeQuietly( closeable ); | ||||
|             m_closable = null; | ||||
|         } | ||||
|         IoUtil.closeQuietly( m_closable ); | ||||
|         m_closable = null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -221,7 +221,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 ) ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -67,6 +67,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; | ||||
| @@ -230,6 +231,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(); | ||||
|         } | ||||
|  | ||||
| @@ -317,6 +328,7 @@ public final class Config | ||||
|         ComputerCraft.modem_highAltitudeRange = modemHighAltitudeRange.get(); | ||||
|         ComputerCraft.modem_rangeDuringStorm = modemRangeDuringStorm.get(); | ||||
|         ComputerCraft.modem_highAltitudeRangeDuringStorm = 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,15 +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 ) continue; | ||||
|         PacketDistributor.TargetPoint target = new PacketDistributor.TargetPoint( pos.x, pos.y, pos.z, range, world.getDimension().getType() ); | ||||
|         network.send( PacketDistributor.NEAR.with( () -> target ), packet ); | ||||
|     } | ||||
|  | ||||
|             double x = pos.x - player.posX; | ||||
|             double y = pos.y - player.posY; | ||||
|             double z = pos.z - player.posZ; | ||||
|             if( x * x + y * y + z * z < range * range ) sendToPlayer( player, 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.api.peripheral.IPeripheralTile; | ||||
| import dan200.computercraft.core.terminal.Terminal; | ||||
| import dan200.computercraft.shared.common.ServerTerminal; | ||||
| import dan200.computercraft.shared.common.TileGeneric; | ||||
| 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; | ||||
| @@ -63,6 +64,10 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile | ||||
|     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; | ||||
| @@ -170,7 +175,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if( m_serverMonitor.pollTerminalChanged() ) updateBlock(); | ||||
|         if( m_serverMonitor.pollTerminalChanged() ) MonitorWatcher.enqueue( this ); | ||||
|     } | ||||
|  | ||||
|     // IPeripheralTile implementation | ||||
| @@ -249,16 +254,10 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile | ||||
|     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 | ||||
| @@ -286,9 +285,8 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile | ||||
|  | ||||
|         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 || | ||||
| @@ -299,6 +297,20 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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() | ||||
|   | ||||
| @@ -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,10 @@ | ||||
| #version 140 | ||||
|  | ||||
| uniform mat4 u_mv; | ||||
| uniform mat4 u_p; | ||||
| #version 130 | ||||
|  | ||||
| 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_ModelViewProjectionMatrix * 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,13 @@ | ||||
| # 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. | ||||
|  | ||||
| # New features in CC: Tweaked 1.88.0 | ||||
|  | ||||
| * Computers and turtles now preserve their ID when broken. | ||||
|   | ||||
| @@ -1,18 +1,11 @@ | ||||
| New features in CC: Tweaked 1.88.0 | ||||
| New features in CC: Tweaked 1.89.0 | ||||
|  | ||||
| * Computers and turtles now preserve their ID when broken. | ||||
| * Add `peripheral.getName` - returns the name of a wrapped peripheral. | ||||
| * Reduce network overhead of monitors and terminals. | ||||
| * Add a TBO backend for monitors, with a significant performance boost. | ||||
| * The Lua REPL warns when declaring locals (lupus590, exerro) | ||||
| * Add config to allow using command computers in survival. | ||||
| * Add fs.isDriveRoot - checks if a path is the root of a drive. | ||||
| * `cc.pretty` can now display a function's arguments and where it was defined. The Lua REPL will show arguments by default. | ||||
| * Move the shell's `require`/`package` implementation to a separate `cc.require` module. | ||||
| * 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 io.lines not accepting arguments. | ||||
| * Fix settings.load using an unknown global (MCJack123). | ||||
| * Prevent computers scanning peripherals twice. | ||||
| * Fix settings.load failing on defined settings. | ||||
|  | ||||
| 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() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 SquidDev
					SquidDev