Added card that can be used to import and export individual files from and to clients.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package li.cil.oc2.api.capabilities;
|
||||
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
|
||||
/**
|
||||
* This interface provides access to a list of {@link PlayerEntity}s that are currently
|
||||
* using a terminal or similar provided by the owner of this capability.
|
||||
* <p>
|
||||
* For example, for computers and robots this is the list of players that currently have
|
||||
* the terminal UI opened.
|
||||
*/
|
||||
public interface TerminalUserProvider {
|
||||
/**
|
||||
* The list of players currently interacting with a terminal.
|
||||
*
|
||||
* @return the list of terminal users.
|
||||
*/
|
||||
Iterable<PlayerEntity> getTerminalUsers();
|
||||
}
|
||||
@@ -28,7 +28,6 @@ public final class ComputerTerminalScreen extends ContainerScreen<ComputerTermin
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@Override
|
||||
protected void drawGuiContainerBackgroundLayer(final MatrixStack matrixStack, final float partialTicks, final int mouseX, final int mouseY) {
|
||||
terminalWidget.renderBackground(matrixStack, mouseX, mouseY);
|
||||
|
||||
375
src/main/java/li/cil/oc2/client/gui/FileChooserScreen.java
Normal file
375
src/main/java/li/cil/oc2/client/gui/FileChooserScreen.java
Normal file
@@ -0,0 +1,375 @@
|
||||
package li.cil.oc2.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.client.gui.widget.button.Button;
|
||||
import net.minecraft.client.gui.widget.list.ExtendedList;
|
||||
import net.minecraft.util.text.Color;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FileChooserScreen extends Screen {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final int MARGIN = 30;
|
||||
private static final int WIDGET_SPACING = 8;
|
||||
|
||||
private static final int TEXT_FIELD_HEIGHT = 20;
|
||||
private static final int BUTTON_HEIGHT = 20;
|
||||
private static final int LIST_ENTRY_HEIGHT = 12;
|
||||
|
||||
private static final TranslationTextComponent OPEN_TITLE_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.title.load");
|
||||
private static final TranslationTextComponent SAVE_TITLE_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.title.save");
|
||||
private static final TranslationTextComponent FILE_NAME_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.text_field.filename");
|
||||
private static final TranslationTextComponent LOAD_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.confirm_button.load");
|
||||
private static final TranslationTextComponent SAVE_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.confirm_button.save");
|
||||
private static final TranslationTextComponent OVERWRITE_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.confirm_button.overwrite");
|
||||
private static final TranslationTextComponent CANCEL_TEXT = new TranslationTextComponent("gui.oc2.file_chooser.cancel_button");
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private static Path directory = Paths.get("").toAbsolutePath();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private final FileChooserCallback callback;
|
||||
private final boolean isLoad;
|
||||
|
||||
private final Screen previousScreen;
|
||||
|
||||
private FileList fileList;
|
||||
private TextFieldWidget fileNameTextField;
|
||||
private Button okButton;
|
||||
|
||||
private boolean isComplete;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@FunctionalInterface
|
||||
public
|
||||
interface FileChooserCallback {
|
||||
void onFileSelected(Path path);
|
||||
|
||||
default void onCanceled() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void openFileChooserForSave(final String name, final FileChooserCallback callback) {
|
||||
final Screen currentScreen = Minecraft.getInstance().currentScreen;
|
||||
if (currentScreen instanceof FileChooserScreen) {
|
||||
currentScreen.closeScreen();
|
||||
}
|
||||
|
||||
final FileChooserScreen screen = new FileChooserScreen(callback, false);
|
||||
Minecraft.getInstance().displayGuiScreen(screen);
|
||||
screen.fileNameTextField.setText(name);
|
||||
}
|
||||
|
||||
public static void openFileChooserForLoad(final FileChooserCallback callback) {
|
||||
final Screen currentScreen = Minecraft.getInstance().currentScreen;
|
||||
if (currentScreen instanceof FileChooserScreen) {
|
||||
currentScreen.closeScreen();
|
||||
}
|
||||
|
||||
final FileChooserScreen screen = new FileChooserScreen(callback, true);
|
||||
Minecraft.getInstance().displayGuiScreen(screen);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public FileChooserScreen(final FileChooserCallback callback, final boolean isLoad) {
|
||||
super(isLoad ? OPEN_TITLE_TEXT : SAVE_TITLE_TEXT);
|
||||
|
||||
this.callback = callback;
|
||||
this.isLoad = isLoad;
|
||||
|
||||
this.previousScreen = Minecraft.getInstance().currentScreen;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
if (!isComplete) {
|
||||
callback.onCanceled();
|
||||
}
|
||||
|
||||
if (previousScreen != null) {
|
||||
minecraft.enqueue(() -> minecraft.displayGuiScreen(previousScreen));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final MatrixStack matrixStack, final int mouseX, final int mouseY, final float partialTicks) {
|
||||
super.renderBackground(matrixStack);
|
||||
fileList.render(matrixStack, mouseX, mouseY, partialTicks);
|
||||
fileNameTextField.render(matrixStack, mouseX, mouseY, partialTicks);
|
||||
super.render(matrixStack, mouseX, mouseY, partialTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPauseScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
minecraft.keyboardListener.enableRepeatEvents(true);
|
||||
|
||||
final int widgetsWidth = width - MARGIN * 2;
|
||||
final int listHeight = height - MARGIN - WIDGET_SPACING - TEXT_FIELD_HEIGHT - WIDGET_SPACING - BUTTON_HEIGHT - MARGIN;
|
||||
fileList = new FileList(MARGIN, listHeight, LIST_ENTRY_HEIGHT);
|
||||
addListener(fileList);
|
||||
|
||||
final int fileNameTop = MARGIN + listHeight + WIDGET_SPACING;
|
||||
fileNameTextField = new TextFieldWidget(font, MARGIN, fileNameTop, widgetsWidth, TEXT_FIELD_HEIGHT, FILE_NAME_TEXT);
|
||||
fileNameTextField.setResponder(s -> {
|
||||
fileList.setSelected(null);
|
||||
updateButtons();
|
||||
});
|
||||
fileNameTextField.setMaxStringLength(1024);
|
||||
addListener(fileNameTextField);
|
||||
|
||||
final int buttonTop = fileNameTop + TEXT_FIELD_HEIGHT + WIDGET_SPACING;
|
||||
final int buttonCount = 2;
|
||||
final int buttonWidth = widgetsWidth / buttonCount - (buttonCount - 1) * WIDGET_SPACING;
|
||||
okButton = addButton(new Button(MARGIN, buttonTop, buttonWidth, BUTTON_HEIGHT, StringTextComponent.EMPTY, this::handleOkPressed));
|
||||
addButton(new Button(MARGIN + buttonWidth + WIDGET_SPACING, buttonTop, buttonWidth, BUTTON_HEIGHT, CANCEL_TEXT, this::handleCancelPressed));
|
||||
|
||||
fileList.refreshFiles(directory);
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean isParentPath() {
|
||||
if (directory == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final FileList.FileEntry selected = fileList.getSelected();
|
||||
if (selected != null) {
|
||||
return selected.file == null || selected.file.equals(directory.getParent());
|
||||
}
|
||||
|
||||
final String selectedFileEntry = fileNameTextField.getText();
|
||||
return "..".equals(selectedFileEntry);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Optional<Path> getPath() {
|
||||
final FileList.FileEntry selected = fileList.getSelected();
|
||||
if (selected != null) {
|
||||
return Optional.ofNullable(selected.file);
|
||||
}
|
||||
|
||||
if (directory == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final String selectedFileEntry = fileNameTextField.getText();
|
||||
if (selectedFileEntry == null || "".equals(selectedFileEntry) || ".".equals(selectedFileEntry)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(directory.resolve(selectedFileEntry));
|
||||
} catch (final InvalidPathException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void confirm() {
|
||||
if (isParentPath()) {
|
||||
fileList.refreshFiles(getPath().orElse(null));
|
||||
fileNameTextField.setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
getPath().ifPresent(path -> {
|
||||
if (path == null || Files.isDirectory(path)) {
|
||||
fileList.refreshFiles(path);
|
||||
fileNameTextField.setText("");
|
||||
return;
|
||||
}
|
||||
if (Files.isRegularFile(path)) {
|
||||
isComplete = true;
|
||||
callback.onFileSelected(path);
|
||||
closeScreen();
|
||||
} else if (!isLoad) {
|
||||
isComplete = true;
|
||||
callback.onFileSelected(path);
|
||||
closeScreen();
|
||||
} // else: cannot load non-existing file
|
||||
});
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
isComplete = true;
|
||||
callback.onCanceled();
|
||||
closeScreen();
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
okButton.active = false;
|
||||
okButton.setMessage(isLoad ? LOAD_TEXT : SAVE_TEXT);
|
||||
okButton.clearFGColor();
|
||||
|
||||
if (isParentPath()) {
|
||||
okButton.active = true;
|
||||
return;
|
||||
}
|
||||
|
||||
getPath().ifPresent(path -> {
|
||||
if (isLoad) {
|
||||
okButton.active = Files.exists(path);
|
||||
} else {
|
||||
okButton.active = true;
|
||||
if (Files.isRegularFile(path)) {
|
||||
okButton.setMessage(OVERWRITE_TEXT);
|
||||
okButton.setFGColor(0xFF0000);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleOkPressed(final Button button) {
|
||||
confirm();
|
||||
}
|
||||
|
||||
private void handleCancelPressed(final Button button) {
|
||||
cancel();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private final class FileList extends ExtendedList<FileList.FileEntry> {
|
||||
public FileList(final int y, final int height, final int slotHeight) {
|
||||
super(FileChooserScreen.this.minecraft, FileChooserScreen.this.width, FileChooserScreen.this.height, y, y + height, slotHeight);
|
||||
}
|
||||
|
||||
public void refreshFiles(final Path directory) {
|
||||
FileChooserScreen.directory = directory;
|
||||
|
||||
setScrollAmount(0);
|
||||
clearEntries();
|
||||
|
||||
if (directory != null && Files.isDirectory(directory)) {
|
||||
addEntry(createDirectoryEntry(directory.getParent(), ".."));
|
||||
|
||||
try {
|
||||
final List<Path> files = Files.list(directory)
|
||||
.sorted((p1, p2) -> {
|
||||
if (Files.isDirectory(p1) && !Files.isDirectory(p2)) {
|
||||
return -1;
|
||||
}
|
||||
if (!Files.isDirectory(p1) && Files.isDirectory(p2)) {
|
||||
return 1;
|
||||
}
|
||||
return p1.getFileName().compareTo(p2.getFileName());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
for (final Path path : files) {
|
||||
if (Files.isHidden(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Files.isDirectory(path)) {
|
||||
addEntry(createDirectoryEntry(path));
|
||||
} else {
|
||||
addEntry(createFileEntry(path));
|
||||
}
|
||||
}
|
||||
} catch (final IOException | SecurityException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
} else {
|
||||
for (final Path path : FileSystems.getDefault().getRootDirectories()) {
|
||||
addEntry(createDirectoryEntry(path, path.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(@Nullable final FileChooserScreen.FileList.FileEntry entry) {
|
||||
super.setSelected(entry);
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private FileList.FileEntry createFileEntry(final Path file) {
|
||||
return new FileList.FileEntry(file, new StringTextComponent(file.getFileName().toString()));
|
||||
}
|
||||
|
||||
private FileList.FileEntry createDirectoryEntry(final Path path) {
|
||||
return createDirectoryEntry(path, path.getFileName().toString() + path.getFileSystem().getSeparator());
|
||||
}
|
||||
|
||||
private FileList.FileEntry createDirectoryEntry(final Path path, final String displayName) {
|
||||
return new FileList.FileEntry(path, new StringTextComponent(displayName)
|
||||
.modifyStyle(s -> s.setColor(Color.fromInt(0xA0A0FF))));
|
||||
}
|
||||
|
||||
private final class FileEntry extends ExtendedList.AbstractListEntry<FileEntry> {
|
||||
private final Path file;
|
||||
private final ITextComponent displayName;
|
||||
|
||||
private long lastEntryClickTime = 0;
|
||||
|
||||
public FileEntry(final Path file, final ITextComponent displayName) {
|
||||
this.file = file;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final MatrixStack stack, final int index, final int top, final int left, final int width, final int height,
|
||||
final int mouseX, final int mouseY, final boolean isHovered, final float deltaTime) {
|
||||
font.func_243246_a(stack, displayName, left, top, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(final double mouseX, final double mouseY, final int button) {
|
||||
final boolean isLeftClick = button == 0;
|
||||
if (isLeftClick) {
|
||||
if (file == null || (directory != null && file.equals(directory.getParent()))) {
|
||||
fileNameTextField.setText("..");
|
||||
} else {
|
||||
final Path fileName = file.getFileName();
|
||||
fileNameTextField.setText(fileName != null ? fileName.toString() : file.toString());
|
||||
}
|
||||
fileNameTextField.setCursorPositionZero();
|
||||
fileNameTextField.setSelectionPos(0);
|
||||
setSelected(this);
|
||||
|
||||
final boolean isDoubleClick = System.currentTimeMillis() - lastEntryClickTime < 250;
|
||||
if (isDoubleClick) {
|
||||
confirm();
|
||||
}
|
||||
|
||||
lastEntryClickTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,9 @@ public final class Config {
|
||||
|
||||
public static double memoryEnergyPerMegabytePerTick = 0.5;
|
||||
public static double hardDriveEnergyPerMegabytePerTick = 1;
|
||||
public static int networkInterfaceEnergyPerTick = 1;
|
||||
public static int redstoneInterfaceCardEnergyPerTick = 1;
|
||||
public static int networkInterfaceEnergyPerTick = 1;
|
||||
public static int cloudInterfaceCardEnergyPerTick = 1;
|
||||
public static int blockOperationsModuleEnergyPerTick = 2;
|
||||
public static int inventoryOperationsModuleEnergyPerTick = 1;
|
||||
|
||||
@@ -94,8 +95,9 @@ public final class Config {
|
||||
|
||||
memoryEnergyPerMegabytePerTick = COMMON_INSTANCE.memoryEnergyPerMegabytePerTick.get();
|
||||
hardDriveEnergyPerMegabytePerTick = COMMON_INSTANCE.hardDriveEnergyPerMegabytePerTick.get();
|
||||
networkInterfaceEnergyPerTick = COMMON_INSTANCE.networkInterfaceEnergyPerTick.get();
|
||||
redstoneInterfaceCardEnergyPerTick = COMMON_INSTANCE.redstoneInterfaceCardEnergyPerTick.get();
|
||||
networkInterfaceEnergyPerTick = COMMON_INSTANCE.networkInterfaceEnergyPerTick.get();
|
||||
cloudInterfaceCardEnergyPerTick = COMMON_INSTANCE.cloudInterfaceCardEnergyPerTick.get();
|
||||
blockOperationsModuleEnergyPerTick = COMMON_INSTANCE.blockOperationsModuleEnergyPerTick.get();
|
||||
inventoryOperationsModuleEnergyPerTick = COMMON_INSTANCE.inventoryOperationsModuleEnergyPerTick.get();
|
||||
|
||||
@@ -125,8 +127,9 @@ public final class Config {
|
||||
|
||||
public final ForgeConfigSpec.DoubleValue memoryEnergyPerMegabytePerTick;
|
||||
public final ForgeConfigSpec.DoubleValue hardDriveEnergyPerMegabytePerTick;
|
||||
public final ForgeConfigSpec.IntValue networkInterfaceEnergyPerTick;
|
||||
public final ForgeConfigSpec.IntValue redstoneInterfaceCardEnergyPerTick;
|
||||
public final ForgeConfigSpec.IntValue networkInterfaceEnergyPerTick;
|
||||
public final ForgeConfigSpec.IntValue cloudInterfaceCardEnergyPerTick;
|
||||
public final ForgeConfigSpec.IntValue blockOperationsModuleEnergyPerTick;
|
||||
public final ForgeConfigSpec.IntValue inventoryOperationsModuleEnergyPerTick;
|
||||
|
||||
@@ -163,8 +166,9 @@ public final class Config {
|
||||
{
|
||||
memoryEnergyPerMegabytePerTick = builder.defineInRange("memoryEnergyPerMegabytePerTick", Config.memoryEnergyPerMegabytePerTick, 0, Integer.MAX_VALUE);
|
||||
hardDriveEnergyPerMegabytePerTick = builder.defineInRange("hardDriveEnergyPerMegabytePerTick", Config.hardDriveEnergyPerMegabytePerTick, 0, Integer.MAX_VALUE);
|
||||
networkInterfaceEnergyPerTick = builder.defineInRange("networkInterfaceEnergyPerTick", Config.networkInterfaceEnergyPerTick, 0, Integer.MAX_VALUE);
|
||||
redstoneInterfaceCardEnergyPerTick = builder.defineInRange("redstoneInterfaceCardEnergyPerTick", Config.redstoneInterfaceCardEnergyPerTick, 0, Integer.MAX_VALUE);
|
||||
networkInterfaceEnergyPerTick = builder.defineInRange("networkInterfaceEnergyPerTick", Config.networkInterfaceEnergyPerTick, 0, Integer.MAX_VALUE);
|
||||
cloudInterfaceCardEnergyPerTick = builder.defineInRange("cloudInterfaceCardEnergyPerTick", Config.cloudInterfaceCardEnergyPerTick, 0, Integer.MAX_VALUE);
|
||||
blockOperationsModuleEnergyPerTick = builder.defineInRange("blockOperationsModuleEnergyPerTick", Config.blockOperationsModuleEnergyPerTick, 0, Integer.MAX_VALUE);
|
||||
inventoryOperationsModuleEnergyPerTick = builder.defineInRange("inventoryOperationsModuleEnergyPerTick", Config.inventoryOperationsModuleEnergyPerTick, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
package li.cil.oc2.common.bus.device.item;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import li.cil.oc2.api.bus.device.ItemDevice;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.bus.device.object.DocumentedDevice;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.bus.device.object.Parameter;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.api.capabilities.TerminalUserProvider;
|
||||
import li.cil.oc2.common.bus.device.util.IdentityProxy;
|
||||
import li.cil.oc2.common.network.Network;
|
||||
import li.cil.oc2.common.network.message.ExportedFileMessage;
|
||||
import li.cil.oc2.common.network.message.RequestImportedFileMessage;
|
||||
import li.cil.oc2.common.network.message.ServerCanceledImportFileMessage;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.StringUtils;
|
||||
import net.minecraftforge.fml.network.PacketDistributor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class CloudInterfaceCardItemDevice extends IdentityProxy<ItemStack> implements RPCDevice, DocumentedDevice, ItemDevice {
|
||||
public static final int MAX_TRANSFERRED_FILE_SIZE = 512 * 1024;
|
||||
|
||||
private static final String BEGIN_EXPORT_FILE = "beginExportFile";
|
||||
private static final String WRITE_EXPORT_FILE = "writeExportFile";
|
||||
private static final String FINISH_EXPORT_FILE = "finishExportFile";
|
||||
private static final String BEGIN_IMPORT_FILE = "beginImportFile";
|
||||
private static final String READ_IMPORT_FILE = "readImportFile";
|
||||
private static final String RESET = "reset";
|
||||
private static final String NAME = "name";
|
||||
private static final String DATA = "data";
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private enum State {
|
||||
IDLE,
|
||||
EXPORTING,
|
||||
IMPORTING,
|
||||
IMPORT_CANCELED,
|
||||
}
|
||||
|
||||
private static final class ExportedFile {
|
||||
public final String name;
|
||||
public final ByteArrayOutputStream data = new ByteArrayOutputStream();
|
||||
|
||||
private ExportedFile(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ImportedFile {
|
||||
public final ByteArrayInputStream data;
|
||||
|
||||
private ImportedFile(final byte[] data) {
|
||||
this.data = new ByteArrayInputStream(data);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ImportFileRequest {
|
||||
public final Set<ServerPlayerEntity> PendingPlayers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
public final WeakReference<CloudInterfaceCardItemDevice> Device;
|
||||
|
||||
private ImportFileRequest(final CloudInterfaceCardItemDevice device) {
|
||||
Device = new WeakReference<>(device);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final Int2ObjectArrayMap<ImportFileRequest> importingDevices = new Int2ObjectArrayMap<>();
|
||||
private static int nextImportId = 1;
|
||||
|
||||
private final TerminalUserProvider userProvider;
|
||||
private final ObjectDevice device;
|
||||
private State state;
|
||||
private ExportedFile exportedFile;
|
||||
private int importingId;
|
||||
private ImportedFile importedFile;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public CloudInterfaceCardItemDevice(final ItemStack identity, final TerminalUserProvider userProvider) {
|
||||
super(identity);
|
||||
this.userProvider = userProvider;
|
||||
this.device = new ObjectDevice(this, "cloud");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static void setImportedFile(final int id, final byte[] data) {
|
||||
final ImportFileRequest request = importingDevices.remove(id);
|
||||
if (request != null) {
|
||||
final CloudInterfaceCardItemDevice device = request.Device.get();
|
||||
if (device != null) {
|
||||
device.importedFile = new ImportedFile(data);
|
||||
final ServerCanceledImportFileMessage message = new ServerCanceledImportFileMessage(id);
|
||||
for (final ServerPlayerEntity player : request.PendingPlayers) {
|
||||
Network.INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void cancelImport(final ServerPlayerEntity player, final int id) {
|
||||
final ImportFileRequest request = importingDevices.get(id);
|
||||
if (request != null) {
|
||||
request.PendingPlayers.remove(player);
|
||||
if (request.PendingPlayers.isEmpty()) {
|
||||
importingDevices.remove(id);
|
||||
final CloudInterfaceCardItemDevice device = request.Device.get();
|
||||
if (device != null) {
|
||||
device.state = State.IMPORT_CANCELED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public List<String> getTypeNames() {
|
||||
return device.getTypeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RPCMethod> getMethods() {
|
||||
return device.getMethods();
|
||||
}
|
||||
|
||||
@Callback(name = BEGIN_EXPORT_FILE, synchronize = false)
|
||||
public void beginExportFile(@Parameter(NAME) final String name) {
|
||||
if (state != State.IDLE) {
|
||||
throw new IllegalStateException("invalid state");
|
||||
}
|
||||
|
||||
if (StringUtils.isNullOrEmpty(name)) {
|
||||
throw new IllegalArgumentException("name must not be empty");
|
||||
}
|
||||
|
||||
state = State.EXPORTING;
|
||||
exportedFile = new ExportedFile(name);
|
||||
}
|
||||
|
||||
@Callback(name = WRITE_EXPORT_FILE, synchronize = false)
|
||||
public void writeExportFile(@Parameter(DATA) final byte[] data) throws IOException {
|
||||
if (state != State.EXPORTING) {
|
||||
throw new IllegalStateException("invalid state");
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("data is required");
|
||||
}
|
||||
|
||||
exportedFile.data.write(data);
|
||||
|
||||
if (exportedFile.data.size() > MAX_TRANSFERRED_FILE_SIZE) {
|
||||
reset();
|
||||
throw new IllegalArgumentException("exported file too large");
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(name = FINISH_EXPORT_FILE)
|
||||
public void finishExportFile() {
|
||||
if (state != State.EXPORTING) {
|
||||
throw new IllegalStateException("invalid state");
|
||||
}
|
||||
|
||||
try {
|
||||
for (final PlayerEntity player : userProvider.getTerminalUsers()) {
|
||||
if (player instanceof ServerPlayerEntity) {
|
||||
final ExportedFileMessage message = new ExportedFileMessage(exportedFile.name, exportedFile.data.toByteArray());
|
||||
Network.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), message);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(name = BEGIN_IMPORT_FILE)
|
||||
public void beginImportFile() {
|
||||
if (state != State.IDLE) {
|
||||
throw new IllegalStateException("invalid state");
|
||||
}
|
||||
|
||||
state = State.IMPORTING;
|
||||
|
||||
importingId = nextImportId++;
|
||||
importingDevices.put(importingId, new ImportFileRequest(this));
|
||||
|
||||
boolean hasAnyUsers = false;
|
||||
for (final PlayerEntity player : userProvider.getTerminalUsers()) {
|
||||
if (player instanceof ServerPlayerEntity) {
|
||||
final RequestImportedFileMessage message = new RequestImportedFileMessage(importingId);
|
||||
Network.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), message);
|
||||
hasAnyUsers = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAnyUsers) {
|
||||
importingDevices.remove(importingId);
|
||||
importedFile = new ImportedFile(new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Callback(name = READ_IMPORT_FILE)
|
||||
public byte[] readImportFile() throws IOException {
|
||||
if (state == State.IMPORT_CANCELED) {
|
||||
reset();
|
||||
throw new IllegalStateException("import was canceled");
|
||||
}
|
||||
|
||||
if (state != State.IMPORTING) {
|
||||
throw new IllegalStateException("invalid state");
|
||||
}
|
||||
|
||||
if (importedFile == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
final byte[] buffer = new byte[512];
|
||||
final int count = importedFile.data.read(buffer);
|
||||
if (count <= 0) {
|
||||
reset();
|
||||
return null;
|
||||
}
|
||||
if (count < buffer.length) {
|
||||
final byte[] data = new byte[count];
|
||||
System.arraycopy(buffer, 0, data, 0, count);
|
||||
return data;
|
||||
} else {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(name = RESET)
|
||||
public void reset() {
|
||||
state = State.IDLE;
|
||||
exportedFile = null;
|
||||
importedFile = null;
|
||||
importingDevices.remove(importingId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDeviceDocumentation(final DeviceVisitor visitor) {
|
||||
visitor.visitCallback(BEGIN_EXPORT_FILE)
|
||||
.description("Begins exporting a file to external data storage. Requires calls to " +
|
||||
WRITE_EXPORT_FILE + "() to provide data of the exported file and a call " +
|
||||
"to " + FINISH_EXPORT_FILE + "() to complete the export.\n" +
|
||||
"This method may error if the device is currently exporting or importing.")
|
||||
.parameterDescription(NAME, "the name of the file being exported.");
|
||||
visitor.visitCallback(WRITE_EXPORT_FILE)
|
||||
.description("Appends more data to the currently being exported file.\n" +
|
||||
"This method may error if the device is not currently exporting or the " +
|
||||
"export was interrupted.\n")
|
||||
.parameterDescription(DATA, "the contents of the file being exported.");
|
||||
visitor.visitCallback(FINISH_EXPORT_FILE)
|
||||
.description("Finishes an export. This will prompt present users to select an external " +
|
||||
"file location for the file being exported. If multiple users are present, " +
|
||||
"the file is provided to all users.\n" +
|
||||
"This method may error if the device is not currently exporting or the " +
|
||||
"export was interrupted.");
|
||||
visitor.visitCallback(BEGIN_IMPORT_FILE)
|
||||
.description("Begins a file import operation. This will prompt present users to select " +
|
||||
"an externally stored file for import. If multiple users are present, the " +
|
||||
"first user to select a file will have their file uploaded. Use the " +
|
||||
READ_IMPORT_FILE + "() method to read the contents of the file being imported.\n" +
|
||||
"This method may error if the device is currently exporting or importing.");
|
||||
visitor.visitCallback(READ_IMPORT_FILE)
|
||||
.description("Tries to read some data from a file being imported. Returns zero length " +
|
||||
"data if no data is available yet. Returns null when no more data is " +
|
||||
"available.\n" +
|
||||
"This method may error if the device is not currently importing or the " +
|
||||
"import was interrupted.")
|
||||
.returnValueDescription("data from the file being imported.");
|
||||
visitor.visitCallback(RESET)
|
||||
.description("Resets the device and cancels any currently running export or import operation.");
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ public final class Providers {
|
||||
ITEM_DEVICE_PROVIDERS.register("flash_memory_custom", FlashMemoryWithExternalDataItemDeviceProvider::new);
|
||||
ITEM_DEVICE_PROVIDERS.register("redstone_interface_card", RedstoneInterfaceCardItemDeviceProvider::new);
|
||||
ITEM_DEVICE_PROVIDERS.register("network_interface_card", NetworkInterfaceCardItemDeviceProvider::new);
|
||||
ITEM_DEVICE_PROVIDERS.register("cloud_interface_card", CloudInterfaceCardItemDeviceProvider::new);
|
||||
|
||||
ITEM_DEVICE_PROVIDERS.register("inventory_operations_module", InventoryOperationsModuleDeviceProvider::new);
|
||||
ITEM_DEVICE_PROVIDERS.register("block_operations_module", BlockOperationsModuleDeviceProvider::new);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package li.cil.oc2.common.bus.device.provider.item;
|
||||
|
||||
import li.cil.oc2.api.bus.device.ItemDevice;
|
||||
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
|
||||
import li.cil.oc2.api.capabilities.TerminalUserProvider;
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.bus.device.item.CloudInterfaceCardItemDevice;
|
||||
import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.item.Items;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class CloudInterfaceCardItemDeviceProvider extends AbstractItemDeviceProvider {
|
||||
public CloudInterfaceCardItemDeviceProvider() {
|
||||
super(Items.CLOUD_INTERFACE_CARD);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected boolean matches(final ItemDeviceQuery query) {
|
||||
return super.matches(query) && getTerminalUserProvider(query).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<ItemDevice> getItemDevice(final ItemDeviceQuery query) {
|
||||
return getTerminalUserProvider(query).map(provider ->
|
||||
new CloudInterfaceCardItemDevice(query.getItemStack(), provider));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getItemDeviceEnergyConsumption(final ItemDeviceQuery query) {
|
||||
return Config.cloudInterfaceCardEnergyPerTick;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private Optional<TerminalUserProvider> getTerminalUserProvider(final ItemDeviceQuery query) {
|
||||
if (query.getContainerTileEntity().isPresent()) {
|
||||
final LazyOptional<TerminalUserProvider> capability = query.getContainerTileEntity().get()
|
||||
.getCapability(Capabilities.TERMINAL_USER_PROVIDER);
|
||||
if (capability.isPresent()) {
|
||||
return capability.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
if (query.getContainerEntity().isPresent()) {
|
||||
final LazyOptional<TerminalUserProvider> capability = query.getContainerEntity().get()
|
||||
.getCapability(Capabilities.TERMINAL_USER_PROVIDER);
|
||||
if (capability.isPresent()) {
|
||||
return capability.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,11 @@ public abstract class AbstractItemDeviceProvider extends ForgeRegistryEntry<Item
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
protected boolean matches(final ItemDeviceQuery query) {
|
||||
final ItemStack stack = query.getItemStack();
|
||||
return !stack.isEmpty() && predicate.test(stack.getItem());
|
||||
}
|
||||
|
||||
protected abstract Optional<ItemDevice> getItemDevice(final ItemDeviceQuery query);
|
||||
|
||||
protected Optional<DeviceType> getItemDeviceType(final ItemDeviceQuery query) {
|
||||
@@ -62,11 +67,4 @@ public abstract class AbstractItemDeviceProvider extends ForgeRegistryEntry<Item
|
||||
protected int getItemDeviceEnergyConsumption(final ItemDeviceQuery query) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean matches(final ItemDeviceQuery query) {
|
||||
final ItemStack stack = query.getItemStack();
|
||||
return !stack.isEmpty() && predicate.test(stack.getItem());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.api.capabilities.NetworkInterface;
|
||||
import li.cil.oc2.api.capabilities.RedstoneEmitter;
|
||||
import li.cil.oc2.api.capabilities.Robot;
|
||||
import li.cil.oc2.api.capabilities.TerminalUserProvider;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityInject;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
@@ -30,6 +31,9 @@ public final class Capabilities {
|
||||
@CapabilityInject(NetworkInterface.class)
|
||||
public static Capability<NetworkInterface> NETWORK_INTERFACE = null;
|
||||
|
||||
@CapabilityInject(TerminalUserProvider.class)
|
||||
public static Capability<TerminalUserProvider> TERMINAL_USER_PROVIDER = null;
|
||||
|
||||
@CapabilityInject(Robot.class)
|
||||
public static Capability<Robot> ROBOT = null;
|
||||
|
||||
@@ -39,6 +43,7 @@ public final class Capabilities {
|
||||
register(DeviceBusElement.class);
|
||||
register(RedstoneEmitter.class);
|
||||
register(NetworkInterface.class);
|
||||
register(TerminalUserProvider.class);
|
||||
register(Robot.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ public final class ComputerTerminalContainer extends AbstractContainer {
|
||||
this.computer = computer;
|
||||
this.energyInfo = energyInfo;
|
||||
|
||||
this.computer.addTerminalUser(player);
|
||||
|
||||
assertIntArraySize(energyInfo, ENERGY_INFO_SIZE);
|
||||
trackIntArray(energyInfo);
|
||||
}
|
||||
|
||||
@@ -59,11 +59,13 @@ public final class Items {
|
||||
public static final RegistryObject<FlashMemoryWithExternalDataItem> FLASH_MEMORY_CUSTOM = register("flash_memory_custom", () ->
|
||||
new FlashMemoryWithExternalDataItem(Firmwares.BUILDROOT.getId()));
|
||||
|
||||
public static final RegistryObject<Item> REDSTONE_INTERFACE_CARD = register("redstone_interface_card");
|
||||
public static final RegistryObject<Item> NETWORK_INTERFACE_CARD = register("network_interface_card");
|
||||
public static final RegistryObject<FloppyItem> FLOPPY = register("floppy", () ->
|
||||
new FloppyItem(512 * Constants.KILOBYTE));
|
||||
|
||||
public static final RegistryObject<Item> REDSTONE_INTERFACE_CARD = register("redstone_interface_card");
|
||||
public static final RegistryObject<Item> NETWORK_INTERFACE_CARD = register("network_interface_card");
|
||||
public static final RegistryObject<Item> CLOUD_INTERFACE_CARD = register("cloud_interface_card");
|
||||
|
||||
public static final RegistryObject<Item> INVENTORY_OPERATIONS_MODULE = register("inventory_operations_module");
|
||||
public static final RegistryObject<Item> BLOCK_OPERATIONS_MODULE = register("block_operations_module", BlockOperationsModule::new);
|
||||
|
||||
|
||||
@@ -134,6 +134,36 @@ public final class Network {
|
||||
.decoder(BusInterfaceNameMessage.ToServer::new)
|
||||
.consumer(BusInterfaceNameMessage::handleMessageServer)
|
||||
.add();
|
||||
|
||||
INSTANCE.messageBuilder(ExportedFileMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(ExportedFileMessage::toBytes)
|
||||
.decoder(ExportedFileMessage::new)
|
||||
.consumer(ExportedFileMessage::handleMessage)
|
||||
.add();
|
||||
|
||||
INSTANCE.messageBuilder(RequestImportedFileMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(RequestImportedFileMessage::toBytes)
|
||||
.decoder(RequestImportedFileMessage::new)
|
||||
.consumer(RequestImportedFileMessage::handleMessage)
|
||||
.add();
|
||||
|
||||
INSTANCE.messageBuilder(ImportedFileMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(ImportedFileMessage::toBytes)
|
||||
.decoder(ImportedFileMessage::new)
|
||||
.consumer(ImportedFileMessage::handleMessage)
|
||||
.add();
|
||||
|
||||
INSTANCE.messageBuilder(ServerCanceledImportFileMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(ServerCanceledImportFileMessage::toBytes)
|
||||
.decoder(ServerCanceledImportFileMessage::new)
|
||||
.consumer(ServerCanceledImportFileMessage::handleMessage)
|
||||
.add();
|
||||
|
||||
INSTANCE.messageBuilder(ClientCanceledImportFileMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(ClientCanceledImportFileMessage::toBytes)
|
||||
.decoder(ClientCanceledImportFileMessage::new)
|
||||
.consumer(ClientCanceledImportFileMessage::handleMessage)
|
||||
.add();
|
||||
}
|
||||
|
||||
public static <T> void sendToClientsTrackingChunk(final T message, final Chunk chunk) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.common.bus.device.item.CloudInterfaceCardItemDevice;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ClientCanceledImportFileMessage {
|
||||
private int id;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ClientCanceledImportFileMessage(final int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ClientCanceledImportFileMessage(final PacketBuffer buffer) {
|
||||
fromBytes(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean handleMessage(final ClientCanceledImportFileMessage message, final Supplier<NetworkEvent.Context> context) {
|
||||
CloudInterfaceCardItemDevice.cancelImport(context.get().getSender(), message.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void toBytes(final ClientCanceledImportFileMessage message, final PacketBuffer buffer) {
|
||||
buffer.writeVarInt(message.id);
|
||||
}
|
||||
|
||||
public void fromBytes(final PacketBuffer buffer) {
|
||||
id = buffer.readVarInt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.client.gui.FileChooserScreen;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ExportedFileMessage {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private String name;
|
||||
private byte[] data;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ExportedFileMessage(final String name, final byte[] data) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ExportedFileMessage(final PacketBuffer buffer) {
|
||||
fromBytes(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean handleMessage(final ExportedFileMessage message, final Supplier<NetworkEvent.Context> context) {
|
||||
context.get().enqueueWork(() -> FileChooserScreen.openFileChooserForSave(message.name, path -> {
|
||||
try {
|
||||
Files.write(path, message.data);
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void toBytes(final ExportedFileMessage message, final PacketBuffer buffer) {
|
||||
buffer.writeString(message.name);
|
||||
buffer.writeByteArray(message.data);
|
||||
}
|
||||
|
||||
public void fromBytes(final PacketBuffer buffer) {
|
||||
name = buffer.readString();
|
||||
data = buffer.readByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.common.bus.device.item.CloudInterfaceCardItemDevice;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ImportedFileMessage {
|
||||
private int id;
|
||||
private byte[] data;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ImportedFileMessage(final int id, final byte[] data) {
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ImportedFileMessage(final PacketBuffer buffer) {
|
||||
fromBytes(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean handleMessage(final ImportedFileMessage message, final Supplier<NetworkEvent.Context> context) {
|
||||
CloudInterfaceCardItemDevice.setImportedFile(message.id, message.data);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void toBytes(final ImportedFileMessage message, final PacketBuffer buffer) {
|
||||
buffer.writeVarInt(message.id);
|
||||
buffer.writeByteArray(message.data);
|
||||
}
|
||||
|
||||
public void fromBytes(final PacketBuffer buffer) {
|
||||
id = buffer.readVarInt();
|
||||
data = buffer.readByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.client.gui.FileChooserScreen;
|
||||
import li.cil.oc2.common.bus.device.item.CloudInterfaceCardItemDevice;
|
||||
import li.cil.oc2.common.network.Network;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.util.text.Color;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class RequestImportedFileMessage {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final TranslationTextComponent FILE_TOO_LARGE_TEXT = new TranslationTextComponent("message.oc2.import_file.file_too_large");
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private int id;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public RequestImportedFileMessage(final int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public RequestImportedFileMessage(final PacketBuffer buffer) {
|
||||
fromBytes(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean handleMessage(final RequestImportedFileMessage message, final Supplier<NetworkEvent.Context> context) {
|
||||
context.get().enqueueWork(() -> FileChooserScreen.openFileChooserForLoad(new FileChooserScreen.FileChooserCallback() {
|
||||
@Override
|
||||
public void onFileSelected(final Path path) {
|
||||
try {
|
||||
final byte[] data = Files.readAllBytes(path);
|
||||
if (data.length > CloudInterfaceCardItemDevice.MAX_TRANSFERRED_FILE_SIZE) {
|
||||
Network.INSTANCE.sendToServer(new ClientCanceledImportFileMessage(message.id));
|
||||
Minecraft.getInstance().player.sendStatusMessage(FILE_TOO_LARGE_TEXT
|
||||
.modifyStyle(s -> s.setColor(Color.fromInt(0xFFA0A0))), false);
|
||||
} else {
|
||||
Network.INSTANCE.sendToServer(new ImportedFileMessage(message.id, data));
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
Network.INSTANCE.sendToServer(new ClientCanceledImportFileMessage(message.id));
|
||||
}
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void toBytes(final RequestImportedFileMessage message, final PacketBuffer buffer) {
|
||||
buffer.writeVarInt(message.id);
|
||||
}
|
||||
|
||||
public void fromBytes(final PacketBuffer buffer) {
|
||||
id = buffer.readVarInt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.common.bus.device.item.CloudInterfaceCardItemDevice;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ServerCanceledImportFileMessage {
|
||||
private int id;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ServerCanceledImportFileMessage(final int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ServerCanceledImportFileMessage(final PacketBuffer buffer) {
|
||||
fromBytes(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean handleMessage(final ServerCanceledImportFileMessage message, final Supplier<NetworkEvent.Context> context) {
|
||||
CloudInterfaceCardItemDevice.cancelImport(context.get().getSender(), message.id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void toBytes(final ServerCanceledImportFileMessage message, final PacketBuffer buffer) {
|
||||
buffer.writeVarInt(message.id);
|
||||
}
|
||||
|
||||
public void fromBytes(final PacketBuffer buffer) {
|
||||
id = buffer.readVarInt();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceTypes;
|
||||
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
|
||||
import li.cil.oc2.api.capabilities.TerminalUserProvider;
|
||||
import li.cil.oc2.client.audio.LoopingSoundManager;
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.Constants;
|
||||
@@ -49,14 +50,12 @@ import net.minecraftforge.fml.network.NetworkHooks;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
import static li.cil.oc2.common.Constants.BLOCK_ENTITY_TAG_NAME_IN_ITEM;
|
||||
import static li.cil.oc2.common.Constants.ITEMS_TAG_NAME;
|
||||
|
||||
public final class ComputerTileEntity extends AbstractTileEntity implements ITickableTileEntity {
|
||||
public final class ComputerTileEntity extends AbstractTileEntity implements ITickableTileEntity, TerminalUserProvider {
|
||||
private static final String BUS_ELEMENT_TAG_NAME = "busElement";
|
||||
private static final String TERMINAL_TAG_NAME = "terminal";
|
||||
private static final String STATE_TAG_NAME = "state";
|
||||
@@ -80,6 +79,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
private final ComputerItemStackHandlers deviceItems = new ComputerItemStackHandlers();
|
||||
private final FixedEnergyStorage energy = new FixedEnergyStorage(Config.computerEnergyStorage);
|
||||
private final ComputerVirtualMachine virtualMachine = new ComputerVirtualMachine(new TileEntityDeviceBusController(busElement, Config.computerEnergyPerTick, this), deviceItems::getDeviceAddressBase);
|
||||
private final Set<PlayerEntity> terminalUsers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -171,6 +171,19 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
}, getPos());
|
||||
}
|
||||
|
||||
public void addTerminalUser(final PlayerEntity player) {
|
||||
terminalUsers.add(player);
|
||||
}
|
||||
|
||||
public void removeTerminalUser(final PlayerEntity player) {
|
||||
terminalUsers.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<PlayerEntity> getTerminalUsers() {
|
||||
return terminalUsers;
|
||||
}
|
||||
|
||||
public void handleNeighborChanged() {
|
||||
virtualMachine.busController.scheduleBusScan();
|
||||
}
|
||||
@@ -292,6 +305,7 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
protected void collectCapabilities(final CapabilityCollector collector, @Nullable final Direction direction) {
|
||||
collector.offer(Capabilities.ITEM_HANDLER, deviceItems.combinedItemHandlers);
|
||||
collector.offer(Capabilities.DEVICE_BUS_ELEMENT, busElement);
|
||||
collector.offer(Capabilities.TERMINAL_USER_PROVIDER, this);
|
||||
|
||||
if (Config.computersUseEnergy()) {
|
||||
collector.offer(Capabilities.ENERGY_STORAGE, energy);
|
||||
|
||||
@@ -35,11 +35,12 @@ public final class ModItemModelProvider extends ItemModelProvider {
|
||||
.texture("layer1", "item/hard_drive_tint");
|
||||
simple(Items.FLASH_MEMORY, "item/flash_memory");
|
||||
simple(Items.FLASH_MEMORY_CUSTOM, "item/flash_memory");
|
||||
simple(Items.FLOPPY, "item/floppy_base")
|
||||
.texture("layer1", "item/floppy_tint");
|
||||
|
||||
simple(Items.REDSTONE_INTERFACE_CARD, "item/redstone_interface_card");
|
||||
simple(Items.NETWORK_INTERFACE_CARD, "item/network_interface_card");
|
||||
simple(Items.FLOPPY, "item/floppy_base")
|
||||
.texture("layer1", "item/floppy_tint");
|
||||
simple(Items.CLOUD_INTERFACE_CARD, "item/cloud_interface_card");
|
||||
|
||||
simple(Items.INVENTORY_OPERATIONS_MODULE, "item/inventory_operations_module");
|
||||
simple(Items.BLOCK_OPERATIONS_MODULE, "item/block_operations_module");
|
||||
|
||||
@@ -48,17 +48,18 @@ public final class ModItemTagsProvider extends ItemTagsProvider {
|
||||
Items.FLASH_MEMORY.get(),
|
||||
Items.FLASH_MEMORY_CUSTOM.get()
|
||||
);
|
||||
getOrCreateBuilder(DEVICES_FLOPPY).add(
|
||||
Items.FLOPPY.get()
|
||||
);
|
||||
getOrCreateBuilder(DEVICES_CARD).add(
|
||||
Items.REDSTONE_INTERFACE_CARD.get(),
|
||||
Items.NETWORK_INTERFACE_CARD.get()
|
||||
Items.NETWORK_INTERFACE_CARD.get(),
|
||||
Items.CLOUD_INTERFACE_CARD.get()
|
||||
);
|
||||
getOrCreateBuilder(DEVICES_ROBOT_MODULE).add(
|
||||
Items.INVENTORY_OPERATIONS_MODULE.get(),
|
||||
Items.BLOCK_OPERATIONS_MODULE.get()
|
||||
);
|
||||
getOrCreateBuilder(DEVICES_FLOPPY).add(
|
||||
Items.FLOPPY.get()
|
||||
);
|
||||
|
||||
getOrCreateBuilder(TOOL_MATERIALS).addTags(
|
||||
TOOL_MATERIAL_WOOD,
|
||||
|
||||
@@ -56,9 +56,18 @@
|
||||
"gui.oc2.device_type.flash_memory": "Flash Memory",
|
||||
"gui.oc2.device_type.card": "Card",
|
||||
|
||||
"gui.oc2.file_chooser.title.load": "Open file",
|
||||
"gui.oc2.file_chooser.title.save": "Save file",
|
||||
"gui.oc2.file_chooser.text_field.filename": "File name",
|
||||
"gui.oc2.file_chooser.confirm_button.load": "Open",
|
||||
"gui.oc2.file_chooser.confirm_button.save": "Save",
|
||||
"gui.oc2.file_chooser.confirm_button.overwrite": "Overwrite",
|
||||
"gui.oc2.file_chooser.cancel_button": "Cancel",
|
||||
|
||||
"message.oc2.connector.error.full": "Cannot attach more cables.",
|
||||
"message.oc2.connector.error.too_far": "Distance between connectors is too large.",
|
||||
"message.oc2.connector.error.obstructed": "No clear line of sight between connectors.",
|
||||
"message.oc2.import_file.file_too_large": "File is too large.",
|
||||
|
||||
"tooltip.oc2.device_needs_reboot": "Requires reboot",
|
||||
"tooltip.oc2.flash_memory_missing": "A flash memory containing a firmware is required to boot.",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "oc2:item/cloud_interface_card"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"animation": {
|
||||
"frametime": 1,
|
||||
"frames": [
|
||||
{ "index": 0, "time": 4 },
|
||||
{ "index": 1, "time": 4 },
|
||||
{ "index": 2, "time": 4 },
|
||||
{ "index": 3, "time": 4 },
|
||||
{ "index": 4, "time": 4 },
|
||||
{ "index": 5, "time": 4 },
|
||||
{ "index": 6, "time": 4 },
|
||||
{ "index": 7, "time": 4 }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local devices = require("devices")
|
||||
local cloud = devices:find("cloud")
|
||||
|
||||
if not cloud then
|
||||
print("A cloud interface card is required for this functionality.")
|
||||
return
|
||||
end
|
||||
|
||||
if not arg[1] then
|
||||
io.write("Usage: export.lua filename\n")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local file = assert(io.open(arg[1], "rb"))
|
||||
|
||||
cloud:reset()
|
||||
|
||||
io.write("Exporting")
|
||||
|
||||
cloud:beginExportFile(arg[1])
|
||||
|
||||
while true do
|
||||
local str = file:read(512)
|
||||
if not str then break end
|
||||
if #str > 0 then
|
||||
local bytes = {string.byte(str, 1, -1)}
|
||||
cloud:writeExportFile(bytes)
|
||||
io.write(".")
|
||||
end
|
||||
end
|
||||
io.write("\n")
|
||||
|
||||
cloud:finishExportFile()
|
||||
|
||||
assert(file:close())
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"attributes": {
|
||||
"is_executable": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local devices = require("devices")
|
||||
local cloud = devices:find("cloud")
|
||||
|
||||
if not cloud then
|
||||
print("A cloud interface card is required for this functionality.")
|
||||
return
|
||||
end
|
||||
|
||||
if not arg[1] then
|
||||
io.write("Usage: import.lua filename\n")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local file = assert(io.open(arg[1], "wb"))
|
||||
|
||||
cloud:reset()
|
||||
|
||||
io.write("Importing")
|
||||
|
||||
cloud:beginImportFile()
|
||||
|
||||
while true do
|
||||
local bytes = cloud:readImportFile()
|
||||
if not bytes then break end
|
||||
if #bytes > 0 then
|
||||
file:write(string.char(table.unpack(bytes)))
|
||||
io.write(".")
|
||||
end
|
||||
end
|
||||
io.write("\n")
|
||||
|
||||
assert(file:close())
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"attributes": {
|
||||
"is_executable": true
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"replace": false,
|
||||
"values": [
|
||||
"oc2:redstone_interface_card",
|
||||
"oc2:network_interface_card"
|
||||
"oc2:network_interface_card",
|
||||
"oc2:cloud_interface_card"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user