mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-14 12:10:30 +00:00
File uploading (#840)
This commit is contained in:
parent
5ef8d52c13
commit
a5bca3f0df
@ -6,20 +6,40 @@
|
|||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
|
import dan200.computercraft.ComputerCraft;
|
||||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
||||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
|
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
|
||||||
|
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||||
|
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||||
|
import dan200.computercraft.shared.network.NetworkHandler;
|
||||||
|
import dan200.computercraft.shared.network.server.ContinueUploadMessage;
|
||||||
|
import dan200.computercraft.shared.network.server.UploadFileMessage;
|
||||||
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
|
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
import net.minecraft.util.text.ITextComponent;
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraft.util.text.TranslationTextComponent;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class ComputerScreenBase<T extends ContainerComputerBase> extends ContainerScreen<T>
|
public abstract class ComputerScreenBase<T extends ContainerComputerBase> extends ContainerScreen<T>
|
||||||
{
|
{
|
||||||
|
private static final ITextComponent OK = new TranslationTextComponent( "gui.ok" );
|
||||||
|
private static final ITextComponent CANCEL = new TranslationTextComponent( "gui.cancel" );
|
||||||
|
private static final ITextComponent OVERWRITE = new TranslationTextComponent( "gui.computercraft.upload.overwrite_button" );
|
||||||
|
|
||||||
protected WidgetTerminal terminal;
|
protected WidgetTerminal terminal;
|
||||||
protected final ClientComputer computer;
|
protected final ClientComputer computer;
|
||||||
protected final ComputerFamily family;
|
protected final ComputerFamily family;
|
||||||
@ -95,4 +115,94 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
|
|||||||
{
|
{
|
||||||
// Skip rendering labels.
|
// Skip rendering labels.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFilesDrop( @Nonnull List<Path> files )
|
||||||
|
{
|
||||||
|
if( files.isEmpty() ) return;
|
||||||
|
|
||||||
|
if( computer == null || !computer.isOn() )
|
||||||
|
{
|
||||||
|
alert( UploadResult.FAILED_TITLE, UploadResult.COMPUTER_OFF_MSG );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
List<FileUpload> toUpload = new ArrayList<>();
|
||||||
|
for( Path file : files )
|
||||||
|
{
|
||||||
|
// TODO: Recurse directories? If so, we probably want to shunt this off-thread.
|
||||||
|
if( !Files.isRegularFile( file ) ) continue;
|
||||||
|
|
||||||
|
try( SeekableByteChannel sbc = Files.newByteChannel( file ) )
|
||||||
|
{
|
||||||
|
long fileSize = sbc.size();
|
||||||
|
if( fileSize > UploadFileMessage.MAX_SIZE || (size += fileSize) >= UploadFileMessage.MAX_SIZE )
|
||||||
|
{
|
||||||
|
alert( UploadResult.FAILED_TITLE, UploadResult.TOO_MUCH_MSG );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocateDirect( (int) fileSize );
|
||||||
|
sbc.read( buffer );
|
||||||
|
buffer.flip();
|
||||||
|
|
||||||
|
toUpload.add( new FileUpload( file.getFileName().toString(), buffer ) );
|
||||||
|
}
|
||||||
|
catch( IOException e )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.error( "Failed uploading files", e );
|
||||||
|
alert( UploadResult.FAILED_TITLE, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( toUpload.size() > 0 )
|
||||||
|
{
|
||||||
|
NetworkHandler.sendToServer( new UploadFileMessage( computer.getInstanceID(), toUpload ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadResult( UploadResult result, ITextComponent message )
|
||||||
|
{
|
||||||
|
switch( result )
|
||||||
|
{
|
||||||
|
case SUCCESS:
|
||||||
|
alert( UploadResult.SUCCESS_TITLE, message );
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
alert( UploadResult.FAILED_TITLE, message );
|
||||||
|
break;
|
||||||
|
case CONFIRM_OVERWRITE:
|
||||||
|
OptionScreen.show(
|
||||||
|
minecraft, UploadResult.UPLOAD_OVERWRITE, message,
|
||||||
|
Arrays.asList(
|
||||||
|
OptionScreen.newButton( CANCEL, b -> cancelUpload() ),
|
||||||
|
OptionScreen.newButton( OVERWRITE, b -> continueUpload() )
|
||||||
|
),
|
||||||
|
this::cancelUpload
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void continueUpload()
|
||||||
|
{
|
||||||
|
if( minecraft.screen instanceof OptionScreen ) ((OptionScreen) minecraft.screen).disable();
|
||||||
|
NetworkHandler.sendToServer( new ContinueUploadMessage( computer.getInstanceID(), true ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelUpload()
|
||||||
|
{
|
||||||
|
minecraft.setScreen( this );
|
||||||
|
NetworkHandler.sendToServer( new ContinueUploadMessage( computer.getInstanceID(), false ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void alert( ITextComponent title, ITextComponent message )
|
||||||
|
{
|
||||||
|
OptionScreen.show( minecraft, title, message,
|
||||||
|
Collections.singletonList( OptionScreen.newButton( OK, b -> minecraft.setScreen( this ) ) ),
|
||||||
|
() -> minecraft.setScreen( this )
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
127
src/main/java/dan200/computercraft/client/gui/OptionScreen.java
Normal file
127
src/main/java/dan200/computercraft/client/gui/OptionScreen.java
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.IBidiRenderer;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.client.gui.widget.Widget;
|
||||||
|
import net.minecraft.client.gui.widget.button.Button;
|
||||||
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class OptionScreen extends Screen
|
||||||
|
{
|
||||||
|
private static final ResourceLocation BACKGROUND = new ResourceLocation( "computercraft", "textures/gui/blank_screen.png" );
|
||||||
|
|
||||||
|
public static final int BUTTON_WIDTH = 100;
|
||||||
|
public static final int BUTTON_HEIGHT = 20;
|
||||||
|
|
||||||
|
private static final int PADDING = 16;
|
||||||
|
private static final int FONT_HEIGHT = 9;
|
||||||
|
|
||||||
|
private int x;
|
||||||
|
private int y;
|
||||||
|
private int innerWidth;
|
||||||
|
private int innerHeight;
|
||||||
|
|
||||||
|
private IBidiRenderer messageRenderer;
|
||||||
|
private final ITextComponent message;
|
||||||
|
private final List<Widget> buttons;
|
||||||
|
private final Runnable exit;
|
||||||
|
|
||||||
|
private final Screen originalScreen;
|
||||||
|
|
||||||
|
private OptionScreen( ITextComponent title, ITextComponent message, List<Widget> buttons, Runnable exit, Screen originalScreen )
|
||||||
|
{
|
||||||
|
super( title );
|
||||||
|
this.message = message;
|
||||||
|
this.buttons = buttons;
|
||||||
|
this.exit = exit;
|
||||||
|
this.originalScreen = originalScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void show( Minecraft minecraft, ITextComponent title, ITextComponent message, List<Widget> buttons, Runnable exit )
|
||||||
|
{
|
||||||
|
minecraft.setScreen( new OptionScreen( title, message, buttons, exit, unwrap( minecraft.screen ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Screen unwrap( Screen screen )
|
||||||
|
{
|
||||||
|
return screen instanceof OptionScreen ? ((OptionScreen) screen).getOriginalScreen() : screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
super.init();
|
||||||
|
|
||||||
|
int buttonWidth = BUTTON_WIDTH * buttons.size() + PADDING * (buttons.size() - 1);
|
||||||
|
int innerWidth = this.innerWidth = Math.max( 256, buttonWidth + PADDING * 2 );
|
||||||
|
|
||||||
|
messageRenderer = IBidiRenderer.create( font, message, innerWidth - PADDING * 2 );
|
||||||
|
|
||||||
|
int textHeight = messageRenderer.getLineCount() * FONT_HEIGHT + PADDING * 2;
|
||||||
|
innerHeight = textHeight + (buttons.isEmpty() ? 0 : buttons.get( 0 ).getHeight()) + PADDING;
|
||||||
|
|
||||||
|
x = (width - innerWidth) / 2;
|
||||||
|
y = (height - innerHeight) / 2;
|
||||||
|
|
||||||
|
int x = (width - buttonWidth) / 2;
|
||||||
|
for( Widget button : buttons )
|
||||||
|
{
|
||||||
|
button.x = x;
|
||||||
|
button.y = y + textHeight;
|
||||||
|
addButton( button );
|
||||||
|
|
||||||
|
x += BUTTON_WIDTH + PADDING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render( @Nonnull MatrixStack transform, int mouseX, int mouseY, float partialTicks )
|
||||||
|
{
|
||||||
|
renderBackground( transform );
|
||||||
|
|
||||||
|
// Render the actual texture.
|
||||||
|
minecraft.textureManager.bind( BACKGROUND );
|
||||||
|
blit( transform, x, y, 0, 0, innerWidth, PADDING );
|
||||||
|
blit( transform,
|
||||||
|
x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
|
||||||
|
innerWidth, PADDING
|
||||||
|
);
|
||||||
|
blit( transform, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING );
|
||||||
|
|
||||||
|
messageRenderer.renderLeftAlignedNoShadow( transform, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040 );
|
||||||
|
super.render( transform, mouseX, mouseY, partialTicks );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
exit.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Widget newButton( ITextComponent component, Button.IPressable clicked )
|
||||||
|
{
|
||||||
|
return new Button( 0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, component, clicked );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disable()
|
||||||
|
{
|
||||||
|
for( Widget widget : buttons ) widget.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public Screen getOriginalScreen()
|
||||||
|
{
|
||||||
|
return originalScreen;
|
||||||
|
}
|
||||||
|
}
|
@ -5,16 +5,18 @@
|
|||||||
*/
|
*/
|
||||||
package dan200.computercraft.shared.computer.core;
|
package dan200.computercraft.shared.computer.core;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||||
|
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||||
import net.minecraft.inventory.container.Container;
|
import net.minecraft.inventory.container.Container;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An instance of {@link Container} which provides a computer. You should implement this
|
* An instance of {@link Container} which provides a computer. You should implement this
|
||||||
* if you provide custom computers/GUIs to interact with them.
|
* if you provide custom computers/GUIs to interact with them.
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
|
||||||
public interface IContainerComputer
|
public interface IContainerComputer
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -33,8 +35,21 @@ public interface IContainerComputer
|
|||||||
* @return This container's input.
|
* @return This container's input.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
default InputState getInput()
|
InputState getInput();
|
||||||
{
|
|
||||||
return new InputState( this );
|
/**
|
||||||
}
|
* Attempt to upload a series of files to this computer.
|
||||||
|
*
|
||||||
|
* @param uploader The player uploading files.
|
||||||
|
* @param files The files to upload.
|
||||||
|
*/
|
||||||
|
void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List<FileUpload> files );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue an upload.
|
||||||
|
*
|
||||||
|
* @param uploader The player uploading files.
|
||||||
|
* @param overwrite Whether the files should be overwritten or not.
|
||||||
|
*/
|
||||||
|
void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite );
|
||||||
}
|
}
|
||||||
|
@ -6,23 +6,41 @@
|
|||||||
package dan200.computercraft.shared.computer.inventory;
|
package dan200.computercraft.shared.computer.inventory;
|
||||||
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.core.filesystem.FileSystem;
|
||||||
|
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||||
|
import dan200.computercraft.core.filesystem.FileSystemWrapper;
|
||||||
import dan200.computercraft.shared.computer.core.*;
|
import dan200.computercraft.shared.computer.core.*;
|
||||||
|
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||||
|
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||||
|
import dan200.computercraft.shared.network.NetworkHandler;
|
||||||
|
import dan200.computercraft.shared.network.client.UploadResultMessage;
|
||||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
|
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||||
import net.minecraft.inventory.container.Container;
|
import net.minecraft.inventory.container.Container;
|
||||||
import net.minecraft.inventory.container.ContainerType;
|
import net.minecraft.inventory.container.ContainerType;
|
||||||
|
import net.minecraft.util.text.TranslationTextComponent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public class ContainerComputerBase extends Container implements IContainerComputer
|
public class ContainerComputerBase extends Container implements IContainerComputer
|
||||||
{
|
{
|
||||||
|
private static final String LIST_PREFIX = "\n \u2022 ";
|
||||||
|
|
||||||
private final Predicate<PlayerEntity> canUse;
|
private final Predicate<PlayerEntity> canUse;
|
||||||
private final IComputer computer;
|
private final IComputer computer;
|
||||||
private final ComputerFamily family;
|
private final ComputerFamily family;
|
||||||
private final InputState input = new InputState( this );
|
private final InputState input = new InputState( this );
|
||||||
|
private List<FileUpload> toUpload;
|
||||||
|
|
||||||
protected ContainerComputerBase( ContainerType<? extends ContainerComputerBase> type, int id, Predicate<PlayerEntity> canUse, IComputer computer, ComputerFamily family )
|
protected ContainerComputerBase( ContainerType<? extends ContainerComputerBase> type, int id, Predicate<PlayerEntity> canUse, IComputer computer, ComputerFamily family )
|
||||||
{
|
{
|
||||||
@ -73,6 +91,86 @@ public class ContainerComputerBase extends Container implements IContainerComput
|
|||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void upload( @Nonnull ServerPlayerEntity uploader, @Nonnull List<FileUpload> files )
|
||||||
|
{
|
||||||
|
UploadResultMessage message = upload( files, false );
|
||||||
|
NetworkHandler.sendToPlayer( uploader, message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void continueUpload( @Nonnull ServerPlayerEntity uploader, boolean overwrite )
|
||||||
|
{
|
||||||
|
List<FileUpload> files = this.toUpload;
|
||||||
|
toUpload = null;
|
||||||
|
if( files == null || files.isEmpty() || !overwrite ) return;
|
||||||
|
|
||||||
|
UploadResultMessage message = upload( files, true );
|
||||||
|
NetworkHandler.sendToPlayer( uploader, message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private UploadResultMessage upload( @Nonnull List<FileUpload> files, boolean forceOverwrite )
|
||||||
|
{
|
||||||
|
ServerComputer computer = (ServerComputer) getComputer();
|
||||||
|
if( computer == null ) return UploadResultMessage.COMPUTER_OFF;
|
||||||
|
|
||||||
|
FileSystem fs = computer.getComputer().getEnvironment().getFileSystem();
|
||||||
|
if( fs == null ) return UploadResultMessage.COMPUTER_OFF;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<String> overwrite = new ArrayList<>();
|
||||||
|
for( FileUpload upload : files )
|
||||||
|
{
|
||||||
|
if( !fs.exists( upload.getName() ) ) continue;
|
||||||
|
if( fs.isDir( upload.getName() ) )
|
||||||
|
{
|
||||||
|
return new UploadResultMessage(
|
||||||
|
UploadResult.ERROR,
|
||||||
|
new TranslationTextComponent( "gui.computercraft.upload.failed.overwrite_dir", upload.getName() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
overwrite.add( upload.getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !overwrite.isEmpty() && !forceOverwrite )
|
||||||
|
{
|
||||||
|
StringJoiner joiner = new StringJoiner( LIST_PREFIX, LIST_PREFIX, "" );
|
||||||
|
for( String value : overwrite ) joiner.add( value );
|
||||||
|
|
||||||
|
toUpload = files;
|
||||||
|
return new UploadResultMessage(
|
||||||
|
UploadResult.CONFIRM_OVERWRITE,
|
||||||
|
new TranslationTextComponent( "gui.computercraft.upload.overwrite.detail", joiner.toString() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
long availableSpace = fs.getFreeSpace( "/" );
|
||||||
|
long neededSpace = 0;
|
||||||
|
for( FileUpload upload : files ) neededSpace += Math.max( 512, upload.getBytes().remaining() );
|
||||||
|
if( neededSpace > availableSpace ) return UploadResultMessage.OUT_OF_SPACE;
|
||||||
|
|
||||||
|
for( FileUpload file : files )
|
||||||
|
{
|
||||||
|
try( FileSystemWrapper<WritableByteChannel> channel = fs.openForWrite( file.getName(), false, Function.identity() ) )
|
||||||
|
{
|
||||||
|
channel.get().write( file.getBytes() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UploadResultMessage(
|
||||||
|
UploadResult.SUCCESS, new TranslationTextComponent( "gui.computercraft.upload.success.msg", files.size() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch( FileSystemException | IOException e )
|
||||||
|
{
|
||||||
|
ComputerCraft.log.error( "Error uploading files", e );
|
||||||
|
return new UploadResultMessage( UploadResult.ERROR, new TranslationTextComponent( "computercraft.gui.upload.failed.generic", e.getMessage() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removed( @Nonnull PlayerEntity player )
|
public void removed( @Nonnull PlayerEntity player )
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,6 @@ import dan200.computercraft.ComputerCraft;
|
|||||||
import dan200.computercraft.shared.Registry;
|
import dan200.computercraft.shared.Registry;
|
||||||
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
|
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import dan200.computercraft.shared.network.container.ViewComputerContainerData;
|
import dan200.computercraft.shared.network.container.ViewComputerContainerData;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
@ -17,7 +16,7 @@ import net.minecraft.entity.player.PlayerInventory;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class ContainerViewComputer extends ContainerComputerBase implements IContainerComputer
|
public class ContainerViewComputer extends ContainerComputerBase
|
||||||
{
|
{
|
||||||
private final int width;
|
private final int width;
|
||||||
private final int height;
|
private final int height;
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.shared.computer.upload;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class FileUpload
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private final ByteBuffer bytes;
|
||||||
|
|
||||||
|
public FileUpload( String name, ByteBuffer bytes )
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.bytes = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer getBytes()
|
||||||
|
{
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.shared.computer.upload;
|
||||||
|
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraft.util.text.TranslationTextComponent;
|
||||||
|
|
||||||
|
public enum UploadResult
|
||||||
|
{
|
||||||
|
SUCCESS,
|
||||||
|
ERROR,
|
||||||
|
CONFIRM_OVERWRITE;
|
||||||
|
|
||||||
|
public static final ITextComponent SUCCESS_TITLE = new TranslationTextComponent( "gui.computercraft.upload.success" );
|
||||||
|
|
||||||
|
public static final ITextComponent FAILED_TITLE = new TranslationTextComponent( "gui.computercraft.upload.failed" );
|
||||||
|
public static final ITextComponent COMPUTER_OFF_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.computer_off" );
|
||||||
|
public static final ITextComponent OUT_OF_SPACE_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.out_of_space" );
|
||||||
|
public static final ITextComponent TOO_MUCH_MSG = new TranslationTextComponent( "gui.computercraft.upload.failed.too_much" );
|
||||||
|
|
||||||
|
public static final ITextComponent UPLOAD_OVERWRITE = new TranslationTextComponent( "gui.computercraft.upload.overwrite" );
|
||||||
|
}
|
@ -48,6 +48,8 @@ public final class NetworkHandler
|
|||||||
registerMainThread( 2, NetworkDirection.PLAY_TO_SERVER, RequestComputerMessage.class, RequestComputerMessage::new );
|
registerMainThread( 2, NetworkDirection.PLAY_TO_SERVER, RequestComputerMessage.class, RequestComputerMessage::new );
|
||||||
registerMainThread( 3, NetworkDirection.PLAY_TO_SERVER, KeyEventServerMessage.class, KeyEventServerMessage::new );
|
registerMainThread( 3, NetworkDirection.PLAY_TO_SERVER, KeyEventServerMessage.class, KeyEventServerMessage::new );
|
||||||
registerMainThread( 4, NetworkDirection.PLAY_TO_SERVER, MouseEventServerMessage.class, MouseEventServerMessage::new );
|
registerMainThread( 4, NetworkDirection.PLAY_TO_SERVER, MouseEventServerMessage.class, MouseEventServerMessage::new );
|
||||||
|
registerMainThread( 5, NetworkDirection.PLAY_TO_SERVER, UploadFileMessage.class, UploadFileMessage::new );
|
||||||
|
registerMainThread( 6, NetworkDirection.PLAY_TO_SERVER, ContinueUploadMessage.class, ContinueUploadMessage::new );
|
||||||
|
|
||||||
// Client messages
|
// Client messages
|
||||||
registerMainThread( 10, NetworkDirection.PLAY_TO_CLIENT, ChatTableClientMessage.class, ChatTableClientMessage::new );
|
registerMainThread( 10, NetworkDirection.PLAY_TO_CLIENT, ChatTableClientMessage.class, ChatTableClientMessage::new );
|
||||||
@ -59,6 +61,7 @@ public final class NetworkHandler
|
|||||||
registerMainThread( 16, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new );
|
registerMainThread( 16, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new );
|
||||||
registerMainThread( 17, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new );
|
registerMainThread( 17, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new );
|
||||||
registerMainThread( 18, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new );
|
registerMainThread( 18, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new );
|
||||||
|
registerMainThread( 19, NetworkDirection.PLAY_TO_CLIENT, UploadResultMessage.class, UploadResultMessage::new );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
|
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.shared.network.client;
|
||||||
|
|
||||||
|
import dan200.computercraft.client.gui.ComputerScreenBase;
|
||||||
|
import dan200.computercraft.client.gui.OptionScreen;
|
||||||
|
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||||
|
import dan200.computercraft.shared.network.NetworkMessage;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraft.util.text.ITextComponent;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class UploadResultMessage implements NetworkMessage
|
||||||
|
{
|
||||||
|
public static final UploadResultMessage COMPUTER_OFF = new UploadResultMessage( UploadResult.ERROR, UploadResult.COMPUTER_OFF_MSG );
|
||||||
|
public static final UploadResultMessage OUT_OF_SPACE = new UploadResultMessage( UploadResult.ERROR, UploadResult.OUT_OF_SPACE_MSG );
|
||||||
|
|
||||||
|
private final UploadResult result;
|
||||||
|
private final ITextComponent message;
|
||||||
|
|
||||||
|
public UploadResultMessage( UploadResult result, ITextComponent message )
|
||||||
|
{
|
||||||
|
this.result = result;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadResultMessage( @Nonnull PacketBuffer buf )
|
||||||
|
{
|
||||||
|
result = buf.readEnum( UploadResult.class );
|
||||||
|
message = buf.readComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toBytes( @Nonnull PacketBuffer buf )
|
||||||
|
{
|
||||||
|
buf.writeEnum( result );
|
||||||
|
buf.writeComponent( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle( NetworkEvent.Context context )
|
||||||
|
{
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
|
Screen screen = OptionScreen.unwrap( minecraft.screen );
|
||||||
|
if( screen instanceof ComputerScreenBase<?> )
|
||||||
|
{
|
||||||
|
((ComputerScreenBase<?>) screen).uploadResult( result, message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ package dan200.computercraft.shared.network.server;
|
|||||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import net.minecraft.network.PacketBuffer;
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ public class ComputerActionServerMessage extends ComputerServerMessage
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
||||||
{
|
{
|
||||||
switch( action )
|
switch( action )
|
||||||
{
|
{
|
||||||
|
@ -49,8 +49,8 @@ public abstract class ComputerServerMessage implements NetworkMessage
|
|||||||
IContainerComputer container = computer.getContainer( context.getSender() );
|
IContainerComputer container = computer.getContainer( context.getSender() );
|
||||||
if( container == null ) return;
|
if( container == null ) return;
|
||||||
|
|
||||||
handle( computer, container );
|
handle( context, computer, container );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container );
|
protected abstract void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container );
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.shared.network.server;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||||
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class ContinueUploadMessage extends ComputerServerMessage
|
||||||
|
{
|
||||||
|
private final boolean overwrite;
|
||||||
|
|
||||||
|
public ContinueUploadMessage( int instanceId, boolean overwrite )
|
||||||
|
{
|
||||||
|
super( instanceId );
|
||||||
|
this.overwrite = overwrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContinueUploadMessage( @Nonnull PacketBuffer buf )
|
||||||
|
{
|
||||||
|
super( buf );
|
||||||
|
overwrite = buf.readBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toBytes( @Nonnull PacketBuffer buf )
|
||||||
|
{
|
||||||
|
super.toBytes( buf );
|
||||||
|
buf.writeBoolean( overwrite );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
||||||
|
{
|
||||||
|
ServerPlayerEntity player = context.getSender();
|
||||||
|
if( player != null ) container.continueUpload( player, overwrite );
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer;
|
|||||||
import dan200.computercraft.shared.computer.core.InputState;
|
import dan200.computercraft.shared.computer.core.InputState;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import net.minecraft.network.PacketBuffer;
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ public class KeyEventServerMessage extends ComputerServerMessage
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
||||||
{
|
{
|
||||||
InputState input = container.getInput();
|
InputState input = container.getInput();
|
||||||
if( type == TYPE_UP )
|
if( type == TYPE_UP )
|
||||||
|
@ -9,6 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer;
|
|||||||
import dan200.computercraft.shared.computer.core.InputState;
|
import dan200.computercraft.shared.computer.core.InputState;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import net.minecraft.network.PacketBuffer;
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ public class MouseEventServerMessage extends ComputerServerMessage
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
||||||
{
|
{
|
||||||
InputState input = container.getInput();
|
InputState input = container.getInput();
|
||||||
switch( type )
|
switch( type )
|
||||||
|
@ -10,6 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
|
|||||||
import dan200.computercraft.shared.util.NBTUtil;
|
import dan200.computercraft.shared.util.NBTUtil;
|
||||||
import net.minecraft.nbt.CompoundNBT;
|
import net.minecraft.nbt.CompoundNBT;
|
||||||
import net.minecraft.network.PacketBuffer;
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -50,7 +51,7 @@ public class QueueEventServerMessage extends ComputerServerMessage
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
||||||
{
|
{
|
||||||
computer.queueEvent( event, args );
|
computer.queueEvent( event, args );
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||||
|
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||||
|
* Send enquiries to dratcliffe@gmail.com
|
||||||
|
*/
|
||||||
|
package dan200.computercraft.shared.network.server;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||||
|
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||||
|
import net.minecraft.network.PacketBuffer;
|
||||||
|
import net.minecraftforge.fml.network.NetworkEvent;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UploadFileMessage extends ComputerServerMessage
|
||||||
|
{
|
||||||
|
public static final int MAX_SIZE = 30 * 1024; // Max packet size is 32767. TODO: Bump this in the future
|
||||||
|
private final List<FileUpload> files;
|
||||||
|
|
||||||
|
public UploadFileMessage( int instanceId, List<FileUpload> files )
|
||||||
|
{
|
||||||
|
super( instanceId );
|
||||||
|
this.files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadFileMessage( @Nonnull PacketBuffer buf )
|
||||||
|
{
|
||||||
|
super( buf );
|
||||||
|
int nFiles = buf.readVarInt();
|
||||||
|
List<FileUpload> files = this.files = new ArrayList<>( nFiles );
|
||||||
|
for( int i = 0; i < nFiles; i++ )
|
||||||
|
{
|
||||||
|
String name = buf.readUtf( 32767 );
|
||||||
|
int size = buf.readVarInt();
|
||||||
|
if( size > MAX_SIZE ) break;
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocateDirect( size );
|
||||||
|
buf.readBytes( buffer );
|
||||||
|
buffer.flip();
|
||||||
|
|
||||||
|
files.add( new FileUpload( name, buffer ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toBytes( @Nonnull PacketBuffer buf )
|
||||||
|
{
|
||||||
|
super.toBytes( buf );
|
||||||
|
buf.writeVarInt( files.size() );
|
||||||
|
for( FileUpload file : files )
|
||||||
|
{
|
||||||
|
buf.writeUtf( file.getName() );
|
||||||
|
buf.writeVarInt( file.getBytes().remaining() );
|
||||||
|
buf.writeBytes( file.getBytes() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handle( NetworkEvent.Context context, @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
|
||||||
|
{
|
||||||
|
ServerPlayerEntity player = context.getSender();
|
||||||
|
if( player != null ) container.upload( player, files );
|
||||||
|
}
|
||||||
|
}
|
@ -116,5 +116,16 @@
|
|||||||
"gui.computercraft.tooltip.turn_off": "Turn this computer off",
|
"gui.computercraft.tooltip.turn_off": "Turn this computer off",
|
||||||
"gui.computercraft.tooltip.turn_off.key": "Hold Ctrl+S",
|
"gui.computercraft.tooltip.turn_off.key": "Hold Ctrl+S",
|
||||||
"gui.computercraft.tooltip.terminate": "Stop the currently running code",
|
"gui.computercraft.tooltip.terminate": "Stop the currently running code",
|
||||||
"gui.computercraft.tooltip.terminate.key": "Hold Ctrl+T"
|
"gui.computercraft.tooltip.terminate.key": "Hold Ctrl+T",
|
||||||
|
"gui.computercraft.upload.success": "Upload Succeeded",
|
||||||
|
"gui.computercraft.upload.success.msg": "%d files uploaded.",
|
||||||
|
"gui.computercraft.upload.failed": "Upload Failed",
|
||||||
|
"gui.computercraft.upload.failed.out_of_space": "Not enough space on the computer for these files.",
|
||||||
|
"gui.computercraft.upload.failed.computer_off": "You must turn the computer on before uploading files.",
|
||||||
|
"gui.computercraft.upload.failed.too_much": "Your files are too large to be uploaded.",
|
||||||
|
"gui.computercraft.upload.failed.overwrite_dir": "Cannot upload %s, as there is already a directory with the same name.",
|
||||||
|
"computercraft.gui.upload.failed.generic": "Uploading files failed (%s)",
|
||||||
|
"gui.computercraft.upload.overwrite": "Files would be overwritten",
|
||||||
|
"gui.computercraft.upload.overwrite.detail": "The following files will be overwritten when uploading. Continue?%s",
|
||||||
|
"gui.computercraft.upload.overwrite_button": "Overwrite"
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 287 B |
Loading…
Reference in New Issue
Block a user