1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-14 20:17:11 +00:00

Merge branch 'mc-1.14.x' into mc-1.15.x

This commit is contained in:
SquidDev
2020-04-22 10:39:00 +01:00
811 changed files with 13275 additions and 6159 deletions

View File

@@ -7,7 +7,6 @@ package dan200.computercraft;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.http.AddressRule;
import dan200.computercraft.core.apis.http.websocket.Websocket;
import dan200.computercraft.shared.Config;
import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
@@ -22,6 +21,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull;
import dan200.computercraft.shared.peripheral.modem.wired.ItemBlockCable;
import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem;
import dan200.computercraft.shared.peripheral.monitor.BlockMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.peripheral.printer.BlockPrinter;
import dan200.computercraft.shared.peripheral.speaker.BlockSpeaker;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
@@ -88,7 +88,7 @@ public final class ComputerCraft
public static long httpMaxDownload = 16 * 1024 * 1024;
public static long httpMaxUpload = 4 * 1024 * 1024;
public static int httpMaxWebsockets = 4;
public static int httpMaxWebsocketMessage = Websocket.MAX_MESSAGE_SIZE;
public static int httpMaxWebsocketMessage = 128 * 1024;
public static boolean enableCommandBlock = false;
public static int modem_range = 64;
@@ -96,6 +96,7 @@ public final class ComputerCraft
public static int modem_rangeDuringStorm = 64;
public static int modem_highAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8;
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000;

View File

@@ -0,0 +1,81 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.filesystem;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
/**
* A simple version of {@link BasicFileAttributes}, which provides what information a {@link IMount} already exposes.
*/
final class FileAttributes implements BasicFileAttributes
{
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
private final boolean isDirectory;
private final long size;
FileAttributes( boolean isDirectory, long size )
{
this.isDirectory = isDirectory;
this.size = size;
}
@Override
public FileTime lastModifiedTime()
{
return EPOCH;
}
@Override
public FileTime lastAccessTime()
{
return EPOCH;
}
@Override
public FileTime creationTime()
{
return EPOCH;
}
@Override
public boolean isRegularFile()
{
return !isDirectory;
}
@Override
public boolean isDirectory()
{
return isDirectory;
}
@Override
public boolean isSymbolicLink()
{
return false;
}
@Override
public boolean isOther()
{
return false;
}
@Override
public long size()
{
return size;
}
@Override
public Object fileKey()
{
return null;
}
}

View File

@@ -27,7 +27,7 @@ public class FileOperationException extends IOException
this.filename = filename;
}
public FileOperationException( String message )
public FileOperationException( @Nonnull String message )
{
super( Objects.requireNonNull( message, "message cannot be null" ) );
this.filename = null;

View File

@@ -12,6 +12,7 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
/**
@@ -76,4 +77,18 @@ public interface IMount
*/
@Nonnull
ReadableByteChannel openForRead( @Nonnull String path ) throws IOException;
/**
* Get attributes about the given file.
*
* @param path The path to query.
* @return File attributes for the given file.
* @throws IOException If the file does not exist, or attributes could not be fetched.
*/
@Nonnull
default BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
if( !exists( path ) ) throw new FileOperationException( path, "No such file" );
return new FileAttributes( isDirectory( path ), getSize( path ) );
}
}

View File

@@ -13,6 +13,7 @@ import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.WritableByteChannel;
import java.util.OptionalLong;
/**
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
@@ -74,4 +75,16 @@ public interface IWritableMount extends IMount
* @throws IOException If the remaining space could not be computed.
*/
long getRemainingSpace() throws IOException;
/**
* Get the capacity of this mount. This should be equal to the size of all files/directories on this mount, minus
* the {@link #getRemainingSpace()}.
*
* @return The capacity of this mount, in bytes.
*/
@Nonnull
default OptionalLong getCapacity()
{
return OptionalLong.empty();
}
}

View File

@@ -46,8 +46,17 @@ public final class FixedWidthFontRenderer
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
}
private static int getColour( char c )
{
int i = "0123456789abcdef".indexOf( c );
return i < 0 ? 0 : 15 - i;
}
private static void drawChar( Matrix4f transform, IVertexBuilder buffer, float x, float y, int index, float r, float g, float b )
{
// Short circuit to avoid the common case - the texture should be blank here after all.
if( index == '\0' || index == ' ' ) return;
int column = index % 16;
int row = index / 16;
@@ -72,6 +81,24 @@ public final class FixedWidthFontRenderer
buffer.pos( transform, x + width, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_END ).endVertex();
}
private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
{
double[] colour = palette.getColour( getColour( colourIndex ) );
float r, g, b;
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
drawQuad( transform, buffer, x, y, width, height, r, g, b );
}
private static void drawBackground(
@Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y,
@Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
@@ -80,56 +107,34 @@ public final class FixedWidthFontRenderer
{
if( leftMarginSize > 0 )
{
double[] colour = palette.getColour( 15 - "0123456789abcdef".indexOf( backgroundColour.charAt( 0 ) ) );
float r, g, b;
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
drawQuad( transform, renderer, x - leftMarginSize, y, leftMarginSize, height, r, g, b );
drawQuad( transform, renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
}
if( rightMarginSize > 0 )
{
double[] colour = palette.getColour( 15 - "0123456789abcdef".indexOf( backgroundColour.charAt( backgroundColour.length() - 1 ) ) );
float r, g, b;
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
drawQuad( transform, renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, r, g, b );
drawQuad( transform, renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) );
}
// Batch together runs of identical background cells.
int blockStart = 0;
char blockColour = '\0';
for( int i = 0; i < backgroundColour.length(); i++ )
{
double[] colour = palette.getColour( 15 - "0123456789abcdef".indexOf( backgroundColour.charAt( i ) ) );
float r, g, b;
if( greyscale )
char colourIndex = backgroundColour.charAt( i );
if( colourIndex == blockColour ) continue;
if( blockColour != '\0' )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
}
drawQuad( transform, renderer, x + i * FONT_WIDTH, y, FONT_WIDTH, height, r, g, b );
blockColour = colourIndex;
blockStart = i;
}
if( blockColour != '\0' )
{
drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
}
}
@@ -172,8 +177,7 @@ public final class FixedWidthFontRenderer
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize
)
{
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
// TODO: RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
bindFont();
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
drawString( IDENTITY, ((IRenderTypeBuffer) renderer).getBuffer( TYPE ), x, y, text, textColour, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize );
@@ -258,9 +262,7 @@ public final class FixedWidthFontRenderer
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
// TODO: Is this the most sane thing?
RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
bindFont();
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
IVertexBuilder buffer = renderer.getBuffer( TYPE );
@@ -284,8 +286,7 @@ public final class FixedWidthFontRenderer
public static void drawEmptyTerminal( @Nonnull Matrix4f transform, float x, float y, float width, float height )
{
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
bindFont();
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
drawEmptyTerminal( transform, renderer, x, y, width, height );
@@ -303,6 +304,12 @@ public final class FixedWidthFontRenderer
drawQuad( transform, renderer.getBuffer( Type.BLOCKER ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
}
private static void bindFont()
{
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
}
private static final class Type extends RenderState
{
private static final int GL_MODE = GL11.GL_TRIANGLES;

View File

@@ -9,6 +9,7 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
@@ -18,6 +19,7 @@ import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.renderer.RenderType;
@@ -26,6 +28,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@@ -51,6 +54,8 @@ public final class ComputerCraftProxyClient
ClientRegistry.bindTileEntityRenderer( TileTurtle.FACTORY_NORMAL, TileEntityTurtleRenderer::new );
ClientRegistry.bindTileEntityRenderer( TileTurtle.FACTORY_ADVANCED, TileEntityTurtleRenderer::new );
// TODO: ClientRegistry.bindTileEntityRenderer( TileCable.FACTORY, x -> new TileEntityCableRenderer() );
RenderingRegistry.registerEntityRenderingHandler( TurtlePlayer.TYPE, TurtlePlayerRenderer::new );
}
private static void registerContainers()

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.client.renderer.*;
@@ -92,7 +93,7 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
boolean redraw = originTerminal.pollTerminalChanged();
if( originTerminal.buffer == null )
{
originTerminal.createBuffer();
originTerminal.createBuffer( MonitorRenderer.VBO );
redraw = true;
}
VertexBuffer vbo = originTerminal.buffer;

View File

@@ -0,0 +1,37 @@
/*
* 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.client.render;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.util.ResourceLocation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class TurtlePlayerRenderer extends EntityRenderer<TurtlePlayer>
{
public TurtlePlayerRenderer( EntityRendererManager renderManager )
{
super( renderManager );
}
@Override
public void doRender( @Nonnull TurtlePlayer entity, double x, double y, double z, float entityYaw, float partialTicks )
{
ComputerCraft.log.error( "Rendering TurtlePlayer on the client side, at {}", entity.getPosition() );
}
@Nullable
@Override
protected ResourceLocation getEntityTexture( @Nonnull TurtlePlayer entity )
{
return null;
}
}

View File

@@ -22,6 +22,11 @@ import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.HashMap;
import java.util.Map;
import java.util.OptionalLong;
import java.util.function.Function;
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
@@ -76,6 +81,8 @@ public class FSAPI implements ILuaAPI
"getFreeSpace",
"find",
"getDir",
"getCapacity",
"attributes",
};
}
@@ -315,9 +322,8 @@ public class FSAPI implements ILuaAPI
throw new LuaException( e.getMessage() );
}
}
case 14:
case 14: // find
{
// find
String path = getString( args, 0 );
try
{
@@ -329,15 +335,50 @@ public class FSAPI implements ILuaAPI
throw new LuaException( e.getMessage() );
}
}
case 15:
case 15: // getDir
{
// getDir
String path = getString( args, 0 );
return new Object[] { FileSystem.getDirectory( path ) };
}
case 16: // getCapacity
{
String path = getString( args, 0 );
try
{
OptionalLong capacity = m_fileSystem.getCapacity( path );
return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 17: // attributes
{
String path = getString( args, 0 );
try
{
BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
Map<String, Object> result = new HashMap<>();
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
result.put( "created", getFileTime( attributes.creationTime() ) );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() );
return new Object[] { result };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
default:
assert false;
return null;
}
}
private static long getFileTime( FileTime time )
{
return time == null ? 0 : time.toMillis();
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nullable;
/**
* A Lua exception which does not contain its stack trace.
*/
public class FastLuaException extends LuaException
{
private static final long serialVersionUID = 5957864899303561143L;
public FastLuaException( @Nullable String message )
{
super( message );
}
public FastLuaException( @Nullable String message, int level )
{
super( message, level );
}
@Override
public synchronized Throwable fillInStackTrace()
{
return this;
}
}

View File

@@ -18,6 +18,8 @@ import javax.annotation.Nullable;
public interface IAPIEnvironment
{
String TIMER_EVENT = "timer";
@FunctionalInterface
interface IPeripheralChangeListener
{
@@ -64,6 +66,10 @@ public interface IAPIEnvironment
void setLabel( @Nullable String label );
int startTimer( long ticks );
void cancelTimer( int id );
void addTrackingChange( @Nonnull TrackingField field, long change );
default void addTrackingChange( @Nonnull TrackingField field )

View File

@@ -9,6 +9,8 @@ import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull;
import java.time.Instant;
@@ -24,24 +26,12 @@ public class OSAPI implements ILuaAPI
{
private IAPIEnvironment m_apiEnvironment;
private final Map<Integer, Timer> m_timers;
private final Map<Integer, Alarm> m_alarms;
private final Int2ObjectMap<Alarm> m_alarms = new Int2ObjectOpenHashMap<>();
private int m_clock;
private double m_time;
private int m_day;
private int m_nextTimerToken;
private int m_nextAlarmToken;
private static class Timer
{
int m_ticksLeft;
Timer( int ticksLeft )
{
m_ticksLeft = ticksLeft;
}
}
private int m_nextAlarmToken = 0;
private static class Alarm implements Comparable<Alarm>
{
@@ -66,10 +56,6 @@ public class OSAPI implements ILuaAPI
public OSAPI( IAPIEnvironment environment )
{
m_apiEnvironment = environment;
m_nextTimerToken = 0;
m_nextAlarmToken = 0;
m_timers = new HashMap<>();
m_alarms = new HashMap<>();
}
// ILuaAPI implementation
@@ -87,11 +73,6 @@ public class OSAPI implements ILuaAPI
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
m_clock = 0;
synchronized( m_timers )
{
m_timers.clear();
}
synchronized( m_alarms )
{
m_alarms.clear();
@@ -101,26 +82,7 @@ public class OSAPI implements ILuaAPI
@Override
public void update()
{
synchronized( m_timers )
{
// Update the clock
m_clock++;
// Countdown all of our active timers
Iterator<Map.Entry<Integer, Timer>> it = m_timers.entrySet().iterator();
while( it.hasNext() )
{
Map.Entry<Integer, Timer> entry = it.next();
Timer timer = entry.getValue();
timer.m_ticksLeft--;
if( timer.m_ticksLeft <= 0 )
{
// Queue the "timer" event
queueLuaEvent( "timer", new Object[] { entry.getKey() } );
it.remove();
}
}
}
m_clock++;
// Wait for all of our alarms
synchronized( m_alarms )
@@ -133,10 +95,10 @@ public class OSAPI implements ILuaAPI
if( time > previousTime || day > previousDay )
{
double now = m_day * 24.0 + m_time;
Iterator<Map.Entry<Integer, Alarm>> it = m_alarms.entrySet().iterator();
Iterator<Int2ObjectMap.Entry<Alarm>> it = m_alarms.int2ObjectEntrySet().iterator();
while( it.hasNext() )
{
Map.Entry<Integer, Alarm> entry = it.next();
Int2ObjectMap.Entry<Alarm> entry = it.next();
Alarm alarm = entry.getValue();
double t = alarm.m_day * 24.0 + alarm.m_time;
if( now >= t )
@@ -155,11 +117,6 @@ public class OSAPI implements ILuaAPI
@Override
public void shutdown()
{
synchronized( m_timers )
{
m_timers.clear();
}
synchronized( m_alarms )
{
m_alarms.clear();
@@ -229,11 +186,8 @@ public class OSAPI implements ILuaAPI
{
// startTimer
double timer = getFiniteDouble( args, 0 );
synchronized( m_timers )
{
m_timers.put( m_nextTimerToken, new Timer( (int) Math.round( timer / 0.05 ) ) );
return new Object[] { m_nextTimerToken++ };
}
int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) );
return new Object[] { id };
}
case 2:
{
@@ -278,10 +232,7 @@ public class OSAPI implements ILuaAPI
return null;
}
case 10: // clock
synchronized( m_timers )
{
return new Object[] { m_clock * 0.05 };
}
return new Object[] { m_clock * 0.05 };
case 11:
{
// time
@@ -345,10 +296,7 @@ public class OSAPI implements ILuaAPI
{
// cancelTimer
int token = getInt( args, 0 );
synchronized( m_timers )
{
m_timers.remove( token );
}
m_apiEnvironment.cancelTimer( token );
return null;
}
case 14:

View File

@@ -366,22 +366,30 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
String methodName = getString( args, 1 );
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
if( side != null )
if( side == null ) throw new LuaException( "No peripheral attached" );
PeripheralWrapper p;
synchronized( m_peripherals )
{
PeripheralWrapper p;
synchronized( m_peripherals )
{
p = m_peripherals[side.ordinal()];
}
if( p != null )
{
return p.call( context, methodName, methodArgs );
}
p = m_peripherals[side.ordinal()];
}
if( p == null ) throw new LuaException( "No peripheral attached" );
try
{
return p.call( context, methodName, methodArgs );
}
catch( LuaException e )
{
// We increase the error level by one in order to shift the error level to where peripheral.call was
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
throw e;
}
throw new LuaException( "No peripheral attached" );
}
default:
return null;
}
}
}

View File

@@ -40,7 +40,11 @@ import java.util.concurrent.Future;
*/
public class Websocket extends Resource<Websocket>
{
public static final int MAX_MESSAGE_SIZE = 64 * 1024;
/**
* We declare the maximum size to be 2^30 bytes. While messages can be much longer, we set an arbitrary limit as
* working with larger messages (especially within a Lua VM) is absurd.
*/
public static final int MAX_MESSAGE_SIZE = 1 << 30;
static final String SUCCESS_EVENT = "websocket_success";
static final String FAILURE_EVENT = "websocket_failure";

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http.websocket;
import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ArgumentHelper;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
@@ -23,6 +24,7 @@ import java.io.Closeable;
import java.util.Arrays;
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
import static dan200.computercraft.core.apis.IAPIEnvironment.TIMER_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT;
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
@@ -53,7 +55,21 @@ public class WebsocketHandle implements ILuaObject, Closeable
switch( method )
{
case 0: // receive
{
checkOpen();
int timeoutId;
if( arguments.length <= 0 || arguments[0] == null )
{
// We do this rather odd argument validation to ensure we can tell the difference between a
// negative timeout and an absent one.
timeoutId = -1;
}
else
{
double timeout = ArgumentHelper.getFiniteDouble( arguments, 0 );
timeoutId = websocket.environment().startTimer( Math.round( timeout / 0.05 ) );
}
while( true )
{
Object[] event = context.pullEvent( null );
@@ -63,9 +79,17 @@ public class WebsocketHandle implements ILuaObject, Closeable
}
else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed )
{
// If the socket is closed abort.
return null;
}
else if( event.length >= 2 && timeoutId != -1 && Objects.equal( event[0], TIMER_EVENT )
&& event[1] instanceof Number && ((Number) event[1]).intValue() == timeoutId )
{
// If we received a matching timer event then abort.
return null;
}
}
}
case 1: // send
{

View File

@@ -181,7 +181,7 @@ public class Computer
executor.tick();
// Update the environment's internal state.
internalEnvironment.update();
internalEnvironment.tick();
// Propagate the environment's output to the world.
if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true );

View File

@@ -426,6 +426,7 @@ final class ComputerExecutor
}
// Init APIs
computer.getEnvironment().reset();
for( ILuaAPI api : apis ) api.startup();
// Init lua
@@ -469,6 +470,7 @@ final class ComputerExecutor
// Shutdown our APIs
for( ILuaAPI api : apis ) api.shutdown();
computer.getEnvironment().reset();
// Unload filesystem
if( fileSystem != null )

View File

@@ -5,6 +5,7 @@
*/
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.IAPIEnvironment;
@@ -12,9 +13,12 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Iterator;
/**
* Represents the "environment" that a {@link Computer} exists in.
@@ -53,6 +57,9 @@ public final class Environment implements IAPIEnvironment
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
private IPeripheralChangeListener peripheralListener = null;
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
private int nextTimerToken = 0;
Environment( Computer computer )
{
this.computer = computer;
@@ -198,17 +205,47 @@ public final class Environment implements IAPIEnvironment
}
/**
* Called on the main thread to update the internal state of the computer.
* Called when the computer starts up or shuts down, to reset any internal state.
*
* This just queues a {@code redstone} event if the input has changed.
* @see ILuaAPI#startup()
* @see ILuaAPI#shutdown()
*/
void update()
void reset()
{
synchronized( timers )
{
timers.clear();
}
}
/**
* Called on the main thread to update the internal state of the computer.
*/
void tick()
{
if( inputChanged )
{
inputChanged = false;
queueEvent( "redstone", null );
}
synchronized( timers )
{
// Countdown all of our active timers
Iterator<Int2ObjectMap.Entry<Timer>> it = timers.int2ObjectEntrySet().iterator();
while( it.hasNext() )
{
Int2ObjectMap.Entry<Timer> entry = it.next();
Timer timer = entry.getValue();
timer.ticksLeft--;
if( timer.ticksLeft <= 0 )
{
// Queue the "timer" event
queueEvent( TIMER_EVENT, new Object[] { entry.getIntKey() } );
it.remove();
}
}
}
}
/**
@@ -303,9 +340,38 @@ public final class Environment implements IAPIEnvironment
computer.setLabel( label );
}
@Override
public int startTimer( long ticks )
{
synchronized( timers )
{
timers.put( nextTimerToken, new Timer( ticks ) );
return nextTimerToken++;
}
}
@Override
public void cancelTimer( int id )
{
synchronized( timers )
{
timers.remove( id );
}
}
@Override
public void addTrackingChange( @Nonnull TrackingField field, long change )
{
Tracking.addValue( computer, field, change );
}
private static class Timer
{
long ticksLeft;
Timer( long ticksLeft )
{
this.ticksLeft = ticksLeft;
}
}
}

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -126,4 +127,19 @@ public class ComboMount implements IMount
}
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
for( int i = m_parts.length - 1; i >= 0; --i )
{
IMount part = m_parts[i];
if( part.exists( path ) && !part.isDirectory( path ) )
{
return part.getAttributes( path );
}
}
throw new FileOperationException( path, "No such file" );
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.filesystem;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IWritableMount;
@@ -14,11 +15,11 @@ import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.List;
import java.util.OptionalLong;
import java.util.Set;
public class FileMount implements IWritableMount
@@ -211,6 +212,19 @@ public class FileMount implements IWritableMount
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
if( created() )
{
File file = getRealPath( path );
if( file.exists() ) return Files.readAttributes( file.toPath(), BasicFileAttributes.class );
}
throw new FileOperationException( path, "No such file" );
}
// IWritableMount implementation
@Override
@@ -331,6 +345,13 @@ public class FileMount implements IWritableMount
return Math.max( m_capacity - m_usedSpace, 0 );
}
@Nonnull
@Override
public OptionalLong getCapacity()
{
return OptionalLong.of( m_capacity - MINIMUM_FILE_SIZE );
}
private File getRealPath( String path )
{
return new File( m_rootPath, path );
@@ -353,23 +374,46 @@ public class FileMount implements IWritableMount
}
}
private static class Visitor extends SimpleFileVisitor<Path>
{
long size;
@Override
public FileVisitResult preVisitDirectory( Path dir, BasicFileAttributes attrs )
{
size += MINIMUM_FILE_SIZE;
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
{
size += Math.max( attrs.size(), MINIMUM_FILE_SIZE );
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed( Path file, IOException exc )
{
ComputerCraft.log.error( "Error computing file size for {}", file, exc );
return FileVisitResult.CONTINUE;
}
}
private static long measureUsedSpace( File file )
{
if( !file.exists() ) return 0;
if( file.isDirectory() )
try
{
long size = MINIMUM_FILE_SIZE;
String[] contents = file.list();
for( String content : contents )
{
size += measureUsedSpace( new File( file, content ) );
}
return size;
Visitor visitor = new Visitor();
Files.walkFileTree( file.toPath(), visitor );
return visitor.size;
}
else
catch( IOException e )
{
return Math.max( file.length(), MINIMUM_FILE_SIZE );
ComputerCraft.log.error( "Error computing file size for {}", file, e );
return 0;
}
}
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
@@ -23,309 +22,23 @@ import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
public class FileSystem
{
private static class MountWrapper
{
private String m_label;
private String m_location;
private IMount m_mount;
private IWritableMount m_writableMount;
MountWrapper( String label, String location, IMount mount )
{
m_label = label;
m_location = location;
m_mount = mount;
m_writableMount = null;
}
MountWrapper( String label, String location, IWritableMount mount )
{
this( label, location, (IMount) mount );
m_writableMount = mount;
}
public String getLabel()
{
return m_label;
}
public String getLocation()
{
return m_location;
}
public long getFreeSpace()
{
if( m_writableMount == null )
{
return 0;
}
try
{
return m_writableMount.getRemainingSpace();
}
catch( IOException e )
{
return 0;
}
}
public boolean isReadOnly( String path )
{
return m_writableMount == null;
}
// IMount forwarders:
public boolean exists( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return m_mount.exists( path );
}
catch( IOException e )
{
throw new FileSystemException( e.getMessage() );
}
}
public boolean isDirectory( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return m_mount.exists( path ) && m_mount.isDirectory( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void list( String path, List<String> contents ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
{
m_mount.list( path, contents );
}
else
{
throw localExceptionOf( path, "Not a directory" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public long getSize( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) )
{
if( m_mount.isDirectory( path ) )
{
return 0;
}
else
{
return m_mount.getSize( path );
}
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public ReadableByteChannel openForRead( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
{
return m_mount.openForRead( path );
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
// IWritableMount forwarders:
public void makeDirectory( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( m_mount.exists( path ) )
{
if( !m_mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
}
else
{
m_writableMount.makeDirectory( path );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void delete( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
try
{
path = toLocal( path );
if( m_mount.exists( path ) )
{
m_writableMount.delete( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForWrite( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( m_mount.exists( path ) && m_mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
if( !path.isEmpty() )
{
String dir = getDirectory( path );
if( !dir.isEmpty() && !m_mount.exists( path ) )
{
m_writableMount.makeDirectory( dir );
}
}
return m_writableMount.openForWrite( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForAppend( String path ) throws FileSystemException
{
if( m_writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( !m_mount.exists( path ) )
{
if( !path.isEmpty() )
{
String dir = getDirectory( path );
if( !dir.isEmpty() && !m_mount.exists( path ) )
{
m_writableMount.makeDirectory( dir );
}
}
return m_writableMount.openForWrite( path );
}
else if( m_mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
return m_writableMount.openForAppend( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
private String toLocal( String path )
{
return FileSystem.toLocal( path, m_location );
}
private FileSystemException localExceptionOf( IOException e )
{
if( !m_location.isEmpty() && e instanceof FileOperationException )
{
FileOperationException ex = (FileOperationException) e;
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
}
return new FileSystemException( e.getMessage() );
}
private FileSystemException localExceptionOf( String path, String message )
{
if( !m_location.isEmpty() ) path = path.isEmpty() ? m_location : m_location + "/" + path;
return exceptionOf( path, message );
}
private static FileSystemException exceptionOf( String path, String message )
{
return new FileSystemException( "/" + path + ": " + message );
}
}
/**
* Maximum depth that {@link #copyRecursive(String, MountWrapper, String, MountWrapper, int)} will descend into.
*
* This is a pretty arbitrary value, though hopefully it is large enough that it'll never be normally hit. This
* exists to prevent it overflowing if it ever gets into an infinite loop.
*/
private static final int MAX_COPY_DEPTH = 128;
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
private final Map<String, MountWrapper> mounts = new HashMap<>();
private final HashMap<WeakReference<FileSystemWrapper<?>>, ChannelWrapper<?>> m_openFiles = new HashMap<>();
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
@@ -355,10 +68,7 @@ public class FileSystem
{
if( mount == null ) throw new NullPointerException();
location = sanitizePath( location );
if( location.contains( ".." ) )
{
throw new FileSystemException( "Cannot mount below the root" );
}
if( location.contains( ".." ) ) throw new FileSystemException( "Cannot mount below the root" );
mount( new MountWrapper( label, location, mount ) );
}
@@ -379,14 +89,13 @@ public class FileSystem
private synchronized void mount( MountWrapper wrapper )
{
String location = wrapper.getLocation();
m_mounts.remove( location );
m_mounts.put( location, wrapper );
mounts.remove( location );
mounts.put( location, wrapper );
}
public synchronized void unmount( String path )
{
path = sanitizePath( path );
m_mounts.remove( path );
mounts.remove( sanitizePath( path ) );
}
public synchronized String combine( String path, String childPath )
@@ -430,27 +139,20 @@ public class FileSystem
public static String getName( String path )
{
path = sanitizePath( path, true );
if( path.isEmpty() )
{
return "root";
}
if( path.isEmpty() ) return "root";
int lastSlash = path.lastIndexOf( '/' );
if( lastSlash >= 0 )
{
return path.substring( lastSlash + 1 );
}
else
{
return path;
}
return lastSlash >= 0 ? path.substring( lastSlash + 1 ) : path;
}
public synchronized long getSize( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getSize( path );
return getMount( sanitizePath( path ) ).getSize( sanitizePath( path ) );
}
public synchronized BasicFileAttributes getAttributes( String path ) throws FileSystemException
{
return getMount( sanitizePath( path ) ).getAttributes( sanitizePath( path ) );
}
public synchronized String[] list( String path ) throws FileSystemException
@@ -463,7 +165,7 @@ public class FileSystem
mount.list( path, list );
// Add any mounts that are mounted at this location
for( MountWrapper otherMount : m_mounts.values() )
for( MountWrapper otherMount : mounts.values() )
{
if( getDirectory( otherMount.getLocation() ).equals( path ) )
{
@@ -611,15 +313,13 @@ public class FileSystem
{
throw new FileSystemException( "/" + sourcePath + ": Can't copy a directory inside itself" );
}
copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ) );
copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ), 0 );
}
private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount ) throws FileSystemException
private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount, int depth ) throws FileSystemException
{
if( !sourceMount.exists( sourcePath ) )
{
return;
}
if( !sourceMount.exists( sourcePath ) ) return;
if( depth >= MAX_COPY_DEPTH ) throw new FileSystemException( "Too many directories to copy" );
if( sourceMount.isDirectory( sourcePath ) )
{
@@ -634,7 +334,8 @@ public class FileSystem
{
copyRecursive(
combine( sourcePath, child ), sourceMount,
combine( destinationPath, child ), destinationMount
combine( destinationPath, child ), destinationMount,
depth + 1
);
}
}
@@ -726,17 +427,25 @@ public class FileSystem
return null;
}
public long getFreeSpace( String path ) throws FileSystemException
public synchronized long getFreeSpace( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getFreeSpace();
}
private MountWrapper getMount( String path ) throws FileSystemException
@Nonnull
public synchronized OptionalLong getCapacity( String path ) throws FileSystemException
{
path = sanitizePath( path );
MountWrapper mount = getMount( path );
return mount.getCapacity();
}
private synchronized MountWrapper getMount( String path ) throws FileSystemException
{
// Return the deepest mount that contains a given path
Iterator<MountWrapper> it = m_mounts.values().iterator();
Iterator<MountWrapper> it = mounts.values().iterator();
MountWrapper match = null;
int matchLength = 999;
while( it.hasNext() )
@@ -854,8 +563,8 @@ public class FileSystem
public static boolean contains( String pathA, String pathB )
{
pathA = sanitizePath( pathA );
pathB = sanitizePath( pathB );
pathA = sanitizePath( pathA ).toLowerCase( Locale.ROOT );
pathB = sanitizePath( pathB ).toLowerCase( Locale.ROOT );
if( pathB.equals( ".." ) )
{

View File

@@ -23,6 +23,9 @@ import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -213,6 +216,20 @@ public class JarMount implements IMount
throw new FileOperationException( path, "No such file" );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
FileEntry file = get( path );
if( file != null )
{
ZipEntry entry = zip.getEntry( file.path );
if( entry != null ) return new ZipEntryAttributes( entry );
}
throw new FileOperationException( path, "No such file" );
}
private static class FileEntry
{
String path;
@@ -253,4 +270,76 @@ public class JarMount implements IMount
Reference<? extends JarMount> next;
while( (next = MOUNT_QUEUE.poll()) != null ) IoUtil.closeQuietly( ((MountReference) next).file );
}
private static class ZipEntryAttributes implements BasicFileAttributes
{
private final ZipEntry entry;
ZipEntryAttributes( ZipEntry entry )
{
this.entry = entry;
}
@Override
public FileTime lastModifiedTime()
{
return orEpoch( entry.getLastModifiedTime() );
}
@Override
public FileTime lastAccessTime()
{
return orEpoch( entry.getLastAccessTime() );
}
@Override
public FileTime creationTime()
{
FileTime time = entry.getCreationTime();
return time == null ? lastModifiedTime() : time;
}
@Override
public boolean isRegularFile()
{
return !entry.isDirectory();
}
@Override
public boolean isDirectory()
{
return entry.isDirectory();
}
@Override
public boolean isSymbolicLink()
{
return false;
}
@Override
public boolean isOther()
{
return false;
}
@Override
public long size()
{
return entry.getSize();
}
@Override
public Object fileKey()
{
return null;
}
private static final FileTime EPOCH = FileTime.from( Instant.EPOCH );
private static FileTime orEpoch( FileTime time )
{
return time == null ? EPOCH : time;
}
}
}

View File

@@ -0,0 +1,312 @@
/*
* 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.core.filesystem;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.OptionalLong;
class MountWrapper
{
private String label;
private String location;
private IMount mount;
private IWritableMount writableMount;
MountWrapper( String label, String location, IMount mount )
{
this.label = label;
this.location = location;
this.mount = mount;
writableMount = null;
}
MountWrapper( String label, String location, IWritableMount mount )
{
this( label, location, (IMount) mount );
writableMount = mount;
}
public String getLabel()
{
return label;
}
public String getLocation()
{
return location;
}
public long getFreeSpace()
{
if( writableMount == null ) return 0;
try
{
return writableMount.getRemainingSpace();
}
catch( IOException e )
{
return 0;
}
}
public OptionalLong getCapacity()
{
return writableMount == null ? OptionalLong.empty() : writableMount.getCapacity();
}
public boolean isReadOnly( String path )
{
return writableMount == null;
}
public boolean exists( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return mount.exists( path );
}
catch( IOException e )
{
throw new FileSystemException( e.getMessage() );
}
}
public boolean isDirectory( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
return mount.exists( path ) && mount.isDirectory( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void list( String path, List<String> contents ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) || !mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Not a directory" );
}
mount.list( path, contents );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public long getSize( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
return mount.isDirectory( path ) ? 0 : mount.getSize( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
@Nonnull
public BasicFileAttributes getAttributes( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( !mount.exists( path ) ) throw localExceptionOf( path, "No such file" );
return mount.getAttributes( path );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public ReadableByteChannel openForRead( String path ) throws FileSystemException
{
path = toLocal( path );
try
{
if( mount.exists( path ) && !mount.isDirectory( path ) )
{
return mount.openChannelForRead( path );
}
else
{
throw localExceptionOf( path, "No such file" );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void makeDirectory( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( mount.exists( path ) )
{
if( !mount.isDirectory( path ) ) throw localExceptionOf( path, "File exists" );
}
else
{
writableMount.makeDirectory( path );
}
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public void delete( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
try
{
path = toLocal( path );
if( mount.exists( path ) )
{
writableMount.delete( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForWrite( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( mount.exists( path ) && mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
if( !path.isEmpty() )
{
String dir = FileSystem.getDirectory( path );
if( !dir.isEmpty() && !mount.exists( path ) )
{
writableMount.makeDirectory( dir );
}
}
return writableMount.openChannelForWrite( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
public WritableByteChannel openForAppend( String path ) throws FileSystemException
{
if( writableMount == null ) throw exceptionOf( path, "Access denied" );
path = toLocal( path );
try
{
if( !mount.exists( path ) )
{
if( !path.isEmpty() )
{
String dir = FileSystem.getDirectory( path );
if( !dir.isEmpty() && !mount.exists( path ) )
{
writableMount.makeDirectory( dir );
}
}
return writableMount.openChannelForWrite( path );
}
else if( mount.isDirectory( path ) )
{
throw localExceptionOf( path, "Cannot write to directory" );
}
else
{
return writableMount.openChannelForAppend( path );
}
}
catch( AccessDeniedException e )
{
throw new FileSystemException( "Access denied" );
}
catch( IOException e )
{
throw localExceptionOf( e );
}
}
private String toLocal( String path )
{
return FileSystem.toLocal( path, location );
}
private FileSystemException localExceptionOf( IOException e )
{
if( !location.isEmpty() && e instanceof FileOperationException )
{
FileOperationException ex = (FileOperationException) e;
if( ex.getFilename() != null ) return localExceptionOf( ex.getFilename(), ex.getMessage() );
}
return new FileSystemException( e.getMessage() );
}
private FileSystemException localExceptionOf( String path, String message )
{
if( !location.isEmpty() ) path = path.isEmpty() ? location : location + "/" + path;
return exceptionOf( path, message );
}
private static FileSystemException exceptionOf( String path, String message )
{
return new FileSystemException( "/" + path + ": " + message );
}
}

View File

@@ -10,61 +10,60 @@ import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
public class SubMount implements IMount
{
private IMount m_parent;
private String m_subPath;
private IMount parent;
private String subPath;
public SubMount( IMount parent, String subPath )
{
m_parent = parent;
m_subPath = subPath;
this.parent = parent;
this.subPath = subPath;
}
// IMount implementation
@Override
public boolean exists( @Nonnull String path ) throws IOException
{
return m_parent.exists( getFullPath( path ) );
return parent.exists( getFullPath( path ) );
}
@Override
public boolean isDirectory( @Nonnull String path ) throws IOException
{
return m_parent.isDirectory( getFullPath( path ) );
return parent.isDirectory( getFullPath( path ) );
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{
m_parent.list( getFullPath( path ), contents );
parent.list( getFullPath( path ), contents );
}
@Override
public long getSize( @Nonnull String path ) throws IOException
{
return m_parent.getSize( getFullPath( path ) );
return parent.getSize( getFullPath( path ) );
}
@Nonnull
@Override
public ReadableByteChannel openForRead( @Nonnull String path ) throws IOException
{
return m_parent.openForRead( getFullPath( path ) );
return parent.openForRead( getFullPath( path ) );
}
@Nonnull
@Override
public BasicFileAttributes getAttributes( @Nonnull String path ) throws IOException
{
return parent.getAttributes( getFullPath( path ) );
}
private String getFullPath( String path )
{
if( path.isEmpty() )
{
return m_subPath;
}
else
{
return m_subPath + "/" + path;
}
return path.isEmpty() ? subPath : subPath + "/" + path;
}
}

View File

@@ -92,6 +92,7 @@ public class CobaltLuaMachine implements ILuaMachine
m_globals.load( state, new MathLib() );
m_globals.load( state, new CoroutineLib() );
m_globals.load( state, new Bit32Lib() );
m_globals.load( state, new Utf8Lib() );
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
// Remove globals we don't want to expose

View File

@@ -0,0 +1,28 @@
/*
* 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.data;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import net.minecraft.data.DataGenerator;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.GatherDataEvent;
@Mod.EventBusSubscriber( bus = Mod.EventBusSubscriber.Bus.MOD )
public class Generators
{
@SubscribeEvent
public static void gather( GatherDataEvent event )
{
ComputerCraftProxyCommon.registerLoot();
DataGenerator generator = event.getGenerator();
generator.addProvider( new Recipes( generator ) );
generator.addProvider( new LootTables( generator ) );
generator.addProvider( new Tags( generator ) );
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.data;
import com.google.common.collect.Multimap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import dan200.computercraft.ComputerCraft;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.DirectoryCache;
import net.minecraft.data.IDataProvider;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.LootTable;
import net.minecraft.world.storage.loot.LootTableManager;
import net.minecraft.world.storage.loot.ValidationResults;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* An alternative to {@link net.minecraft.data.LootTableProvider}, with a more flexible interface.
*/
public abstract class LootTableProvider implements IDataProvider
{
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
private final DataGenerator generator;
public LootTableProvider( DataGenerator generator )
{
this.generator = generator;
}
@Override
public void act( @Nonnull DirectoryCache cache )
{
ValidationResults validation = new ValidationResults();
Map<ResourceLocation, LootTable> tables = new HashMap<>();
registerLoot( ( id, table ) -> {
if( tables.containsKey( id ) ) validation.addProblem( "Duplicate loot tables for " + id );
tables.put( id, table );
} );
tables.forEach( ( key, value ) -> LootTableManager.func_215302_a( validation, key, value, tables::get ) );
Multimap<String, String> problems = validation.getProblems();
if( !problems.isEmpty() )
{
problems.forEach( ( child, problem ) ->
ComputerCraft.log.warn( "Found validation problem in " + child + ": " + problem ) );
throw new IllegalStateException( "Failed to validate loot tables, see logs" );
}
tables.forEach( ( key, value ) -> {
Path path = getPath( key );
try
{
IDataProvider.save( GSON, cache, LootTableManager.toJson( value ), path );
}
catch( IOException e )
{
ComputerCraft.log.error( "Couldn't save loot table {}", path, e );
}
} );
}
protected abstract void registerLoot( BiConsumer<ResourceLocation, LootTable> add );
@Nonnull
@Override
public String getName()
{
return "LootTables";
}
private Path getPath( ResourceLocation id )
{
return generator.getOutputFolder()
.resolve( "data" ).resolve( id.getNamespace() ).resolve( "loot_tables" )
.resolve( id.getPath() + ".json" );
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.data;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import net.minecraft.block.Block;
import net.minecraft.data.DataGenerator;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.*;
import net.minecraft.world.storage.loot.conditions.Alternative;
import net.minecraft.world.storage.loot.conditions.SurvivesExplosion;
import java.util.function.BiConsumer;
public class LootTables extends LootTableProvider
{
public LootTables( DataGenerator generator )
{
super( generator );
}
@Override
protected void registerLoot( BiConsumer<ResourceLocation, LootTable> add )
{
basicDrop( add, ComputerCraft.Blocks.diskDrive );
basicDrop( add, ComputerCraft.Blocks.monitorNormal );
basicDrop( add, ComputerCraft.Blocks.monitorAdvanced );
basicDrop( add, ComputerCraft.Blocks.printer );
basicDrop( add, ComputerCraft.Blocks.speaker );
basicDrop( add, ComputerCraft.Blocks.wiredModemFull );
basicDrop( add, ComputerCraft.Blocks.wirelessModemNormal );
basicDrop( add, ComputerCraft.Blocks.wirelessModemAdvanced );
computerDrop( add, ComputerCraft.Blocks.computerNormal );
computerDrop( add, ComputerCraft.Blocks.computerAdvanced );
computerDrop( add, ComputerCraft.Blocks.turtleNormal );
computerDrop( add, ComputerCraft.Blocks.turtleAdvanced );
}
private static void basicDrop( BiConsumer<ResourceLocation, LootTable> add, Block block )
{
add.accept( block.getRegistryName(), LootTable
.builder()
.addLootPool( LootPool.builder()
.name( "main" )
.rolls( ConstantRange.of( 1 ) )
.addEntry( ItemLootEntry.builder( block ) )
.acceptCondition( SurvivesExplosion.builder() )
).build() );
}
private static void computerDrop( BiConsumer<ResourceLocation, LootTable> add, Block block )
{
add.accept( block.getRegistryName(), LootTable
.builder()
.addLootPool( LootPool.builder()
.name( "main" )
.rolls( ConstantRange.of( 1 ) )
.addEntry( DynamicLootEntry.func_216162_a( new ResourceLocation( ComputerCraft.MOD_ID, "computer" ) ) )
.acceptCondition( Alternative.builder(
BlockNamedEntityLootCondition.builder(),
PlayerCreativeLootCondition.builder().inverted()
) )
).build() );
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.data;
import com.google.gson.JsonObject;
import net.minecraft.data.IFinishedRecipe;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.JSONUtils;
import net.minecraft.util.ResourceLocation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
* Adapter for recipes which overrides the serializer and adds custom item NBT.
*/
public final class RecipeWrapper implements IFinishedRecipe
{
private final IFinishedRecipe recipe;
private final CompoundNBT resultData;
private final IRecipeSerializer<?> serializer;
private RecipeWrapper( IFinishedRecipe recipe, CompoundNBT resultData, IRecipeSerializer<?> serializer )
{
this.resultData = resultData;
this.recipe = recipe;
this.serializer = serializer;
}
public static Consumer<IFinishedRecipe> wrap( IRecipeSerializer<?> serializer, Consumer<IFinishedRecipe> original )
{
return x -> original.accept( new RecipeWrapper( x, null, serializer ) );
}
public static Consumer<IFinishedRecipe> wrap( IRecipeSerializer<?> serializer, Consumer<IFinishedRecipe> original, CompoundNBT resultData )
{
return x -> original.accept( new RecipeWrapper( x, resultData, serializer ) );
}
public static Consumer<IFinishedRecipe> wrap( IRecipeSerializer<?> serializer, Consumer<IFinishedRecipe> original, Consumer<CompoundNBT> resultData )
{
CompoundNBT tag = new CompoundNBT();
resultData.accept( tag );
return x -> original.accept( new RecipeWrapper( x, tag, serializer ) );
}
@Override
public void serialize( @Nonnull JsonObject jsonObject )
{
recipe.serialize( jsonObject );
if( resultData != null )
{
JsonObject object = JSONUtils.getJsonObject( jsonObject, "result" );
object.addProperty( "nbt", resultData.toString() );
}
}
@Nonnull
@Override
public ResourceLocation getID()
{
return recipe.getID();
}
@Nonnull
@Override
public IRecipeSerializer<?> getSerializer()
{
return serializer;
}
@Nullable
@Override
public JsonObject getAdvancementJson()
{
return recipe.getAdvancementJson();
}
@Nullable
@Override
public ResourceLocation getAdvancementID()
{
return recipe.getAdvancementID();
}
}

View File

@@ -0,0 +1,320 @@
/*
* 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.data;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.data.Tags.CCTags;
import dan200.computercraft.shared.TurtleUpgrades;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.ImpostorRecipe;
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
import net.minecraft.advancements.criterion.InventoryChangeTrigger;
import net.minecraft.advancements.criterion.ItemPredicate;
import net.minecraft.block.Blocks;
import net.minecraft.data.*;
import net.minecraft.item.*;
import net.minecraft.tags.Tag;
import net.minecraft.util.IItemProvider;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.Tags;
import javax.annotation.Nonnull;
import java.util.Locale;
import java.util.function.Consumer;
public class Recipes extends RecipeProvider
{
public Recipes( DataGenerator generator )
{
super( generator );
}
@Override
protected void registerRecipes( @Nonnull Consumer<IFinishedRecipe> add )
{
basicRecipes( add );
diskColours( add );
pocketUpgrades( add );
turtleUpgrades( add );
}
/**
* Register a crafting recipe for a disk of every dye colour.
*
* @param add The callback to add recipes.
*/
private void diskColours( @Nonnull Consumer<IFinishedRecipe> add )
{
for( Colour colour : Colour.VALUES )
{
ShapelessRecipeBuilder
.shapelessRecipe( ComputerCraft.Items.disk )
.addIngredient( Tags.Items.DUSTS_REDSTONE )
.addIngredient( Items.PAPER )
.addIngredient( DyeItem.getItem( ofColour( colour ) ) )
.setGroup( "computercraft:disk" )
.addCriterion( "has_drive", inventoryChange( ComputerCraft.Blocks.diskDrive ) )
.build( RecipeWrapper.wrap(
ImpostorShapelessRecipe.SERIALIZER, add,
x -> x.putInt( "color", colour.getHex() )
), new ResourceLocation( ComputerCraft.MOD_ID, "disk_" + (colour.ordinal() + 1) ) );
}
}
/**
* Register a crafting recipe for each turtle upgrade.
*
* @param add The callback to add recipes.
*/
private void turtleUpgrades( @Nonnull Consumer<IFinishedRecipe> add )
{
for( ComputerFamily family : ComputerFamily.values() )
{
ItemStack base = TurtleItemFactory.create( -1, null, -1, family, null, null, 0, null );
if( base.isEmpty() ) continue;
String nameId = family.name().toLowerCase( Locale.ROOT );
TurtleUpgrades.getVanillaUpgrades().forEach( upgrade -> {
ItemStack result = TurtleItemFactory.create( -1, null, -1, family, null, upgrade, -1, null );
ShapedRecipeBuilder
.shapedRecipe( result.getItem() )
.setGroup( String.format( "%s:turtle_%s", ComputerCraft.MOD_ID, nameId ) )
.patternLine( "#T" )
.key( '#', base.getItem() )
.key( 'T', upgrade.getCraftingItem().getItem() )
.addCriterion( "has_items",
inventoryChange( base.getItem(), upgrade.getCraftingItem().getItem() ) )
.build(
RecipeWrapper.wrap( ImpostorRecipe.SERIALIZER, add, result.getTag() ),
new ResourceLocation( ComputerCraft.MOD_ID, String.format( "turtle_%s/%s/%s",
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
) )
);
} );
}
}
/**
* Register a crafting recipe for each pocket upgrade.
*
* @param add The callback to add recipes.
*/
private void pocketUpgrades( @Nonnull Consumer<IFinishedRecipe> add )
{
for( ComputerFamily family : ComputerFamily.values() )
{
ItemStack base = PocketComputerItemFactory.create( -1, null, -1, family, null );
if( base.isEmpty() ) continue;
String nameId = family.name().toLowerCase( Locale.ROOT );
TurtleUpgrades.getVanillaUpgrades().forEach( upgrade -> {
ItemStack result = PocketComputerItemFactory.create( -1, null, -1, family, null );
ShapedRecipeBuilder
.shapedRecipe( result.getItem() )
.setGroup( String.format( "%s:pocket_%s", ComputerCraft.MOD_ID, nameId ) )
.patternLine( "#" )
.patternLine( "P" )
.key( '#', base.getItem() )
.key( 'P', upgrade.getCraftingItem().getItem() )
.addCriterion( "has_items",
inventoryChange( base.getItem(), upgrade.getCraftingItem().getItem() ) )
.build(
RecipeWrapper.wrap( ImpostorRecipe.SERIALIZER, add, result.getTag() ),
new ResourceLocation( ComputerCraft.MOD_ID, String.format( "pocket_%s/%s/%s",
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
) )
);
} );
}
}
private void basicRecipes( @Nonnull Consumer<IFinishedRecipe> add )
{
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Items.cable, 6 )
.patternLine( " # " )
.patternLine( "#R#" )
.patternLine( " # " )
.key( '#', Tags.Items.STONE )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.addCriterion( "has_modem", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.computerNormal )
.patternLine( "###" )
.patternLine( "#R#" )
.patternLine( "#G#" )
.key( '#', Tags.Items.STONE )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_redstone", inventoryChange( Tags.Items.DUSTS_REDSTONE ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.computerAdvanced )
.patternLine( "###" )
.patternLine( "#R#" )
.patternLine( "#G#" )
.key( '#', Tags.Items.INGOTS_GOLD )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_components", inventoryChange( Items.REDSTONE, Items.GOLD_INGOT ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.computerCommand )
.patternLine( "###" )
.patternLine( "#R#" )
.patternLine( "#G#" )
.key( '#', Tags.Items.INGOTS_GOLD )
.key( 'R', Blocks.COMMAND_BLOCK )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_components", inventoryChange( Blocks.COMMAND_BLOCK ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.diskDrive )
.patternLine( "###" )
.patternLine( "#R#" )
.patternLine( "#R#" )
.key( '#', Tags.Items.STONE )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.monitorNormal )
.patternLine( "###" )
.patternLine( "#G#" )
.patternLine( "###" )
.key( '#', Tags.Items.STONE )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.monitorAdvanced, 4 )
.patternLine( "###" )
.patternLine( "#G#" )
.patternLine( "###" )
.key( '#', Tags.Items.INGOTS_GOLD )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Items.pocketComputerNormal )
.patternLine( "###" )
.patternLine( "#A#" )
.patternLine( "#G#" )
.key( '#', Tags.Items.STONE )
.key( 'A', Items.GOLDEN_APPLE )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.addCriterion( "has_apple", inventoryChange( Items.GOLDEN_APPLE ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Items.pocketComputerAdvanced )
.patternLine( "###" )
.patternLine( "#A#" )
.patternLine( "#G#" )
.key( '#', Tags.Items.INGOTS_GOLD )
.key( 'A', Items.GOLDEN_APPLE )
.key( 'G', Tags.Items.GLASS_PANES )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.addCriterion( "has_apple", inventoryChange( Items.GOLDEN_APPLE ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.printer )
.patternLine( "###" )
.patternLine( "#R#" )
.patternLine( "#D#" )
.key( '#', Tags.Items.STONE )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.key( 'D', Tags.Items.DYES )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.speaker )
.patternLine( "###" )
.patternLine( "#N#" )
.patternLine( "#R#" )
.key( '#', Tags.Items.STONE )
.key( 'N', Blocks.NOTE_BLOCK )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Items.wiredModem )
.patternLine( "###" )
.patternLine( "#R#" )
.patternLine( "###" )
.key( '#', Tags.Items.STONE )
.key( 'R', Tags.Items.DUSTS_REDSTONE )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.addCriterion( "has_cable", inventoryChange( ComputerCraft.Items.cable ) )
.build( add );
ShapelessRecipeBuilder
.shapelessRecipe( ComputerCraft.Blocks.wiredModemFull )
.addIngredient( ComputerCraft.Items.wiredModem )
.addCriterion( "has_modem", inventoryChange( CCTags.WIRED_MODEM ) )
.build( add, new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full_from" ) );
ShapelessRecipeBuilder
.shapelessRecipe( ComputerCraft.Items.wiredModem )
.addIngredient( ComputerCraft.Blocks.wiredModemFull )
.addCriterion( "has_modem", inventoryChange( CCTags.WIRED_MODEM ) )
.build( add, new ResourceLocation( ComputerCraft.MOD_ID, "wired_modem_full_to" ) );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.wirelessModemNormal )
.patternLine( "###" )
.patternLine( "#E#" )
.patternLine( "###" )
.key( '#', Tags.Items.STONE )
.key( 'E', Tags.Items.ENDER_PEARLS )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.build( add );
ShapedRecipeBuilder
.shapedRecipe( ComputerCraft.Blocks.wirelessModemAdvanced )
.patternLine( "###" )
.patternLine( "#E#" )
.patternLine( "###" )
.key( '#', Tags.Items.INGOTS_GOLD )
.key( 'E', Items.ENDER_EYE )
.addCriterion( "has_computer", inventoryChange( CCTags.COMPUTER ) )
.addCriterion( "has_wireless", inventoryChange( ComputerCraft.Blocks.wirelessModemNormal ) )
.build( add );
}
private static DyeColor ofColour( Colour colour )
{
return DyeColor.byId( 15 - colour.ordinal() );
}
private static InventoryChangeTrigger.Instance inventoryChange( Tag<Item> stack )
{
return InventoryChangeTrigger.Instance.forItems( ItemPredicate.Builder.create().tag( stack ).build() );
}
private static InventoryChangeTrigger.Instance inventoryChange( IItemProvider... stack )
{
return InventoryChangeTrigger.Instance.forItems( stack );
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.data;
import dan200.computercraft.ComputerCraft;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.ItemTagsProvider;
import net.minecraft.item.Item;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.Tag;
import net.minecraft.util.ResourceLocation;
import static dan200.computercraft.data.Tags.CCTags.*;
public class Tags extends ItemTagsProvider
{
public static class CCTags
{
public static final Tag<Item> COMPUTER = item( "computer" );
public static final Tag<Item> TURTLE = item( "turtle" );
public static final Tag<Item> WIRED_MODEM = item( "wired_modem" );
public static final Tag<Item> MONITOR = item( "monitor" );
}
public Tags( DataGenerator generator )
{
super( generator );
}
@Override
protected void registerTags()
{
getBuilder( COMPUTER )
.add( ComputerCraft.Items.computerNormal )
.add( ComputerCraft.Items.computerAdvanced )
.add( ComputerCraft.Items.computerCommand );
getBuilder( TURTLE ).add( ComputerCraft.Items.turtleNormal, ComputerCraft.Items.turtleAdvanced );
getBuilder( WIRED_MODEM ).add( ComputerCraft.Items.wiredModem, ComputerCraft.Blocks.wiredModemFull.asItem() );
getBuilder( MONITOR )
.add( ComputerCraft.Blocks.monitorNormal.asItem() )
.add( ComputerCraft.Blocks.monitorAdvanced.asItem() );
}
private static Tag<Item> item( String name )
{
return new ItemTags.Wrapper( new ResourceLocation( ComputerCraft.MOD_ID, name ) );
}
}

View File

@@ -38,42 +38,42 @@ public final class Config
private static final String TRANSLATION_PREFIX = "gui.computercraft.config.";
private static ConfigValue<Integer> computerSpaceLimit;
private static ConfigValue<Integer> floppySpaceLimit;
private static ConfigValue<Integer> maximumFilesOpen;
private static ConfigValue<Boolean> disableLua51Features;
private static ConfigValue<String> defaultComputerSettings;
private static ConfigValue<Boolean> debugEnabled;
private static ConfigValue<Boolean> logComputerErrors;
private static final ConfigValue<Integer> computerSpaceLimit;
private static final ConfigValue<Integer> floppySpaceLimit;
private static final ConfigValue<Integer> maximumFilesOpen;
private static final ConfigValue<Boolean> disableLua51Features;
private static final ConfigValue<String> defaultComputerSettings;
private static final ConfigValue<Boolean> debugEnabled;
private static final ConfigValue<Boolean> logComputerErrors;
private static ConfigValue<Integer> computerThreads;
private static ConfigValue<Integer> maxMainGlobalTime;
private static ConfigValue<Integer> maxMainComputerTime;
private static final ConfigValue<Integer> computerThreads;
private static final ConfigValue<Integer> maxMainGlobalTime;
private static final ConfigValue<Integer> maxMainComputerTime;
private static ConfigValue<Boolean> httpEnabled;
private static ConfigValue<Boolean> httpWebsocketEnabled;
private static ConfigValue<List<? extends UnmodifiableConfig>> httpRules;
private static final ConfigValue<Boolean> httpEnabled;
private static final ConfigValue<Boolean> httpWebsocketEnabled;
private static final ConfigValue<List<? extends UnmodifiableConfig>> httpRules;
private static ConfigValue<Integer> httpTimeout;
private static ConfigValue<Integer> httpMaxRequests;
private static ConfigValue<Integer> httpMaxDownload;
private static ConfigValue<Integer> httpMaxUpload;
private static ConfigValue<Integer> httpMaxWebsockets;
private static ConfigValue<Integer> httpMaxWebsocketMessage;
private static final ConfigValue<Integer> httpTimeout;
private static final ConfigValue<Integer> httpMaxRequests;
private static final ConfigValue<Integer> httpMaxDownload;
private static final ConfigValue<Integer> httpMaxUpload;
private static final ConfigValue<Integer> httpMaxWebsockets;
private static final ConfigValue<Integer> httpMaxWebsocketMessage;
private static ConfigValue<Boolean> commandBlockEnabled;
private static ConfigValue<Integer> modemRange;
private static ConfigValue<Integer> modemHighAltitudeRange;
private static ConfigValue<Integer> modemRangeDuringStorm;
private static ConfigValue<Integer> modemHighAltitudeRangeDuringStorm;
private static ConfigValue<Integer> maxNotesPerTick;
private static final ConfigValue<Boolean> commandBlockEnabled;
private static final ConfigValue<Integer> modemRange;
private static final ConfigValue<Integer> modemHighAltitudeRange;
private static final ConfigValue<Integer> modemRangeDuringStorm;
private static final ConfigValue<Integer> modemHighAltitudeRangeDuringStorm;
private static final ConfigValue<Integer> maxNotesPerTick;
private static ConfigValue<Boolean> turtlesNeedFuel;
private static ConfigValue<Integer> turtleFuelLimit;
private static ConfigValue<Integer> advancedTurtleFuelLimit;
private static ConfigValue<Boolean> turtlesObeyBlockProtection;
private static ConfigValue<Boolean> turtlesCanPush;
private static ConfigValue<List<? extends String>> turtleDisabledActions;
private static final ConfigValue<Boolean> turtlesNeedFuel;
private static final ConfigValue<Integer> turtleFuelLimit;
private static final ConfigValue<Integer> advancedTurtleFuelLimit;
private static final ConfigValue<Boolean> turtlesObeyBlockProtection;
private static final ConfigValue<Boolean> turtlesCanPush;
private static final ConfigValue<List<? extends String>> turtleDisabledActions;
private static final ForgeConfigSpec spec;

View File

@@ -40,4 +40,9 @@ public final class BlockNamedEntityLootCondition implements ILootCondition
{
return Collections.singleton( LootParameters.BLOCK_ENTITY );
}
public static IBuilder builder()
{
return () -> INSTANCE;
}
}

View File

@@ -40,4 +40,9 @@ public final class PlayerCreativeLootCondition implements ILootCondition
{
return Collections.singleton( LootParameters.THIS_ENTITY );
}
public static IBuilder builder()
{
return () -> INSTANCE;
}
}

View File

@@ -16,7 +16,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ClientMonitor extends ClientTerminal
public final class ClientMonitor extends ClientTerminal
{
private static final Set<ClientMonitor> allMonitors = new HashSet<>();
@@ -24,7 +24,8 @@ public class ClientMonitor extends ClientTerminal
public long lastRenderFrame = -1;
public BlockPos lastRenderPos = null;
public VertexBuffer buffer = null;
public VertexBuffer buffer;
public ClientMonitor( boolean colour, TileMonitor origin )
{
@@ -37,16 +38,45 @@ public class ClientMonitor extends ClientTerminal
return origin;
}
/**
* Create the appropriate buffer if needed.
*
* @param renderer The renderer to use. This can be fetched from {@link MonitorRenderer#current()}.
* @return If a buffer was created. This will return {@code false} if we already have an appropriate buffer,
* or this mode does not require one.
*/
@OnlyIn( Dist.CLIENT )
public void createBuffer()
public boolean createBuffer( MonitorRenderer renderer )
{
if( buffer == null )
switch( renderer )
{
buffer = new VertexBuffer( FixedWidthFontRenderer.TYPE.getVertexFormat() );
synchronized( allMonitors )
{
allMonitors.add( this );
}
case VBO:
if( buffer != null ) return false;
deleteBuffers();
buffer = new VertexBuffer( FixedWidthFontRenderer.POSITION_COLOR_TEX );
addMonitor();
return true;
default:
return false;
}
}
private void addMonitor()
{
synchronized( allMonitors )
{
allMonitors.add( this );
}
}
private void deleteBuffers()
{
if( buffer != null )
{
buffer.close();
buffer = null;
}
}
@@ -60,8 +90,7 @@ public class ClientMonitor extends ClientTerminal
allMonitors.remove( this );
}
buffer.close();
buffer = null;
deleteBuffers();
}
}
@@ -73,11 +102,7 @@ public class ClientMonitor extends ClientTerminal
for( Iterator<ClientMonitor> iterator = allMonitors.iterator(); iterator.hasNext(); )
{
ClientMonitor monitor = iterator.next();
if( monitor.buffer != null )
{
monitor.buffer.close();
monitor.buffer = null;
}
monitor.deleteBuffers();
iterator.remove();
}

View File

@@ -0,0 +1,98 @@
/*
* 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 com.mojang.blaze3d.platform.GLX;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import javax.annotation.Nonnull;
import java.util.Locale;
/**
* The render type to use for monitors.
*
* @see TileEntityMonitorRenderer
* @see ClientMonitor
*/
public enum MonitorRenderer
{
/**
* Determine the best monitor backend.
*/
BEST,
/**
* Render using VBOs.
*
* @see net.minecraft.client.renderer.vertex.VertexBuffer
*/
VBO;
private static final MonitorRenderer[] VALUES = values();
public static final String[] NAMES;
private final String displayName = "gui.computercraft:config.peripheral.monitor_renderer." + name().toLowerCase( Locale.ROOT );
static
{
NAMES = new String[VALUES.length];
for( int i = 0; i < VALUES.length; i++ ) NAMES[i] = VALUES[i].displayName();
}
public String displayName()
{
return displayName;
}
@Nonnull
public static MonitorRenderer ofString( String name )
{
for( MonitorRenderer backend : VALUES )
{
if( backend.displayName.equalsIgnoreCase( name ) || backend.name().equalsIgnoreCase( name ) )
{
return backend;
}
}
ComputerCraft.log.warn( "Unknown monitor renderer {}. Falling back to default.", name );
return BEST;
}
/**
* Get the current renderer to use.
*
* @return The current renderer. Will not return {@link MonitorRenderer#BEST}.
*/
@Nonnull
public static MonitorRenderer current()
{
MonitorRenderer current = ComputerCraft.monitorRenderer;
switch( current )
{
case BEST:
return best();
case VBO:
if( !GLX.useVbo() )
{
ComputerCraft.log.warn( "VBOs are not supported on your graphics card. Falling back to default." );
ComputerCraft.monitorRenderer = BEST;
return best();
}
return VBO;
default:
return current;
}
}
private static MonitorRenderer best()
{
return VBO;
}
}

View File

@@ -45,14 +45,20 @@ import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
public final class ComputerCraftProxyCommon
{
@SubscribeEvent
@SuppressWarnings( "deprecation" )
public static void init( FMLCommonSetupEvent event )
{
NetworkHandler.setup();
registerProviders();
ArgumentSerializers.register();
net.minecraftforge.fml.DeferredWorkQueue.runLater( () -> {
registerProviders();
ArgumentSerializers.register();
registerLoot();
} );
}
public static void registerLoot()
{
LootConditionManager.registerCondition( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "block_named" ),
BlockNamedEntityLootCondition.class,

View File

@@ -48,13 +48,13 @@ public class Palette
{
if( i >= 0 && i < colours.length )
{
setColour( i, Colour.values()[i] );
setColour( i, Colour.VALUES[i] );
}
}
public void resetColours()
{
for( int i = 0; i < Colour.values().length; i++ )
for( int i = 0; i < Colour.VALUES.length; i++ )
{
resetColour( i );
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.shared.util;
import com.google.common.base.Predicate;
import com.google.common.collect.MapMaker;
import net.minecraft.block.BlockState;
import net.minecraft.entity.*;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
@@ -54,8 +53,7 @@ public final class WorldUtil
public static boolean isLiquidBlock( World world, BlockPos pos )
{
if( !World.isValid( pos ) ) return false;
BlockState state = world.getBlockState( pos );
return !state.getFluidState().isEmpty();
return world.getBlockState( pos ).getMaterial().isLiquid();
}
public static boolean isVecInside( VoxelShape shape, Vec3d vec )