Skip to content

Commit

Permalink
Merge pull request #24 from Querz/import-chunks-offset
Browse files Browse the repository at this point in the history
Import chunks with an offset
  • Loading branch information
Querz authored Jul 15, 2019
2 parents 6dbdb4b + 7d93665 commit 018064e
Show file tree
Hide file tree
Showing 20 changed files with 444 additions and 132 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ If you would like to contribute a translation, you can find the language files i

## Download and installation

[**Download Version 1.7.3**](https://github.com/Querz/mcaselector/releases/download/1.7.3/mcaselector-1.7.3.jar)
[**Download Version 1.7.4**](https://github.com/Querz/mcaselector/releases/download/1.7.4/mcaselector-1.7.4.jar)

"Requirements":
* Either:
Expand All @@ -103,11 +103,11 @@ If you would like to contribute a translation, you can find the language files i

#### If you have Java from Oracle installed on your system:

Most likely, `.jar` files are associated with java on your computer, it should therefore launch by simply double clicking the file (or however your OS is configured to open files using your mouse or keyboard). If not, you can try `java -jar mcaselector-1.7.3.jar` from your console. If this doesn't work, you might want to look into how to modify the `PATH` variable on your system to tell your system that java is an executable program.
Most likely, `.jar` files are associated with java on your computer, it should therefore launch by simply double clicking the file (or however your OS is configured to open files using your mouse or keyboard). If not, you can try `java -jar mcaselector-1.7.4.jar` from your console. If this doesn't work, you might want to look into how to modify the `PATH` variable on your system to tell your system that java is an executable program.

#### If you have Minecraft Java Edition installed on your system:

Minecraft Java Edition comes with a JRE that you can use to start the MCA Selector, so there is no need to install another version of java on your system. On Windows, that java version is usually located in `C:\Program Files (x86)\Minecraft\runtime\jre-x64\bin\` and once inside this folder you can simply run `java.exe -jar <path-to-mcaselector-1.7.3.jar>`. On Mac OS you should find it in `Applications/Minecraft.app/Contents/runtime/jre-x64/1.8.0_74/bin` where you can execute `./java -jar <path-to-mcaselector-1.7.3.jar>`.
Minecraft Java Edition comes with a JRE that you can use to start the MCA Selector, so there is no need to install another version of java on your system. On Windows, that java version is usually located in `C:\Program Files (x86)\Minecraft\runtime\jre-x64\bin\` and once inside this folder you can simply run `java.exe -jar <path-to-mcaselector-1.7.4.jar>`. On Mac OS you should find it in `Applications/Minecraft.app/Contents/runtime/jre-x64/1.8.0_74/bin` where you can execute `./java -jar <path-to-mcaselector-1.7.4.jar>`.

#### If you are using OpenJDK:

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ apply plugin: 'idea'
apply plugin: 'css'

group 'net.querz.mcaselector'
version '1.7.3'
version '1.7.4'

sourceCompatibility = 1.8

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
import net.querz.mcaselector.util.Helper;
import net.querz.mcaselector.util.Point2i;
import net.querz.mcaselector.util.Timer;
import net.querz.mcaselector.util.Translation;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ChunkFilterDeleter {

Expand All @@ -23,7 +23,7 @@ private ChunkFilterDeleter() {}
public static void deleteFilter(GroupFilter filter, Map<Point2i, Set<Point2i>> selection, ProgressTask progressChannel) {
File[] files = Config.getWorldDir().listFiles((d, n) -> n.matches(Helper.MCA_FILE_PATTERN));
if (files == null || files.length == 0) {
progressChannel.done("no files");
progressChannel.done(Translation.DIALOG_PROGRESS_NO_FILES.toString());
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.querz.mcaselector.util.Helper;
import net.querz.mcaselector.util.Point2i;
import net.querz.mcaselector.util.Timer;
import net.querz.mcaselector.util.Translation;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.file.Files;
Expand All @@ -22,7 +23,7 @@ private ChunkFilterExporter() {}
public static void exportFilter(GroupFilter filter, Map<Point2i, Set<Point2i>> selection, File destination, ProgressTask progressChannel) {
File[] files = Config.getWorldDir().listFiles((d, n) -> n.matches(Helper.MCA_FILE_PATTERN));
if (files == null || files.length == 0) {
progressChannel.done("no files");
progressChannel.done(Translation.DIALOG_PROGRESS_NO_FILES.toString());
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import net.querz.mcaselector.util.Helper;
import net.querz.mcaselector.util.Point2i;
import net.querz.mcaselector.util.Timer;
import net.querz.mcaselector.util.Translation;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -22,7 +23,7 @@ private ChunkFilterSelector() {}
public static void selectFilter(GroupFilter filter, TileMap tileMap, ProgressTask progressChannel) {
File[] files = Config.getWorldDir().listFiles((d, n) -> n.matches(Helper.MCA_FILE_PATTERN));
if (files == null || files.length == 0) {
progressChannel.done("no files");
progressChannel.done(Translation.DIALOG_PROGRESS_NO_FILES.toString());
return;
}

Expand Down
201 changes: 149 additions & 52 deletions src/main/java/net/querz/mcaselector/io/ChunkImporter.java
Original file line number Diff line number Diff line change
@@ -1,135 +1,209 @@
package net.querz.mcaselector.io;

import net.querz.mcaselector.Config;
import net.querz.mcaselector.ui.ProgressTask;
import net.querz.mcaselector.util.Debug;
import net.querz.mcaselector.util.Helper;
import net.querz.mcaselector.util.Point2i;
import net.querz.mcaselector.util.Timer;
import net.querz.mcaselector.util.Translation;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ChunkImporter {

private ChunkImporter() {}

public static void importChunks(File importDir, ProgressTask progressChannel, boolean overwrite) {
File[] importFiles = importDir.listFiles((dir, name) -> name.matches(Helper.MCA_FILE_PATTERN));
if (importFiles == null || importFiles.length == 0) {
progressChannel.done("no files");
return;
}
public static void importChunks(File importDir, ProgressTask progressChannel, boolean overwrite, Point2i offset) {
try {
File[] importFiles = importDir.listFiles((dir, name) -> name.matches(Helper.MCA_FILE_PATTERN));
if (importFiles == null || importFiles.length == 0) {
progressChannel.done(Translation.DIALOG_PROGRESS_NO_FILES.toString());
return;
}

MCAFilePipe.clearQueues();

progressChannel.setMax(importFiles.length * (offset.getX() % 32 != 0 ? 2 : 1) * (offset.getY() % 32 != 0 ? 2 : 1));
progressChannel.infoProperty().setValue(Translation.DIALOG_PROGRESS_COLLECTING_DATA.toString());

Map<Point2i, Set<Point2i>> targetMapping = new HashMap<>();

MCAFilePipe.clearQueues();

progressChannel.setMax(importFiles.length);
progressChannel.updateProgress(importFiles[0].getName(), 0);
// find target files
for (File file : importFiles) {
Point2i source = Helper.parseMCAFileName(file);
if (source == null) {
Debug.dumpf("could not parse region from mca file name: %s", file.getName());
continue;
}

try {
Set<Point2i> targets = getTargetRegions(source, offset);
mapSourceRegionsByTargetRegion(source, targets, targetMapping);
} catch (Exception ex) {
ex.printStackTrace();
}
}

for (File file : importFiles) {
MCAFilePipe.addJob(new MCAChunkImporterLoadJob(file, progressChannel, overwrite));
progressChannel.updateProgress(importFiles[0].getName(), 0);

for (Map.Entry<Point2i, Set<Point2i>> entry : targetMapping.entrySet()) {
File targetFile = Helper.createMCAFilePath(entry.getKey());
MCAFilePipe.addJob(new MCAChunkImporterLoadJob(targetFile, importDir, entry.getKey(), entry.getValue(), offset, progressChannel, overwrite));
}
} catch (Exception ex) {
ex.printStackTrace();
}
}

public static class MCAChunkImporterLoadJob extends LoadDataJob {

private Point2i target;
private Set<Point2i> sources;
private File sourceDir;
private Point2i offset;
private ProgressTask progressChannel;
private boolean overwrite;

MCAChunkImporterLoadJob(File file, ProgressTask progressChannel, boolean overwrite) {
super(file);
MCAChunkImporterLoadJob(File targetFile, File sourceDir, Point2i target, Set<Point2i> sources, Point2i offset, ProgressTask progressChannel, boolean overwrite) {
super(targetFile);
this.target = target;
this.sources = sources;
this.sourceDir = sourceDir;
this.offset = offset;
this.progressChannel = progressChannel;
this.overwrite = overwrite;
}

@Override
public void execute() {
File dest = new File(Config.getWorldDir(), getFile().getName());

if (!dest.exists()) {
// special case for non existing destination file and no offset
if (offset.getX() == 0 && offset.getY() == 0 && !getFile().exists()) {
//if the entire mca file doesn't exist, just copy it over
File source = new File(sourceDir, getFile().getName());
try {
Files.copy(getFile().toPath(), dest.toPath());

Files.copy(source.toPath(), getFile().toPath());
} catch (IOException ex) {
Debug.errorf("failed to copy file %s to %s: %s", getFile(), dest, ex.getMessage());
Debug.errorf("failed to copy file %s to %s: %s", source, getFile(), ex.getMessage());
}
progressChannel.incrementProgress(getFile().getName());
progressChannel.incrementProgress(getFile().getName(), sources.size());
return;
}

// load both files
byte[] sourceData = load();
byte[] destData = load(dest);
// regular case

if (sourceData == null) {
Debug.errorf("error loading source mca file %s", getFile().getName());
progressChannel.incrementProgress(getFile().getName());
return;
Map<Point2i, byte[]> sourceDataMapping = new HashMap<>();

for (Point2i sourceRegion : sources) {
File source = new File(sourceDir, Helper.createMCAFileName(sourceRegion));

byte[] sourceData = load(source);

if (sourceData == null) {
Debug.errorf("error loading source mca file %s", source.getName());
progressChannel.incrementProgress(getFile().getName());
continue;
}

sourceDataMapping.put(sourceRegion, sourceData);
}

if (destData == null) {
Debug.errorf("error loading destination mca file %s", getFile().getName());
progressChannel.incrementProgress(getFile().getName());
if (sourceDataMapping.isEmpty()) {
Debug.errorf("could not load any source mca files to merge into %s with offset %s", getFile().getName(), offset);
// don't increment progress here, if there were errors loading it has already been incremented
return;
}

MCAFilePipe.executeProcessData(new MCAChunkImporterProcessJob(getFile(), dest, sourceData, destData, progressChannel, overwrite));
byte[] destData;

if (getFile().exists()) {
destData = load();
if (destData == null) {
Debug.errorf("error loading destination mca file %s", getFile().getName());
progressChannel.incrementProgress(getFile().getName(), sourceDataMapping.size());
return;
}
} else {
destData = null;
}

MCAFilePipe.executeProcessData(new MCAChunkImporterProcessJob(getFile(), sourceDir, target, sourceDataMapping, destData, offset, progressChannel, overwrite));
}
}

public static class MCAChunkImporterProcessJob extends ProcessDataJob {

private File destFile;
private byte[] destData;
private File sourceDir;
private Point2i target;
private Map<Point2i, byte[]> sourceDataMapping;
private Point2i offset;
private ProgressTask progressChannel;
private boolean overwrite;

MCAChunkImporterProcessJob(File sourceFile, File destFile, byte[] sourceData, byte[] destData, ProgressTask progressChannel, boolean overwrite) {
super(sourceFile, sourceData);
this.destFile = destFile;
this.destData = destData;
MCAChunkImporterProcessJob(File targetFile, File sourceDir, Point2i target, Map<Point2i, byte[]> sourceDataMapping, byte[] destData, Point2i offset, ProgressTask progressChannel, boolean overwrite) {
super(targetFile, destData);
this.sourceDir = sourceDir;
this.target = target;
this.sourceDataMapping = sourceDataMapping;
this.offset = offset;
this.progressChannel = progressChannel;
this.overwrite = overwrite;
}

@Override
public void execute() {
//load MCAFiles
Timer t = new Timer();
try {
MCAFile source = MCAFile.readAll(getFile(), new ByteArrayPointer(getData()));
if (source == null) {
progressChannel.incrementProgress(getFile().getName());
Debug.errorf("failed to load source MCAFile %s", getFile().getName());
return;
MCAFile destination;
// no destination file: create new MCAFile
if (getData() == null) {
destination = new MCAFile(getFile());
} else {
destination = MCAFile.readAll(getFile(), new ByteArrayPointer(getData()));
}

MCAFile dest = MCAFile.readAll(destFile, new ByteArrayPointer(destData));
if (dest == null) {
progressChannel.incrementProgress(destFile.getName());
Debug.errorf("failed to load destination MCAFile %s", destFile.getName());
if (destination == null) {
progressChannel.incrementProgress(getFile().getName(), sourceDataMapping.size());
Debug.errorf("failed to load target MCAFile %s", getFile().getName());
return;
}

source.mergeChunksInto(dest, overwrite);
for (Map.Entry<Point2i, byte[]> sourceData : sourceDataMapping.entrySet()) {
MCAFile source = MCAFile.readAll(new File(sourceDir, Helper.createMCAFileName(sourceData.getKey())), new ByteArrayPointer(sourceData.getValue()));

Debug.dumpf("merging chunk from region %s into %s", sourceData.getKey(), target);

MCAFilePipe.executeSaveData(new MCAChunkImporterSaveJob(destFile, dest, progressChannel));
source.mergeChunksInto(destination, getRelativeOffset(sourceData.getKey(), target, offset), overwrite);
}

MCAFilePipe.executeSaveData(new MCAChunkImporterSaveJob(getFile(), destination, sourceDataMapping.size(), progressChannel));

} catch (Exception ex) {
Debug.errorf("error merging chunks from %s into %s: %s", getFile(), destFile, ex.getMessage());
Debug.errorf("error merging chunks into %s with offset %s: %s", getFile(), offset, ex.getMessage());
progressChannel.incrementProgress(getFile().getName());
}
Debug.dumpf("took %s to merge %s into %s", t, getFile(), destFile.getName());

Debug.dumpf("took %s to merge chunks into %s with offset %s", t, getFile(), offset);
}
}

public static class MCAChunkImporterSaveJob extends SaveDataJob<MCAFile> {

private int sourceCount;
private ProgressTask progressChannel;

MCAChunkImporterSaveJob(File file, MCAFile data, ProgressTask progressChannel) {
MCAChunkImporterSaveJob(File file, MCAFile data, int sourceCount, ProgressTask progressChannel) {
super(file, data);
this.sourceCount = sourceCount;
this.progressChannel = progressChannel;
}

Expand All @@ -145,8 +219,31 @@ public void execute() {
} catch (Exception ex) {
Debug.error(ex);
}
progressChannel.incrementProgress(getFile().getName());
progressChannel.incrementProgress(getFile().getName(), sourceCount);
Debug.dumpf("took %s to save data to %s", t, getFile().getName());
}
}

public static void mapSourceRegionsByTargetRegion(Point2i source, Set<Point2i> targets, Map<Point2i, Set<Point2i>> map) {
for (Point2i target : targets) {
map.computeIfAbsent(target, key -> new HashSet<>(4));
map.get(target).add(source);
}
}


// source is a region coordinate, offset is a chunk coordinate
public static Set<Point2i> getTargetRegions(Point2i source, Point2i offset) {
Set<Point2i> result = new HashSet<>(4);
Point2i sourceChunk = Helper.regionToChunk(source).add(offset);
result.add(Helper.chunkToRegion(sourceChunk));
result.add(Helper.chunkToRegion(sourceChunk.add(31, 0)));
result.add(Helper.chunkToRegion(sourceChunk.add(0, 31)));
result.add(Helper.chunkToRegion(sourceChunk.add(31, 31)));
return result;
}

public static Point2i getRelativeOffset(Point2i source, Point2i target, Point2i offset) {
return Helper.regionToChunk(source).add(offset).sub(Helper.regionToChunk(target));
}
}
10 changes: 10 additions & 0 deletions src/main/java/net/querz/mcaselector/io/MCAChunkData.java
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,14 @@ public Point2i getLocation() {
}
return new Point2i(data.getCompoundTag("Level").getInt("xPos"), data.getCompoundTag("Level").getInt("zPos"));
}

public boolean setLocation(Point2i location) {
if (data == null || !data.containsKey("Level")) {
return false;
}

data.getCompoundTag("Level").putInt("xPos", location.getX());
data.getCompoundTag("Level").putInt("zPos", location.getY());
return true;
}
}
Loading

0 comments on commit 018064e

Please sign in to comment.