diff --git a/src/main/java/com/simibubi/create/ClientSchematicLoader.java b/src/main/java/com/simibubi/create/ClientSchematicLoader.java index eb12aa3aa..d510fda76 100644 --- a/src/main/java/com/simibubi/create/ClientSchematicLoader.java +++ b/src/main/java/com/simibubi/create/ClientSchematicLoader.java @@ -1,5 +1,7 @@ package com.simibubi.create; +import static com.simibubi.create.ServerSchematicLoader.MAX_PACKET_SIZE; + import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -19,6 +21,7 @@ import com.simibubi.create.networking.Packets; import com.simibubi.create.utility.FilesHelper; import net.minecraft.client.Minecraft; +import net.minecraft.util.text.StringTextComponent; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -26,39 +29,28 @@ import net.minecraftforge.api.distmarker.OnlyIn; public class ClientSchematicLoader { public static final int PACKET_DELAY = 10; - public static final int PACKET_SIZE = 2048; - + private List availableSchematics; private Map activeUploads; - private Map progress; private int packetCycle; - + public ClientSchematicLoader() { availableSchematics = new ArrayList<>(); activeUploads = new HashMap<>(); - progress = new HashMap<>(); refresh(); } - + public void tick() { if (activeUploads.isEmpty()) return; if (packetCycle-- > 0) return; packetCycle = PACKET_DELAY; - + for (String schematic : new HashSet<>(activeUploads.keySet())) { continueUpload(schematic); } } - - public float getProgress(String schematic) { - if (progress.containsKey(schematic)) { - return progress.get(schematic).getProgress(); - } - - return 0; - } public void startNewUpload(String schematic) { Path path = Paths.get("schematics", schematic); @@ -67,15 +59,24 @@ public class ClientSchematicLoader { Create.logger.fatal("Missing Schematic file: " + path.toString()); return; } - + InputStream in; try { + long size = Files.size(path); + + // Too big + if (size > ServerSchematicLoader.MAX_SCHEMATIC_FILE_SIZE) { + Minecraft.getInstance().player + .sendMessage(new StringTextComponent("Your schematic is too large (" + size / 1024 + " KB).")); + Minecraft.getInstance().player + .sendMessage(new StringTextComponent("The maximum allowed schematic file size is: " + + ServerSchematicLoader.MAX_SCHEMATIC_FILE_SIZE / 1024 + " KB")); + return; + } + in = Files.newInputStream(path, StandardOpenOption.READ); activeUploads.put(schematic, in); - ReadProgress tracker = new ReadProgress(); - tracker.length = Files.size(path); - progress.put(schematic, tracker); - Packets.channel.sendToServer(PacketSchematicUpload.begin(schematic)); + Packets.channel.sendToServer(PacketSchematicUpload.begin(schematic, size)); } catch (IOException e) { e.printStackTrace(); } @@ -83,24 +84,21 @@ public class ClientSchematicLoader { private void continueUpload(String schematic) { if (activeUploads.containsKey(schematic)) { - byte[] data = new byte[PACKET_SIZE]; + byte[] data = new byte[MAX_PACKET_SIZE]; try { int status = activeUploads.get(schematic).read(data); - - progress.get(schematic).progress += status; - - if (status < PACKET_SIZE) { + if (status < MAX_PACKET_SIZE) { data = Arrays.copyOf(data, status); } - + if (Minecraft.getInstance().world != null) Packets.channel.sendToServer(PacketSchematicUpload.write(schematic, data)); else { activeUploads.remove(schematic); return; } - - if (status < PACKET_SIZE) + + if (status < MAX_PACKET_SIZE) finishUpload(schematic); } catch (IOException e) { e.printStackTrace(); @@ -114,17 +112,17 @@ public class ClientSchematicLoader { activeUploads.remove(schematic); } } - + public void refresh() { FilesHelper.createFolderIfMissing("schematics"); availableSchematics.clear(); try { Files.list(Paths.get("schematics/")) - .forEach(path -> { + .filter(f -> !Files.isDirectory(f) && f.getFileName().toString().endsWith(".nbt")).forEach(path -> { if (Files.isDirectory(path)) return; - + availableSchematics.add(path.getFileName().toString()); }); } catch (NoSuchFileException e) { @@ -142,13 +140,5 @@ public class ClientSchematicLoader { public Path getPath(String name) { return Paths.get("schematics", name + ".nbt"); } - - public static class ReadProgress { - public long length; - public long progress; - public float getProgress() { - return (float) (progress * 1d / length); - } - } } diff --git a/src/main/java/com/simibubi/create/Create.java b/src/main/java/com/simibubi/create/Create.java index b6b08b98c..5e985d8d5 100644 --- a/src/main/java/com/simibubi/create/Create.java +++ b/src/main/java/com/simibubi/create/Create.java @@ -23,8 +23,10 @@ import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent.ServerTickEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.event.server.FMLServerStoppingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; @EventBusSubscriber(bus = Bus.FORGE) @@ -73,6 +75,16 @@ public class Create { sSchematicLoader = new ServerSchematicLoader(); } + @SubscribeEvent + public static void onTick(ServerTickEvent event) { + sSchematicLoader.tick(); + } + + @SubscribeEvent + public static void onServerClose(FMLServerStoppingEvent event) { + sSchematicLoader.shutdown(); + } + @OnlyIn(Dist.CLIENT) @SubscribeEvent public static void onClientTick(ClientTickEvent event) { diff --git a/src/main/java/com/simibubi/create/ServerSchematicLoader.java b/src/main/java/com/simibubi/create/ServerSchematicLoader.java index b83d93a5e..02d1ab3fc 100644 --- a/src/main/java/com/simibubi/create/ServerSchematicLoader.java +++ b/src/main/java/com/simibubi/create/ServerSchematicLoader.java @@ -3,12 +3,17 @@ package com.simibubi.create; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; -import com.simibubi.create.block.SchematicTableContainer; import com.simibubi.create.block.SchematicTableTileEntity; import com.simibubi.create.item.ItemBlueprint; import com.simibubi.create.networking.PacketSchematicUpload.DimensionPos; @@ -17,80 +22,196 @@ import com.simibubi.create.utility.FilesHelper; import net.minecraft.block.BlockState; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.util.text.StringTextComponent; public class ServerSchematicLoader { public static final String PATH = "schematics/uploaded"; - private Map activeDownloads; - private Map activeTables; + public static final int IDLE_TIMEOUT = 600; + public static final int MAX_PACKET_SIZE = 1024; + public static final int MAX_SCHEMATICS_PER_PLAYER = 10; + public static final int MAX_SCHEMATIC_FILE_SIZE = 256 * 1024; // 256 kiB + + private Map activeUploads; + + public class SchematicUploadEntry { + public OutputStream stream; + public long bytesUploaded; + public long totalBytes; + public DimensionPos tablePos; + public int idleTime; + + public SchematicUploadEntry(OutputStream stream, long totalBytes, DimensionPos tablePos) { + this.stream = stream; + this.totalBytes = totalBytes; + this.tablePos = tablePos; + this.bytesUploaded = 0; + this.idleTime = 0; + } + } public ServerSchematicLoader() { - activeDownloads = new HashMap<>(); - activeTables = new HashMap<>(); + activeUploads = new HashMap<>(); FilesHelper.createFolderIfMissing("schematics"); FilesHelper.createFolderIfMissing(PATH); } - public void handleNewUpload(ServerPlayerEntity player, String schematic, DimensionPos dimPos) { + public void tick() { + // Detect Timed out Uploads + Set deadEntries = new HashSet<>(); + for (String upload : activeUploads.keySet()) { + SchematicUploadEntry entry = activeUploads.get(upload); + + if (entry.idleTime++ > IDLE_TIMEOUT) { + Create.logger.warn("Schematic Upload timed out: " + upload); + deadEntries.add(upload); + } + + } + + // Remove Timed out Uploads + deadEntries.forEach(this::cancelUpload); + } + + public void shutdown() { + // Close open streams + new HashSet<>(activeUploads.keySet()).forEach(this::cancelUpload); + } + + public void handleNewUpload(ServerPlayerEntity player, String schematic, long size, DimensionPos dimPos) { String playerPath = PATH + "/" + player.getName().getFormattedText(); String playerSchematicId = player.getName().getFormattedText() + "/" + schematic; - FilesHelper.createFolderIfMissing(playerPath); - if (activeDownloads.containsKey(playerSchematicId)) + // Unsupported Format + if (!schematic.endsWith(".nbt")) { + Create.logger.warn("Attempted Schematic Upload with non-supported Format: " + playerSchematicId); + } + + // Too big + if (size > MAX_SCHEMATIC_FILE_SIZE) { + player.sendMessage(new StringTextComponent("Your schematic is too large (" + size/1024 + " KB).")); + player.sendMessage(new StringTextComponent( + "The maximum allowed schematic file size is: " + MAX_SCHEMATIC_FILE_SIZE/1024 + " KB")); + return; + } + + // Skip existing Uploads + if (activeUploads.containsKey(playerSchematicId)) return; try { + // Validate Referenced Block + BlockState blockState = dimPos.world.getBlockState(dimPos.pos); + if (!AllBlocks.SCHEMATIC_TABLE.typeOf(blockState)) + return; + + // Delete schematic with same name Files.deleteIfExists(Paths.get(PATH, playerSchematicId)); + + // Too many Schematics + Stream list = Files.list(Paths.get(playerPath)); + if (list.count() >= MAX_SCHEMATICS_PER_PLAYER) { + Stream list2 = Files.list(Paths.get(playerPath)); + Optional lastFilePath = list2.filter(f -> !Files.isDirectory(f)) + .min(Comparator.comparingLong(f -> f.toFile().lastModified())); + list2.close(); + if (lastFilePath.isPresent()) { + Files.deleteIfExists(lastFilePath.get()); + } + } + list.close(); + + // Open Stream OutputStream writer = Files.newOutputStream(Paths.get(PATH, playerSchematicId), StandardOpenOption.CREATE_NEW); - Create.logger.info("Receiving New Schematic: " + playerSchematicId); - activeDownloads.put(playerSchematicId, writer); - activeTables.put(playerSchematicId, dimPos); + activeUploads.put(playerSchematicId, new SchematicUploadEntry(writer, size, dimPos)); - SchematicTableTileEntity tileEntity = (SchematicTableTileEntity) dimPos.world - .getTileEntity(dimPos.pos); - tileEntity.uploadingSchematic = schematic; - tileEntity.uploadingProgress = 0; - BlockState blockState = dimPos.world.getBlockState(dimPos.pos); - dimPos.world.notifyBlockUpdate(dimPos.pos, blockState, blockState, 6); - - if (player.openContainer instanceof SchematicTableContainer) { - SchematicTableContainer c = (SchematicTableContainer) player.openContainer; - c.sendSchematicUpdate = true; - c.detectAndSendChanges(); - } + // Notify Tile Entity + SchematicTableTileEntity tileEntity = (SchematicTableTileEntity) dimPos.world.getTileEntity(dimPos.pos); + tileEntity.startUpload(schematic); } catch (IOException e) { + Create.logger.error("Exception Thrown when starting Upload: " + playerSchematicId); e.printStackTrace(); } } public void handleWriteRequest(ServerPlayerEntity player, String schematic, byte[] data) { String playerSchematicId = player.getName().getFormattedText() + "/" + schematic; - if (activeDownloads.containsKey(playerSchematicId)) { + + if (activeUploads.containsKey(playerSchematicId)) { + SchematicUploadEntry entry = activeUploads.get(playerSchematicId); + entry.bytesUploaded += data.length; + + // Size Validations + if (data.length > MAX_PACKET_SIZE) { + Create.logger.warn("Oversized Upload Packet received: " + playerSchematicId); + cancelUpload(playerSchematicId); + return; + } + + if (entry.bytesUploaded > entry.totalBytes) { + Create.logger.warn("Received more data than Expected: " + playerSchematicId); + cancelUpload(playerSchematicId); + return; + } try { - activeDownloads.get(playerSchematicId).write(data); - Create.logger.info("Writing to Schematic: " + playerSchematicId); + entry.stream.write(data); + entry.idleTime = 0; + BlockState blockState = entry.tablePos.world.getBlockState(entry.tablePos.pos); + if (!AllBlocks.SCHEMATIC_TABLE.typeOf(blockState)) + return; + + SchematicTableTileEntity tileEntity = (SchematicTableTileEntity) entry.tablePos.world + .getTileEntity(entry.tablePos.pos); + tileEntity.uploadingProgress = (float) ((double) entry.bytesUploaded / entry.totalBytes); + tileEntity.sendUpdate = true; + } catch (IOException e) { + Create.logger.error("Exception Thrown when uploading Schematic: " + playerSchematicId); e.printStackTrace(); - activeDownloads.remove(playerSchematicId); - activeTables.remove(playerSchematicId); + cancelUpload(playerSchematicId); } } } + protected void cancelUpload(String playerSchematicId) { + if (!activeUploads.containsKey(playerSchematicId)) + return; + + SchematicUploadEntry entry = activeUploads.remove(playerSchematicId); + try { + entry.stream.close(); + Files.deleteIfExists(Paths.get(PATH, playerSchematicId)); + Create.logger.warn("Cancelled Schematic Upload: " + playerSchematicId); + + } catch (IOException e) { + Create.logger.error("Exception Thrown when cancelling Upload: " + playerSchematicId); + e.printStackTrace(); + } + + DimensionPos dimpos = entry.tablePos; + if (dimpos == null) + return; + + BlockState blockState = dimpos.world.getBlockState(dimpos.pos); + if (!AllBlocks.SCHEMATIC_TABLE.typeOf(blockState)) + return; + + SchematicTableTileEntity tileEntity = (SchematicTableTileEntity) dimpos.world.getTileEntity(dimpos.pos); + tileEntity.finishUpload(); + } + public void handleFinishedUpload(ServerPlayerEntity player, String schematic) { String playerSchematicId = player.getName().getFormattedText() + "/" + schematic; - if (activeDownloads.containsKey(playerSchematicId)) { + if (activeUploads.containsKey(playerSchematicId)) { try { - activeDownloads.get(playerSchematicId).close(); - activeDownloads.remove(playerSchematicId); - Create.logger.info("Finished receiving Schematic: " + playerSchematicId); - - DimensionPos dimpos = activeTables.remove(playerSchematicId); + activeUploads.get(playerSchematicId).stream.close(); + DimensionPos dimpos = activeUploads.remove(playerSchematicId).tablePos; + Create.logger.info("New Schematic Uploaded: " + playerSchematicId); if (dimpos == null) return; @@ -100,21 +221,13 @@ public class ServerSchematicLoader { return; SchematicTableTileEntity tileEntity = (SchematicTableTileEntity) dimpos.world.getTileEntity(dimpos.pos); - + tileEntity.finishUpload(); tileEntity.inventory.setStackInSlot(0, ItemStack.EMPTY); tileEntity.inventory.setStackInSlot(1, ItemBlueprint.create(schematic, player.getName().getFormattedText())); - tileEntity.uploadingSchematic = null; - tileEntity.uploadingProgress = 0; - dimpos.world.notifyBlockUpdate(dimpos.pos, blockState, blockState, 3); - - if (player.openContainer instanceof SchematicTableContainer) { - SchematicTableContainer c = (SchematicTableContainer) player.openContainer; - c.sendSchematicUpdate = true; - c.detectAndSendChanges(); - } } catch (IOException e) { + Create.logger.error("Exception Thrown when finishing Upload: " + playerSchematicId); e.printStackTrace(); } } diff --git a/src/main/java/com/simibubi/create/block/SchematicTableContainer.java b/src/main/java/com/simibubi/create/block/SchematicTableContainer.java index 75eb6d45a..23b004a84 100644 --- a/src/main/java/com/simibubi/create/block/SchematicTableContainer.java +++ b/src/main/java/com/simibubi/create/block/SchematicTableContainer.java @@ -2,21 +2,15 @@ package com.simibubi.create.block; import com.simibubi.create.AllContainers; import com.simibubi.create.AllItems; -import com.simibubi.create.networking.PacketSchematicTableContainer; -import com.simibubi.create.networking.Packets; import net.minecraft.client.Minecraft; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.container.Container; import net.minecraft.inventory.container.Slot; import net.minecraft.item.ItemStack; import net.minecraft.network.PacketBuffer; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.fml.network.PacketDistributor; import net.minecraftforge.items.SlotItemHandler; public class SchematicTableContainer extends Container { @@ -26,11 +20,6 @@ public class SchematicTableContainer extends Container { private Slot outputSlot; private PlayerEntity player; - public String schematicUploading; - public boolean isUploading; - public float progress; - public boolean sendSchematicUpdate; - public SchematicTableContainer(int id, PlayerInventory inv, PacketBuffer extraData) { super(AllContainers.SchematicTable.type, id); player = inv.player; @@ -68,32 +57,17 @@ public class SchematicTableContainer extends Container { // player Slots for (int row = 0; row < 3; ++row) { for (int col = 0; col < 9; ++col) { - this.addSlot(new Slot(player.inventory, col + row * 9 + 9, -8 + col * 18, 102 + row * 18)); + this.addSlot(new Slot(player.inventory, col + row * 9 + 9, 12 + col * 18, 102 + row * 18)); } } for (int hotbarSlot = 0; hotbarSlot < 9; ++hotbarSlot) { - this.addSlot(new Slot(player.inventory, hotbarSlot, -8 + hotbarSlot * 18, 160)); + this.addSlot(new Slot(player.inventory, hotbarSlot, 12 + hotbarSlot * 18, 160)); } detectAndSendChanges(); } - @Override - public void detectAndSendChanges() { - if (te.uploadingSchematic != null) { - schematicUploading = te.uploadingSchematic; - progress = te.uploadingProgress; - isUploading = true; - } else { - schematicUploading = null; - progress = 0; - isUploading = false; - } - super.detectAndSendChanges(); - sendSchematicInfo(); - } - public boolean canWrite() { return inputSlot.getHasStack() && !outputSlot.getHasStack(); } @@ -110,54 +84,14 @@ public class SchematicTableContainer extends Container { return ItemStack.EMPTY; ItemStack stack = clickedSlot.getStack(); - if (clickedSlot == inputSlot || clickedSlot == outputSlot) { - int indexToPut = playerIn.inventory.getFirstEmptyStack(); - - if (indexToPut == -1) - return ItemStack.EMPTY; - - playerIn.inventory.setInventorySlotContents(indexToPut, stack); - clickedSlot.putStack(ItemStack.EMPTY); - return ItemStack.EMPTY; - } - - if (AllItems.EMPTY_BLUEPRINT.typeOf(stack) && !inputSlot.getHasStack()) { - clickedSlot.putStack(ItemStack.EMPTY); - inputSlot.putStack(stack); - } - + if (index < 2) + mergeItemStack(stack, 2, inventorySlots.size(), false); + else + mergeItemStack(stack, 0, 1, false); + return ItemStack.EMPTY; } - public void sendSchematicInfo() { - if (player instanceof ServerPlayerEntity) { - if (sendSchematicUpdate) { - Packets.channel.send(PacketDistributor.PLAYER.with(() -> (ServerPlayerEntity) player), - new PacketSchematicTableContainer(schematicUploading, progress)); - sendSchematicUpdate = false; - } - } - } - - @OnlyIn(Dist.CLIENT) - public void receiveSchematicInfo(String schematic, float progress) { - if (schematic.isEmpty()) { - this.schematicUploading = null; - this.isUploading = false; - this.progress = 0; - return; - } - - this.isUploading = true; - this.schematicUploading = schematic; - this.progress = .5f; - } - - @Override - public void onContainerClosed(PlayerEntity playerIn) { - super.onContainerClosed(playerIn); - } - public SchematicTableTileEntity getTileEntity() { return te; } diff --git a/src/main/java/com/simibubi/create/block/SchematicTableTileEntity.java b/src/main/java/com/simibubi/create/block/SchematicTableTileEntity.java index d2726a1df..8e50a4a7c 100644 --- a/src/main/java/com/simibubi/create/block/SchematicTableTileEntity.java +++ b/src/main/java/com/simibubi/create/block/SchematicTableTileEntity.java @@ -18,63 +18,97 @@ import net.minecraftforge.items.ItemStackHandler; public class SchematicTableTileEntity extends TileEntitySynced implements ITickableTileEntity, INamedContainerProvider { public SchematicTableInventory inventory; + public boolean isUploading; public String uploadingSchematic; public float uploadingProgress; - + public boolean sendUpdate; + public class SchematicTableInventory extends ItemStackHandler { public SchematicTableInventory() { super(2); } - + @Override protected void onContentsChanged(int slot) { super.onContentsChanged(slot); markDirty(); } } - + public SchematicTableTileEntity() { this(AllTileEntities.SchematicTable.type); } - + public SchematicTableTileEntity(TileEntityType tileEntityTypeIn) { super(tileEntityTypeIn); inventory = new SchematicTableInventory(); uploadingSchematic = null; uploadingProgress = 0; } - + public void sendToContainer(PacketBuffer buffer) { buffer.writeBlockPos(getPos()); buffer.writeCompoundTag(getUpdateTag()); } - + @Override public void read(CompoundNBT compound) { inventory.deserializeNBT(compound.getCompound("Inventory")); - if (compound.contains("Schematic")) { + readClientUpdate(compound); + super.read(compound); + } + + @Override + public void readClientUpdate(CompoundNBT compound) { + if (compound.contains("Uploading")) { + isUploading = true; uploadingSchematic = compound.getString("Schematic"); uploadingProgress = compound.getFloat("Progress"); } else { + isUploading = false; uploadingSchematic = null; uploadingProgress = 0; } - super.read(compound); } - + @Override public CompoundNBT write(CompoundNBT compound) { compound.put("Inventory", inventory.serializeNBT()); - if (uploadingSchematic != null) { - compound.putString("Schematic", uploadingSchematic); - compound.putFloat("Progress", uploadingProgress); - } + writeToClient(compound); return super.write(compound); } + @Override + public CompoundNBT writeToClient(CompoundNBT compound) { + if (isUploading) { + compound.putBoolean("Uploading", true); + compound.putString("Schematic", uploadingSchematic); + compound.putFloat("Progress", uploadingProgress); + } + return compound; + } + @Override public void tick() { - + // Update Client Tile + if (sendUpdate) { + sendUpdate = false; + world.notifyBlockUpdate(pos, getBlockState(), getBlockState(), 6); + } + } + + public void startUpload(String schematic) { + isUploading = true; + uploadingProgress = 0; + uploadingSchematic = schematic; + sendUpdate = true; + } + + public void finishUpload() { + isUploading = false; + uploadingProgress = 0; + uploadingSchematic = null; + sendUpdate = true; } @Override diff --git a/src/main/java/com/simibubi/create/block/SchematicannonRenderer.java b/src/main/java/com/simibubi/create/block/SchematicannonRenderer.java index b8bbb5321..e48d5c136 100644 --- a/src/main/java/com/simibubi/create/block/SchematicannonRenderer.java +++ b/src/main/java/com/simibubi/create/block/SchematicannonRenderer.java @@ -1,11 +1,14 @@ package com.simibubi.create.block; +import java.util.Random; + import com.mojang.blaze3d.platform.GlStateManager; import com.simibubi.create.AllBlocks; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.AtlasTexture; import net.minecraft.client.renderer.tileentity.TileEntityRenderer; +import net.minecraft.particles.ParticleTypes; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -50,7 +53,7 @@ public class SchematicannonRenderer extends TileEntityRenderer block.totalTicks - 10) { recoil = Math.max(recoil, (block.ticksRemaining + 1 - partialTicks) - block.totalTicks + 10); } + + // Render particles for launch + if (block.ticksRemaining == block.totalTicks && tileEntityIn.firstRenderTick) { + tileEntityIn.firstRenderTick = false; + for (int i = 0; i < 10; i++) { + Random r = tileEntityIn.getWorld().getRandom(); + double sX = cannonOffset.x * .01f; + double sY = (cannonOffset.y + 1) * .01f; + double sZ = cannonOffset.z * .01f; + double rX = r.nextFloat() - sX * 40; + double rY = r.nextFloat() - sY * 40; + double rZ = r.nextFloat() - sZ * 40; + tileEntityIn.getWorld().addParticle(ParticleTypes.CLOUD, start.x + rX, start.y + rY, start.z + rZ, + sX, sY, sZ); + } + } + } } diff --git a/src/main/java/com/simibubi/create/block/SchematicannonTileEntity.java b/src/main/java/com/simibubi/create/block/SchematicannonTileEntity.java index 6b358c201..25c753371 100644 --- a/src/main/java/com/simibubi/create/block/SchematicannonTileEntity.java +++ b/src/main/java/com/simibubi/create/block/SchematicannonTileEntity.java @@ -9,12 +9,12 @@ import com.simibubi.create.AllItems; import com.simibubi.create.AllTileEntities; import com.simibubi.create.item.ItemBlueprint; import com.simibubi.create.schematic.Cuboid; +import com.simibubi.create.schematic.MaterialChecklist; import com.simibubi.create.schematic.SchematicWorld; import com.simibubi.create.utility.TileEntitySynced; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; -import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.container.Container; @@ -35,10 +35,8 @@ import net.minecraft.util.SoundEvents; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.StringTextComponent; -import net.minecraft.world.Explosion; import net.minecraft.world.gen.feature.template.Template; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -49,9 +47,12 @@ import net.minecraftforge.items.ItemStackHandler; public class SchematicannonTileEntity extends TileEntitySynced implements ITickableTileEntity, INamedContainerProvider { + public static final int NEIGHBOUR_CHECKING = 100; public static final int PLACEMENT_DELAY = 10; public static final float FUEL_PER_GUNPOWDER = .2f; - public static final float FUEL_USAGE_RATE = .0001f; + public static final float FUEL_USAGE_RATE = .0005f; + public static final int SKIPS_PER_TICK = 10; + public static final int MAX_ANCHOR_DISTANCE = 256; public enum State { STOPPED, PAUSED, RUNNING; @@ -62,6 +63,8 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka // Sync public boolean sendUpdate; + public boolean dontUpdateChecklist; + public int neighbourCheckCooldown; // Printer private SchematicWorld blockReader; @@ -69,26 +72,35 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka public BlockPos schematicAnchor; public boolean schematicLoaded; public boolean missingBlock; + public boolean blockNotLoaded; public boolean hasCreativeCrate; private int printerCooldown; + private int skipsLeft; + private boolean blockSkipped; public BlockPos target; public BlockPos previousTarget; public List attachedInventories; public List flyingBlocks; + public MaterialChecklist checklist; // Gui information public float fuelLevel; - public float paperPrintingProgress; + public float bookPrintingProgress; public float schematicProgress; public String statusMsg; public State state; + public int blocksPlaced; + public int blocksToPlace; // Settings public int replaceMode; public boolean skipMissing; public boolean replaceTileEntities; + // Render + public boolean firstRenderTick; + public class SchematicannonInventory extends ItemStackHandler { public SchematicannonInventory() { super(5); @@ -107,8 +119,8 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka return AllItems.BLUEPRINT.typeOf(stack); case 1: // Blueprint output return false; - case 2: // Paper input - return stack.getItem() == Items.PAPER; + case 2: // Book input + return stack.getItem() == Items.BOOK || stack.getItem() == Items.WRITTEN_BOOK; case 3: // Material List output return false; case 4: // Gunpowder @@ -168,12 +180,18 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka statusMsg = "Idle"; state = State.STOPPED; replaceMode = 2; + neighbourCheckCooldown = NEIGHBOUR_CHECKING; + checklist = new MaterialChecklist(); } public void findInventories() { hasCreativeCrate = false; + attachedInventories.clear(); for (Direction facing : Direction.values()) { + if (!world.isBlockPresent(pos.offset(facing))) + continue; + if (AllBlocks.CREATIVE_CRATE.typeOf(world.getBlockState(pos.offset(facing)))) { hasCreativeCrate = true; } @@ -192,19 +210,13 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka @Override public void read(CompoundNBT compound) { inventory.deserializeNBT(compound.getCompound("Inventory")); - + if (compound.contains("Running")) currentPos = NBTUtil.readBlockPos(compound.getCompound("CurrentPos")); - + readClientUpdate(compound); super.read(compound); } - - @Override - public void onLoad() { - findInventories(); - super.onLoad(); - } @Override public void readClientUpdate(CompoundNBT compound) { @@ -212,9 +224,11 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka // Gui information statusMsg = compound.getString("Status"); schematicProgress = compound.getFloat("Progress"); - paperPrintingProgress = compound.getFloat("PaperProgress"); + bookPrintingProgress = compound.getFloat("PaperProgress"); fuelLevel = compound.getFloat("Fuel"); state = State.valueOf(compound.getString("State")); + blocksPlaced = compound.getInt("AmountPlaced"); + blocksToPlace = compound.getInt("AmountToPlace"); // Settings CompoundNBT options = compound.getCompound("Options"); @@ -271,12 +285,12 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka @Override public CompoundNBT write(CompoundNBT compound) { compound.put("Inventory", inventory.serializeNBT()); - + if (state == State.RUNNING) { compound.putBoolean("Running", true); compound.put("CurrentPos", NBTUtil.writeBlockPos(currentPos)); } - + writeToClient(compound); return super.write(compound); } @@ -286,10 +300,12 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka // Gui information compound.putFloat("Progress", schematicProgress); - compound.putFloat("PaperProgress", paperPrintingProgress); + compound.putFloat("PaperProgress", bookPrintingProgress); compound.putFloat("Fuel", fuelLevel); compound.putString("Status", statusMsg); compound.putString("State", state.name()); + compound.putInt("AmountPlaced", blocksPlaced); + compound.putInt("AmountToPlace", blocksToPlace); // Settings CompoundNBT options = new CompoundNBT(); @@ -317,6 +333,12 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka @Override public void tick() { + if (neighbourCheckCooldown-- <= 0) { + neighbourCheckCooldown = NEIGHBOUR_CHECKING; + findInventories(); + } + + firstRenderTick = true; previousTarget = target; tickFlyingBlocks(); @@ -328,7 +350,15 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka refillFuelIfPossible(); // Update Printer - tickPrinter(); + skipsLeft = SKIPS_PER_TICK; + blockSkipped = true; + + while (blockSkipped && skipsLeft-- > 0) + tickPrinter(); + + schematicProgress = 0; + if (blocksToPlace > 0) + schematicProgress = (float) blocksPlaced / blocksToPlace; // Update Client Tile if (sendUpdate) { @@ -339,6 +369,7 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka protected void tickPrinter() { ItemStack blueprint = inventory.getStackInSlot(0); + blockSkipped = false; // Skip if not Active if (state == State.STOPPED) { @@ -346,8 +377,6 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka resetPrinter(); return; } - if (state == State.PAUSED && !missingBlock && fuelLevel > FUEL_USAGE_RATE) - return; if (blueprint.isEmpty()) { state = State.STOPPED; @@ -356,31 +385,12 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka return; } + if (state == State.PAUSED && !blockNotLoaded && !missingBlock && fuelLevel > FUEL_USAGE_RATE) + return; + // Initialize Printer if (!schematicLoaded) { - if (!blueprint.hasTag()) { - state = State.STOPPED; - statusMsg = "Invalid Blueprint"; - sendUpdate = true; - return; - } - - if (!blueprint.getTag().getBoolean("Deployed")) { - state = State.STOPPED; - statusMsg = "Blueprint not Deployed"; - sendUpdate = true; - return; - } - - currentPos = currentPos != null ? currentPos.west() : BlockPos.ZERO.west(); - schematicAnchor = NBTUtil.readBlockPos(blueprint.getTag().getCompound("Anchor")); - - // Load blocks into reader - Template activeTemplate = ItemBlueprint.getSchematic(blueprint); - blockReader = new SchematicWorld(new HashMap<>(), new Cuboid(), schematicAnchor); - activeTemplate.addBlocksToWorld(blockReader, schematicAnchor, ItemBlueprint.getSettings(blueprint)); - schematicLoaded = true; - sendUpdate = true; + initializePrinter(blueprint); return; } @@ -400,7 +410,13 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka } // Update Target - if (!missingBlock) { + if (hasCreativeCrate) { + if (missingBlock) { + missingBlock = false; + state = State.RUNNING; + } + } + if (!missingBlock && !blockNotLoaded) { advanceCurrentPos(); // End reached @@ -412,15 +428,31 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka } // Check block - BlockState blockState = blockReader.getBlockState(target); - if (!shouldPlace(target, blockState)) + if (!getWorld().isAreaLoaded(target, 0)) { + blockNotLoaded = true; + statusMsg = "Block is not loaded"; + state = State.PAUSED; return; + } else { + if (blockNotLoaded) { + blockNotLoaded = false; + state = State.RUNNING; + } + } + + BlockState blockState = blockReader.getBlockState(target); + if (!shouldPlace(target, blockState)) { + statusMsg = "Searching"; + blockSkipped = true; + return; + } // Find Item ItemStack requiredItem = getItemForBlock(blockState); if (!findItemInAttachedInventories(requiredItem)) { if (skipMissing) { statusMsg = "Skipping"; + blockSkipped = true; if (missingBlock) { missingBlock = false; state = State.RUNNING; @@ -436,7 +468,10 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka // Success state = State.RUNNING; - statusMsg = "Running..."; + if (blockState.getBlock() != Blocks.AIR) + statusMsg = "Placing: " + blocksPlaced + " / " + blocksToPlace; + else + statusMsg = "Clearing Blocks"; launchBlock(target, blockState); printerCooldown = PLACEMENT_DELAY; fuelLevel -= FUEL_USAGE_RATE; @@ -444,6 +479,51 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka missingBlock = false; } + protected void initializePrinter(ItemStack blueprint) { + if (!blueprint.hasTag()) { + state = State.STOPPED; + statusMsg = "Invalid Blueprint"; + sendUpdate = true; + return; + } + + if (!blueprint.getTag().getBoolean("Deployed")) { + state = State.STOPPED; + statusMsg = "Blueprint not Deployed"; + sendUpdate = true; + return; + } + + // Load blocks into reader + Template activeTemplate = ItemBlueprint.getSchematic(blueprint); + BlockPos anchor = NBTUtil.readBlockPos(blueprint.getTag().getCompound("Anchor")); + + if (activeTemplate.getSize().equals(BlockPos.ZERO)) { + state = State.STOPPED; + statusMsg = "Schematic File Expired"; + inventory.setStackInSlot(0, ItemStack.EMPTY); + inventory.setStackInSlot(1, new ItemStack(AllItems.EMPTY_BLUEPRINT.get())); + return; + } + + if (!anchor.withinDistance(getPos(), MAX_ANCHOR_DISTANCE)) { + state = State.STOPPED; + statusMsg = "Target too Far Away"; + return; + } + + schematicAnchor = anchor; + blockReader = new SchematicWorld(new HashMap<>(), new Cuboid(), schematicAnchor); + activeTemplate.addBlocksToWorld(blockReader, schematicAnchor, ItemBlueprint.getSettings(blueprint)); + schematicLoaded = true; + state = State.PAUSED; + statusMsg = "Ready"; + updateChecklist(); + sendUpdate = true; + blocksToPlace += blocksPlaced; + currentPos = currentPos != null ? currentPos.west() : blockReader.getBounds().getOrigin().west(); + } + protected ItemStack getItemForBlock(BlockState blockState) { return new ItemStack(BlockItem.BLOCK_TO_ITEM.getOrDefault(blockState.getBlock(), Items.AIR)); } @@ -467,18 +547,18 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka protected void advanceCurrentPos() { BlockPos size = blockReader.getBounds().getSize(); currentPos = currentPos.offset(Direction.EAST); + BlockPos posInBounds = currentPos.subtract(blockReader.getBounds().getOrigin()); - schematicProgress += 1d / (size.getX() * size.getY() * size.getZ()); - - if (currentPos.getX() > size.getX()) - currentPos = new BlockPos(0, currentPos.getY(), currentPos.getZ() + 1); - if (currentPos.getZ() > size.getZ()) - currentPos = new BlockPos(currentPos.getX(), currentPos.getY() + 1, 0); + if (posInBounds.getX() > size.getX()) + currentPos = new BlockPos(blockReader.getBounds().x, currentPos.getY(), currentPos.getZ() + 1).west(); + if (posInBounds.getZ() > size.getZ()) + currentPos = new BlockPos(currentPos.getX(), currentPos.getY() + 1, blockReader.getBounds().z).west(); // End reached if (currentPos.getY() > size.getY()) { inventory.setStackInSlot(0, ItemStack.EMPTY); - inventory.setStackInSlot(1, new ItemStack(AllItems.EMPTY_BLUEPRINT.get())); + inventory.setStackInSlot(1, + new ItemStack(AllItems.EMPTY_BLUEPRINT.get(), inventory.getStackInSlot(1).getCount() + 1)); state = State.STOPPED; statusMsg = "Finished"; resetPrinter(); @@ -498,6 +578,8 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka missingBlock = false; sendUpdate = true; schematicProgress = 0; + blocksPlaced = 0; + blocksToPlace = 0; } protected boolean shouldPlace(BlockPos pos, BlockState state) { @@ -548,37 +630,52 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka } protected void tickPaperPrinter() { - int PaperInput = 2; - int PaperOutput = 3; + int BookInput = 2; + int BookOutput = 3; - ItemStack paper = inventory.extractItem(PaperInput, 1, true); - boolean outputFull = inventory.getStackInSlot(PaperOutput).getCount() == inventory.getSlotLimit(PaperOutput); + ItemStack blueprint = inventory.getStackInSlot(0); + ItemStack paper = inventory.extractItem(BookInput, 1, true); + boolean outputFull = inventory.getStackInSlot(BookOutput).getCount() == inventory.getSlotLimit(BookOutput); if (paper.isEmpty() || outputFull) { - if (paperPrintingProgress != 0) + if (bookPrintingProgress != 0) sendUpdate = true; - paperPrintingProgress = 0; + bookPrintingProgress = 0; + dontUpdateChecklist = false; return; } - if (paperPrintingProgress >= 1) { - paperPrintingProgress = 0; - inventory.extractItem(PaperInput, 1, false); - inventory.setStackInSlot(PaperOutput, - new ItemStack(Items.PAPER, inventory.getStackInSlot(PaperOutput).getCount() + 1)); + if (!schematicLoaded) { + if (!blueprint.isEmpty()) + initializePrinter(blueprint); + return; + } + + if (bookPrintingProgress >= 1) { + bookPrintingProgress = 0; + + if (!dontUpdateChecklist) + updateChecklist(); + + dontUpdateChecklist = true; + inventory.extractItem(BookInput, 1, false); + ItemStack stack = checklist.createItem(); + stack.setCount(inventory.getStackInSlot(BookOutput).getCount() + 1); + inventory.setStackInSlot(BookOutput, stack); sendUpdate = true; return; } - paperPrintingProgress += 0.05f; + bookPrintingProgress += 0.05f; sendUpdate = true; } protected void launchBlock(BlockPos target, BlockState state) { + if (state.getBlock() != Blocks.AIR) + blocksPlaced++; flyingBlocks.add(new LaunchedBlock(target, state)); - Vec3d explosionPos = new Vec3d(pos).add(new Vec3d(target.subtract(pos)).normalize()); - this.world.createExplosion((Entity) null, explosionPos.x, explosionPos.y + 1.5f, explosionPos.z, 0, - Explosion.Mode.NONE); + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.BLOCKS, + .1f, 1.1f); } public void sendToContainer(PacketBuffer buffer) { @@ -596,4 +693,36 @@ public class SchematicannonTileEntity extends TileEntitySynced implements ITicka return new StringTextComponent(getType().getRegistryName().toString()); } + public void updateChecklist() { + checklist.required.clear(); + checklist.blocksNotLoaded = false; + + if (schematicLoaded) { + blocksToPlace = blocksPlaced; + for (BlockPos pos : blockReader.getAllPositions()) { + BlockState required = blockReader.getBlockState(pos.add(schematicAnchor)); + + if (!getWorld().isAreaLoaded(pos.add(schematicAnchor), 0)) { + checklist.warnBlockNotLoaded(); + continue; + } + if (!shouldPlace(pos.add(schematicAnchor), required)) + continue; + ItemStack requiredItem = getItemForBlock(required); + checklist.require(requiredItem.getItem()); + blocksToPlace++; + } + } + checklist.gathered.clear(); + for (IItemHandler inventory : attachedInventories) { + for (int slot = 0; slot < inventory.getSlots(); slot++) { + ItemStack stackInSlot = inventory.getStackInSlot(slot); + if (inventory.extractItem(slot, 1, true).isEmpty()) + continue; + checklist.collect(stackInSlot); + } + } + sendUpdate = true; + } + } diff --git a/src/main/java/com/simibubi/create/gui/GuiResources.java b/src/main/java/com/simibubi/create/gui/GuiResources.java index f78670bb3..8f42dabac 100644 --- a/src/main/java/com/simibubi/create/gui/GuiResources.java +++ b/src/main/java/com/simibubi/create/gui/GuiResources.java @@ -45,8 +45,8 @@ public enum GuiResources { ICON_TARGET("icons.png", 48, 0, 16, 16), ICON_CONFIRM("icons.png", 0, 16, 16, 16), - ICON_NORMAL_ROOF("icons.png", 32, 16, 16, 16), - ICON_FLAT_ROOF("icons.png", 48, 16, 16, 16), + ICON_OPEN_FOLDER("icons.png", 32, 16, 16, 16), + ICON_REFRESH("icons.png", 48, 16, 16, 16), ICON_DONT_REPLACE("icons.png", 0, 32, 16, 16), ICON_REPLACE_SOLID("icons.png", 16, 32, 16, 16), @@ -55,6 +55,7 @@ public enum GuiResources { ICON_TOOL_DEPLOY("icons.png", 0, 48, 16, 16), ICON_SKIP_MISSING("icons.png", 16, 48, 16, 16), + ICON_SKIP_TILES("icons.png", 32, 48, 16, 16), ICON_TOOL_MOVE_XZ("icons.png", 0, 64, 16, 16), ICON_TOOL_MOVE_Y("icons.png", 16, 64, 16, 16), diff --git a/src/main/java/com/simibubi/create/gui/SchematicTableScreen.java b/src/main/java/com/simibubi/create/gui/SchematicTableScreen.java index c5c472b2d..63d91a901 100644 --- a/src/main/java/com/simibubi/create/gui/SchematicTableScreen.java +++ b/src/main/java/com/simibubi/create/gui/SchematicTableScreen.java @@ -1,12 +1,12 @@ package com.simibubi.create.gui; +import java.nio.file.Paths; import java.util.List; import com.mojang.blaze3d.platform.GlStateManager; import com.simibubi.create.AllBlocks; import com.simibubi.create.Create; import com.simibubi.create.block.SchematicTableContainer; -import com.simibubi.create.gui.widgets.AbstractSimiWidget; import com.simibubi.create.gui.widgets.DynamicLabel; import com.simibubi.create.gui.widgets.OptionScrollArea; import com.simibubi.create.gui.widgets.ScrollArea; @@ -14,29 +14,25 @@ import com.simibubi.create.gui.widgets.SimiButton; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.IHasContainer; -import net.minecraft.client.gui.screen.inventory.ContainerScreen; -import net.minecraft.client.gui.widget.Widget; import net.minecraft.client.renderer.RenderHelper; import net.minecraft.client.renderer.texture.AtlasTexture; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import net.minecraft.util.text.ITextComponent; -public class SchematicTableScreen extends ContainerScreen +public class SchematicTableScreen extends AbstractSimiContainerScreen implements IHasContainer { - private ScrollArea schematics; - private SimiButton button; - private DynamicLabel label; + private ScrollArea schematicsArea; + private SimiButton confirmButton; + private SimiButton folderButton; + private SimiButton refreshButton; + private DynamicLabel schematicsLabel; private float progress; - private float lastProgress; - - private int xTopLeft; - private int yTopLeft; - - private int xMainWindow; - private int yMainWindow; + private float chasingProgress; + private float lastChasingProgress; public SchematicTableScreen(SchematicTableContainer container, PlayerInventory playerInventory, ITextComponent title) { @@ -45,63 +41,71 @@ public class SchematicTableScreen extends ContainerScreen availableSchematics = Create.cSchematicLoader.getAvailableSchematics(); if (!availableSchematics.isEmpty()) { - label = new DynamicLabel(xMainWindow + 36, yMainWindow + 26, "").withShadow(); - schematics = new OptionScrollArea(xMainWindow + 33, yMainWindow + 23, 134, 14) - .forOptions(availableSchematics).titled("Available Schematics").writingTo(label); - buttons.add(schematics); - buttons.add(label); - } else { + schematicsLabel = new DynamicLabel(mainLeft + 36, mainTop + 26, "").withShadow(); + schematicsArea = new OptionScrollArea(mainLeft + 33, mainTop + 23, 134, 14).forOptions(availableSchematics) + .titled("Available Schematics").writingTo(schematicsLabel); + widgets.add(schematicsArea); + widgets.add(schematicsLabel); + } - } - - button = new SimiButton(xMainWindow + 69, yMainWindow + 55, GuiResources.ICON_CONFIRM); - buttons.add(button); + confirmButton = new SimiButton(mainLeft + 69, mainTop + 55, GuiResources.ICON_CONFIRM); + folderButton = new SimiButton(mainLeft + 204, mainTop + 6, GuiResources.ICON_OPEN_FOLDER); + refreshButton = new SimiButton(mainLeft + 204, mainTop + 26, GuiResources.ICON_REFRESH); + widgets.add(confirmButton); + widgets.add(folderButton); + widgets.add(refreshButton); } @Override protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) { - int x = xTopLeft; - int y = yTopLeft; - - GuiResources.SCHEMATIC_TABLE.draw(this, xMainWindow, yMainWindow); - GuiResources.PLAYER_INVENTORY.draw(this, x, y + 20); - - if (container.isUploading) - font.drawString("Uploading...", xMainWindow + 76, yMainWindow + 10, GuiResources.FONT_COLOR); - else - font.drawString("Choose a Schematic", xMainWindow + 50, yMainWindow + 10, GuiResources.FONT_COLOR); - font.drawString("Inventory", x + 7, y + 26, 0x666666); - - if (schematics == null) { - font.drawStringWithShadow(" No Schematics Saved ", xMainWindow + 39, yMainWindow + 26, 0xFFDD44); - } + super.drawGuiContainerBackgroundLayer(partialTicks, mouseX, mouseY); + } @Override - public void render(int mouseX, int mouseY, float pt) { - renderBackground(); - super.render(mouseX, mouseY, pt); + protected void renderWindow(int mouseX, int mouseY, float partialTicks) { + + int x = guiLeft + 20; + int y = guiTop; + + int mainLeft = guiLeft - 56; + int mainTop = guiTop - 16; + GuiResources.PLAYER_INVENTORY.draw(this, x- 16, y + 70 + 14); + font.drawString("Inventory", x - 15 + 7, y + 64 + 26, 0x666666); + + GuiResources.SCHEMATIC_TABLE.draw(this, mainLeft, mainTop); + if (container.getTileEntity().isUploading) + font.drawString("Uploading...", mainLeft + 76, mainTop + 10, GuiResources.FONT_COLOR); + else if (container.getSlot(1).getHasStack()) + font.drawString("Upload Finished!", mainLeft + 60, mainTop + 10, GuiResources.FONT_COLOR); + else + font.drawString("Schematic Table", mainLeft + 60, mainTop + 10, GuiResources.FONT_COLOR); + + if (schematicsArea == null) { + font.drawStringWithShadow(" No Schematics Saved ", mainLeft + 39, mainTop + 26, 0xFFDD44); + } + minecraft.getTextureManager().bindTexture(GuiResources.SCHEMATIC_TABLE_PROGRESS.location); - int width = (int) (GuiResources.SCHEMATIC_TABLE_PROGRESS.width * MathHelper.lerp(pt, lastProgress, progress)); + int width = (int) (GuiResources.SCHEMATIC_TABLE_PROGRESS.width + * MathHelper.lerp(partialTicks, lastChasingProgress, chasingProgress)); int height = GuiResources.SCHEMATIC_TABLE_PROGRESS.height; GlStateManager.disableLighting(); - blit(xMainWindow + 94, yMainWindow + 56, GuiResources.SCHEMATIC_TABLE_PROGRESS.startX, + blit(mainLeft + 94, mainTop + 56, GuiResources.SCHEMATIC_TABLE_PROGRESS.startX, GuiResources.SCHEMATIC_TABLE_PROGRESS.startY, width, height); - GlStateManager.pushLightingAttributes(); GlStateManager.pushMatrix(); GlStateManager.enableBlend(); @@ -112,73 +116,81 @@ public class SchematicTableScreen extends ContainerScreen toolTip = ((AbstractSimiWidget) w).getToolTip(); - renderTooltip(toolTip, mouseX, mouseY); - } - } - } - - @Override - public boolean isPauseScreen() { - return false; } @Override public void tick() { super.tick(); - if (container.isUploading) { - lastProgress = progress; - progress = Create.cSchematicLoader.getProgress(container.schematicUploading); - label.colored(0xCCDDFF); - button.active = false; - schematics.visible = false; + boolean finished = container.getSlot(1).getHasStack(); + + if (container.getTileEntity().isUploading || finished) { + if (finished) { + chasingProgress = lastChasingProgress = progress = 1; + } else { + lastChasingProgress = chasingProgress; + progress = container.getTileEntity().uploadingProgress; + chasingProgress += (progress - chasingProgress) * .5f; + } + confirmButton.active = false; + + if (schematicsLabel != null) { + schematicsLabel.colored(0xCCDDFF); + schematicsLabel.text = container.getTileEntity().uploadingSchematic; + } + if (schematicsArea != null) + schematicsArea.visible = false; } else { progress = 0; - lastProgress = 0; - label.colored(0xFFFFFF); - button.active = true; - schematics.visible = true; + chasingProgress = lastChasingProgress = 0; + confirmButton.active = true; + + if (schematicsLabel != null) + schematicsLabel.colored(0xFFFFFF); + if (schematicsArea != null) { + schematicsArea.writingTo(schematicsLabel); + schematicsArea.visible = true; + } } } @Override public boolean mouseClicked(double p_mouseClicked_1_, double p_mouseClicked_3_, int p_mouseClicked_5_) { - if (button.active && button.isHovered() && ((SchematicTableContainer) container).canWrite() && schematics != null) { - - lastProgress = progress = 0; + if (confirmButton.active && confirmButton.isHovered() && ((SchematicTableContainer) container).canWrite() + && schematicsArea != null) { + + lastChasingProgress = chasingProgress = progress = 0; List availableSchematics = Create.cSchematicLoader.getAvailableSchematics(); - String schematic = availableSchematics.get(schematics.getState()); + String schematic = availableSchematics.get(schematicsArea.getState()); Create.cSchematicLoader.startNewUpload(schematic); } + + if (folderButton.isHovered()) { + Util.getOSType().openFile(Paths.get("schematics/").toFile()); + } + + if (refreshButton.isHovered()) { + Create.cSchematicLoader.refresh(); + List availableSchematics = Create.cSchematicLoader.getAvailableSchematics(); + widgets.remove(schematicsArea); + schematicsArea = new OptionScrollArea(guiLeft - 56 + 33, guiTop - 16 + 23, 134, 14).forOptions(availableSchematics) + .titled("Available Schematics").writingTo(schematicsLabel); + widgets.add(schematicsArea); + } return super.mouseClicked(p_mouseClicked_1_, p_mouseClicked_3_, p_mouseClicked_5_); } - @Override - public boolean mouseScrolled(double p_mouseScrolled_1_, double p_mouseScrolled_3_, double p_mouseScrolled_5_) { - boolean b = false; - for (Widget w : buttons) { - if (w.mouseScrolled(p_mouseScrolled_1_, p_mouseScrolled_3_, p_mouseScrolled_5_)) - b = true; - } - return b || super.mouseScrolled(p_mouseScrolled_1_, p_mouseScrolled_3_, p_mouseScrolled_5_); - } - } diff --git a/src/main/java/com/simibubi/create/gui/SchematicannonScreen.java b/src/main/java/com/simibubi/create/gui/SchematicannonScreen.java index c7cd9c686..fa8f99263 100644 --- a/src/main/java/com/simibubi/create/gui/SchematicannonScreen.java +++ b/src/main/java/com/simibubi/create/gui/SchematicannonScreen.java @@ -30,6 +30,8 @@ public class SchematicannonScreen extends AbstractSimiContainerScreen tip = skipTilesButton.getToolTip(); + tip.remove(1); + tip.add(TextFormatting.BLUE + + (skipTilesIndicator.state == State.ON ? "Currently Enabled" : "Currently Disabled")); + tip.add(TextFormatting.GRAY + "The Schematicannon will avoid replacing"); + tip.add(TextFormatting.GRAY + "data holding blocks such as Chests."); + } if (replaceLevelButtons.get(0).isHovered()) { List tip = replaceLevelButtons.get(0).getToolTip(); tip.remove(1); @@ -199,7 +215,7 @@ public class SchematicannonScreen extends AbstractSimiContainerScreen () -> { - GuiOpener.open(new BlueprintEditScreen()); + displayBlueprintScreen(); }); return ActionResultType.SUCCESS; } @@ -116,11 +117,16 @@ public class ItemBlueprint extends Item { return super.onItemUse(context); } + @OnlyIn(value = Dist.CLIENT) + protected void displayBlueprintScreen() { + GuiOpener.open(new BlueprintEditScreen()); + } + @Override public ActionResult onItemRightClick(World worldIn, PlayerEntity playerIn, Hand handIn) { if (playerIn.isSneaking() && handIn == Hand.MAIN_HAND) { DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> { - GuiOpener.open(new BlueprintEditScreen()); + displayBlueprintScreen(); }); return new ActionResult(ActionResultType.SUCCESS, playerIn.getHeldItem(handIn)); } diff --git a/src/main/java/com/simibubi/create/networking/PacketConfigureSchematicannon.java b/src/main/java/com/simibubi/create/networking/PacketConfigureSchematicannon.java index 1388ab1ab..aa5590e9a 100644 --- a/src/main/java/com/simibubi/create/networking/PacketConfigureSchematicannon.java +++ b/src/main/java/com/simibubi/create/networking/PacketConfigureSchematicannon.java @@ -15,7 +15,7 @@ import net.minecraftforge.fml.network.NetworkEvent.Context; public class PacketConfigureSchematicannon { public static enum Option { - DONT_REPLACE, REPLACE_SOLID, REPLACE_ANY, REPLACE_EMPTY, SKIP_MISSING, PLAY, PAUSE, STOP; + DONT_REPLACE, REPLACE_SOLID, REPLACE_ANY, REPLACE_EMPTY, SKIP_MISSING, SKIP_TILES, PLAY, PAUSE, STOP; } private Option option; @@ -66,6 +66,9 @@ public class PacketConfigureSchematicannon { case SKIP_MISSING: te.skipMissing = set; break; + case SKIP_TILES: + te.replaceTileEntities = set; + break; case PLAY: te.state = State.RUNNING; diff --git a/src/main/java/com/simibubi/create/networking/PacketSchematicTableContainer.java b/src/main/java/com/simibubi/create/networking/PacketSchematicTableContainer.java deleted file mode 100644 index 5a5605be4..000000000 --- a/src/main/java/com/simibubi/create/networking/PacketSchematicTableContainer.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.simibubi.create.networking; - -import java.util.function.Supplier; - -import com.simibubi.create.block.SchematicTableContainer; - -import net.minecraft.client.Minecraft; -import net.minecraft.inventory.container.Container; -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.NetworkEvent.Context; - -public class PacketSchematicTableContainer { - - public String schematic; - public float progress; - - public PacketSchematicTableContainer(String schematicToUpload, float progress) { - this.schematic = schematicToUpload; - if (this.schematic == null) - this.schematic = ""; - this.progress = progress; - } - - public PacketSchematicTableContainer(PacketBuffer buffer) { - this.schematic = buffer.readString(); - this.progress = buffer.readFloat(); - } - - public void toBytes(PacketBuffer buffer) { - buffer.writeString(schematic); - buffer.writeFloat(progress); - } - - public void handle(Supplier context) { - context.get().enqueueWork(() -> { - Container c = Minecraft.getInstance().player.openContainer; - if (c != null && c instanceof SchematicTableContainer) { - ((SchematicTableContainer) c).receiveSchematicInfo(schematic, progress); - } - }); - - } - -} diff --git a/src/main/java/com/simibubi/create/networking/PacketSchematicUpload.java b/src/main/java/com/simibubi/create/networking/PacketSchematicUpload.java index 71c8a1f10..19fc3c109 100644 --- a/src/main/java/com/simibubi/create/networking/PacketSchematicUpload.java +++ b/src/main/java/com/simibubi/create/networking/PacketSchematicUpload.java @@ -18,6 +18,7 @@ public class PacketSchematicUpload { public static final int FINISH = 2; private int code; + private long size; private String schematic; private byte[] data; @@ -26,8 +27,9 @@ public class PacketSchematicUpload { this.schematic = schematic; } - public static PacketSchematicUpload begin(String schematic) { + public static PacketSchematicUpload begin(String schematic, long size) { PacketSchematicUpload pkt = new PacketSchematicUpload(BEGIN, schematic); + pkt.size = size; return pkt; } @@ -45,6 +47,8 @@ public class PacketSchematicUpload { code = buffer.readInt(); schematic = buffer.readString(256); + if (code == BEGIN) + size = buffer.readLong(); if (code == WRITE) data = buffer.readByteArray(); } @@ -53,6 +57,8 @@ public class PacketSchematicUpload { buffer.writeInt(code); buffer.writeString(schematic); + if (code == BEGIN) + buffer.writeLong(size); if (code == WRITE) buffer.writeByteArray(data); } @@ -62,7 +68,7 @@ public class PacketSchematicUpload { ServerPlayerEntity player = context.get().getSender(); if (code == BEGIN) { BlockPos pos = ((SchematicTableContainer) player.openContainer).getTileEntity().getPos(); - Create.sSchematicLoader.handleNewUpload(player, schematic, new DimensionPos(player, pos)); + Create.sSchematicLoader.handleNewUpload(player, schematic, size, new DimensionPos(player, pos)); } if (code == WRITE) { Create.sSchematicLoader.handleWriteRequest(player, schematic, data); diff --git a/src/main/java/com/simibubi/create/networking/Packets.java b/src/main/java/com/simibubi/create/networking/Packets.java index 8bae2a1bb..2fd1055ee 100644 --- a/src/main/java/com/simibubi/create/networking/Packets.java +++ b/src/main/java/com/simibubi/create/networking/Packets.java @@ -21,8 +21,6 @@ public class Packets { channel.registerMessage(i++, PacketNbt.class, PacketNbt::toBytes, PacketNbt::new, PacketNbt::handle); channel.registerMessage(i++, PacketConfigureSchematicannon.class, PacketConfigureSchematicannon::toBytes, PacketConfigureSchematicannon::new, PacketConfigureSchematicannon::handle); - channel.registerMessage(i++, PacketSchematicTableContainer.class, PacketSchematicTableContainer::toBytes, - PacketSchematicTableContainer::new, PacketSchematicTableContainer::handle); channel.registerMessage(i++, PacketSchematicUpload.class, PacketSchematicUpload::toBytes, PacketSchematicUpload::new, PacketSchematicUpload::handle); channel.registerMessage(i++, PacketSymmetryEffect.class, PacketSymmetryEffect::toBytes, diff --git a/src/main/java/com/simibubi/create/schematic/BlueprintHandler.java b/src/main/java/com/simibubi/create/schematic/BlueprintHandler.java index c1c6508d8..2bfc1190e 100644 --- a/src/main/java/com/simibubi/create/schematic/BlueprintHandler.java +++ b/src/main/java/com/simibubi/create/schematic/BlueprintHandler.java @@ -29,6 +29,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.StringTextComponent; import net.minecraft.world.gen.feature.template.PlacementSettings; import net.minecraft.world.gen.feature.template.Template; +import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.InputEvent.KeyInputEvent; import net.minecraftforge.client.event.InputEvent.MouseInputEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent; @@ -38,7 +39,7 @@ import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus; import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent; -@EventBusSubscriber(bus = Bus.FORGE) +@EventBusSubscriber(value = Dist.CLIENT, bus = Bus.FORGE) public class BlueprintHandler { public static BlueprintHandler instance; @@ -227,11 +228,15 @@ public class BlueprintHandler { Packets.channel.sendToServer(new PacketNbt(item, slot)); if (deployed) { + Template schematic = ItemBlueprint.getSchematic(item); + + if (schematic.getSize().equals(BlockPos.ZERO)) + return; + SchematicWorld w = new SchematicWorld(new HashMap<>(), new Cuboid(), anchor); PlacementSettings settings = cachedSettings.copy(); settings.setBoundingBox(null); - ItemBlueprint.getSchematic(item).addBlocksToWorld(w, anchor, settings); - + schematic.addBlocksToWorld(w, anchor, settings); new SchematicHologram().startHologram(w); } } diff --git a/src/main/java/com/simibubi/create/schematic/MaterialChecklist.java b/src/main/java/com/simibubi/create/schematic/MaterialChecklist.java new file mode 100644 index 000000000..084d2f486 --- /dev/null +++ b/src/main/java/com/simibubi/create/schematic/MaterialChecklist.java @@ -0,0 +1,135 @@ +package com.simibubi.create.schematic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.StringNBT; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.TranslationTextComponent; + +public class MaterialChecklist { + + public Map gathered; + public Map required; + public boolean blocksNotLoaded; + + public MaterialChecklist() { + required = new HashMap<>(); + gathered = new HashMap<>(); + } + + public void warnBlockNotLoaded() { + blocksNotLoaded = true; + } + + public void require(Item item) { + if (required.containsKey(item)) + required.put(item, required.get(item) + 1); + else + required.put(item, 1); + } + + public void collect(ItemStack stack) { + Item item = stack.getItem(); + if (required.containsKey(item)) + if (gathered.containsKey(item)) + gathered.put(item, gathered.get(item) + stack.getCount()); + else + gathered.put(item, stack.getCount()); + } + + public ItemStack createItem() { + ItemStack book = new ItemStack(Items.WRITTEN_BOOK); + + CompoundNBT tag = book.getOrCreateTag(); + ListNBT pages = new ListNBT(); + + int itemsWritten = 0; + StringBuilder string = new StringBuilder("{\"text\":\""); + + if (blocksNotLoaded) { + string.append("\n" + TextFormatting.RED + "* Disclaimer *\n\n"); + string.append("Material List may be inaccurate due to relevant chunks not being loaded."); + string.append("\"}"); + pages.add(new StringNBT(string.toString())); + string = new StringBuilder("{\"text\":\""); + } + + List keys = new ArrayList<>(required.keySet()); + Collections.sort(keys, (item1, item2) -> { + String name1 = new TranslationTextComponent(((Item) item1).getTranslationKey()).getFormattedText().toLowerCase(); + String name2 = new TranslationTextComponent(((Item) item2).getTranslationKey()).getFormattedText().toLowerCase(); + return name1.compareTo(name2); + }); + + List completed = new ArrayList<>(); + for (Item item : keys) { + int amount = required.get(item); + if (gathered.containsKey(item)) + amount -= gathered.get(item); + + if (amount <= 0) { + completed.add(item); + continue; + } + + if (itemsWritten == 6) { + itemsWritten = 0; + string.append("\"}"); + pages.add(new StringNBT(string.toString())); + string = new StringBuilder("{\"text\":\""); + } + + itemsWritten++; + string.append(unfinishedEntry(new ItemStack(item), amount)); + } + + for (Item item : completed) { + if (itemsWritten == 6) { + itemsWritten = 0; + string.append("\"}"); + pages.add(new StringNBT(string.toString())); + string = new StringBuilder("{\"text\":\""); + } + + itemsWritten++; + string.append(gatheredEntry(new ItemStack(item), required.get(item))); + } + + string.append("\"}"); + pages.add(new StringNBT(string.toString())); + + tag.put("pages", pages); + tag.putString("author", "Schematicannon"); + tag.putString("title", TextFormatting.BLUE + "Material Checklist"); + book.setTag(tag); + + return book; + } + + private String gatheredEntry(ItemStack item, int amount) { + int stacks = amount / 64; + int remainder = amount % 64; + ITextComponent tc = new TranslationTextComponent(item.getTranslationKey()); + return TextFormatting.DARK_GREEN + tc.getFormattedText() + + " \\u2714\n x" + amount + TextFormatting.GRAY + " | " + stacks + "\\u25A4 +" + remainder + "\n"; + } + + private String unfinishedEntry(ItemStack item, int amount) { + int stacks = amount / 64; + int remainder = amount % 64; + ITextComponent tc = new TranslationTextComponent(item.getTranslationKey()); + return TextFormatting.BLUE + tc.getFormattedText() + "\n x" + amount + + TextFormatting.GRAY + " | " + stacks + "\\u25A4 +" + remainder + "\n"; + } + +} diff --git a/src/main/resources/assets/create/textures/gui/icons.png b/src/main/resources/assets/create/textures/gui/icons.png index 9072b95f4..d2275b6d0 100644 Binary files a/src/main/resources/assets/create/textures/gui/icons.png and b/src/main/resources/assets/create/textures/gui/icons.png differ diff --git a/src/main/resources/assets/create/textures/gui/schematicannon.png b/src/main/resources/assets/create/textures/gui/schematicannon.png index 3ec65b3d0..2417f1f2a 100644 Binary files a/src/main/resources/assets/create/textures/gui/schematicannon.png and b/src/main/resources/assets/create/textures/gui/schematicannon.png differ