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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 )
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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( ".." ) )
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
28
src/main/java/dan200/computercraft/data/Generators.java
Normal file
28
src/main/java/dan200/computercraft/data/Generators.java
Normal 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 ) );
|
||||
}
|
||||
}
|
||||
@@ -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" );
|
||||
}
|
||||
}
|
||||
72
src/main/java/dan200/computercraft/data/LootTables.java
Normal file
72
src/main/java/dan200/computercraft/data/LootTables.java
Normal 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() );
|
||||
}
|
||||
}
|
||||
92
src/main/java/dan200/computercraft/data/RecipeWrapper.java
Normal file
92
src/main/java/dan200/computercraft/data/RecipeWrapper.java
Normal 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();
|
||||
}
|
||||
}
|
||||
320
src/main/java/dan200/computercraft/data/Recipes.java
Normal file
320
src/main/java/dan200/computercraft/data/Recipes.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
52
src/main/java/dan200/computercraft/data/Tags.java
Normal file
52
src/main/java/dan200/computercraft/data/Tags.java
Normal 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 ) );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -40,4 +40,9 @@ public final class BlockNamedEntityLootCondition implements ILootCondition
|
||||
{
|
||||
return Collections.singleton( LootParameters.BLOCK_ENTITY );
|
||||
}
|
||||
|
||||
public static IBuilder builder()
|
||||
{
|
||||
return () -> INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,9 @@ public final class PlayerCreativeLootCondition implements ILootCondition
|
||||
{
|
||||
return Collections.singleton( LootParameters.THIS_ENTITY );
|
||||
}
|
||||
|
||||
public static IBuilder builder()
|
||||
{
|
||||
return () -> INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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 )
|
||||
|
||||
Reference in New Issue
Block a user