Fix bugs with world.

This commit is contained in:
huanghongxun 2018-09-17 16:30:27 +08:00
parent 9d5da5a39d
commit 67b6a60382
8 changed files with 114 additions and 56 deletions

View File

@ -8,6 +8,8 @@ import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.IntVersionNumber;
import org.jackhuang.hmcl.util.VersionNumber;
import java.io.File;
import java.util.Date;
@ -24,7 +26,7 @@ public class WorldListItem extends Control {
this.world = world;
title.set(world.getWorldName());
subtitle.set(i18n("world.description", world.getFileName(), new Date(world.getLastPlayed()).toString(), world.getGameVersion()));
subtitle.set(i18n("world.description", world.getFileName(), new Date(world.getLastPlayed()).toString(), world.getGameVersion() == null ? i18n("message.unknown") : world.getGameVersion()));
}
@Override
@ -58,6 +60,12 @@ public class WorldListItem extends Control {
}
public void manageDatapacks() {
if (world.getGameVersion() == null || // old game will not write game version to level.dat
(IntVersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
Controllers.dialog(i18n("world.datapack.1_13"));
return;
}
Controllers.navigate(new DatapackListPage(world.getWorldName(), world.getFile()));
}
}

View File

@ -3,16 +3,20 @@ package org.jackhuang.hmcl.ui.versions;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.util.Logging;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@ -25,33 +29,38 @@ public class WorldListPage extends ListPage<WorldListItem> {
public void loadVersion(Profile profile, String id) {
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
itemsProperty().clear();
try {
if (Files.exists(savesDir))
for (Path worldDir : Files.newDirectoryStream(savesDir)) {
itemsProperty().add(new WorldListItem(new World(worldDir)));
}
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Failed to read saves", e);
}
itemsProperty().setAll(World.getWorlds(savesDir).stream()
.map(WorldListItem::new).collect(Collectors.toList()));
}
@Override
public void add() {
FileChooser chooser = new FileChooser();
chooser.setTitle(i18n("world.choose_world"));
chooser.setTitle(i18n("world.import.choose"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("world.extension"), "*.zip"));
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
if (res == null) return;
res.forEach(it -> {
try {
World world = new World(it.toPath());
world.install(savesDir, world.getWorldName());
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(world.getWorldName()))));
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
}
});
if (res == null || res.isEmpty()) return;
try {
// Only accept one world file because user is required to confirm the new world name
// Or too many input dialogs are popped.
World world = new World(res.get(0).toPath());
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.of(() -> world.install(savesDir, name))
.finalized(Schedulers.javafx(), var -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
else
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
}).start();
}).setInitialText(world.getWorldName());
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + res.get(0), e);
}
}
}

View File

@ -251,8 +251,8 @@ datapack.title=World %s - Datapacks
datapack.remove=Remove
world=Worlds/Datapacks
world.choose_world=Choose the save zip to be imported
world.datapack=Manage data packs
world.datapack.1_13=Only Minecraft 1.13 and later versions support data packs.
world.description=%s. Last played time: %s. Game version: %s.
world.export=Export this world
world.export.title=Choose a file location to hold your world
@ -260,7 +260,11 @@ world.export.location=Export to
world.export.wizard=Export world %s
world.extension=World zip
world.game_version=Game Version
world.import.already_exists=This world already exists.
world.import.choose=Choose the save zip to be imported
world.import.failed=Unable to import this world: %s
world.name=World Name
world.name.enter=Enter the world name
profile=Game Directories
profile.default=Current directory

View File

@ -244,23 +244,27 @@ mods.add.success=成功新增模組 %s。
mods.choose_mod=選擇模組
mods.remove=刪除
datapack=數據檔
datapack.add=添加數據檔
datapack.choose_datapack=選擇要導入的數據包壓縮檔
datapack.title=世界 %s - 數據檔
datapack=資料包
datapack.add=添加資料包
datapack.choose_datapack=選擇要導入的資料包壓縮檔
datapack.title=世界 %s - 資料包
datapack.remove=刪除
world=世界/數據檔
world.choose_world=選擇要導入的存檔壓縮檔
world.datapack=管理數據檔
world=世界/資料包
world.datapack=管理資料包
world.datapack.1_13=僅 Minecraft 1.13 及之後的版本支持資料包
world.description=%s. 上一次遊戲時間: %s. 遊戲版本: %s
world.export=導出此世界
world.export.title=選擇該世界的存儲位置
world.export.location=保存到
world.export.wizard=導出世界 %s
world.extension=存檔壓縮檔
world.import.already_exists=此世界已經存在
world.import.choose=選擇要導入的存檔壓縮檔
world.import.failed=無法導入此世界: %s
world.game_version=遊戲版本
world.name=世界名稱
world.name.enter=輸入世界名稱
profile=遊戲目錄
profile.default=目前目錄

View File

@ -251,8 +251,8 @@ datapack.title=世界 %s - 数据包
datapack.remove=删除
world=世界/数据包
world.choose_world=选择要导入的存档压缩包
world.datapack=管理数据包
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
world.description=%s. 上一次游戏时间: %s. 游戏版本: %s
world.export=导出此世界
world.export.title=选择该世界的存储位置
@ -260,7 +260,11 @@ world.export.location=保存到
world.export.wizard=导出世界 %s
world.extension=世界压缩包
world.game_version=游戏版本
world.import.already_exists=此世界已经存在
world.import.choose=选择要导入的存档压缩包
world.import.failed=无法导入此世界:%s
world.name=世界名称
world.name.enter=输入世界名称
profile=游戏目录
profile.default=当前目录

View File

@ -5,18 +5,18 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.LongTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Unzipper;
import org.jackhuang.hmcl.util.Zipper;
import org.jackhuang.hmcl.util.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@ -27,7 +27,7 @@ public class World {
private String fileName;
private String worldName;
private String gameVersion;
private long lastPlayed, seed;
private long lastPlayed;
public World(Path file) throws IOException {
this.file = file;
@ -62,10 +62,6 @@ public class World {
return lastPlayed;
}
public long getSeed() {
return seed;
}
public String getGameVersion() {
return gameVersion;
}
@ -88,12 +84,23 @@ public class World {
CompoundTag nbt = parseLevelDat(levelDat);
CompoundTag data = nbt.get("Data");
String name = data.<StringTag>get("LevelName").getValue();
lastPlayed = data.<LongTag>get("LastPlayed").getValue();
seed = data.<LongTag>get("RandomSeed").getValue();
CompoundTag version = data.get("Version");
gameVersion = version.<StringTag>get("Name").getValue();
worldName = name;
if (data.get("LevelName") instanceof StringTag)
worldName = data.<StringTag>get("LevelName").getValue();
else
throw new IOException("level.dat missing LevelName");
if (data.get("LastPlayed") instanceof LongTag)
lastPlayed = data.<LongTag>get("LastPlayed").getValue();
else
throw new IOException("level.dat missing LastPlayed");
gameVersion = null;
if (data.get("Version") instanceof CompoundTag) {
CompoundTag version = data.get("Version");
if (version.get("Name") instanceof StringTag)
gameVersion = version.<StringTag>get("Name").getValue();
}
}
public void rename(String newName) throws IOException {
@ -106,7 +113,9 @@ public class World {
CompoundTag data = nbt.get("Data");
data.put(new StringTag("LevelName", newName));
NBTIO.writeTag(new GZIPOutputStream(Files.newOutputStream(levelDat)), nbt);
try (OutputStream os = new GZIPOutputStream(Files.newOutputStream(levelDat))) {
NBTIO.writeTag(os, nbt);
}
// then change the folder's name
Files.move(file, file.resolveSibling(newName));
@ -127,9 +136,10 @@ public class World {
}
subDirectoryName = FileUtils.getName(subDirs.get(0));
}
new Unzipper(file, savesDir)
new Unzipper(file, worldDir)
.setSubDirectory("/" + subDirectoryName + "/")
.unzip();
new World(worldDir).rename(name);
} else if (Files.isDirectory(file)) {
FileUtils.copyDirectory(file, worldDir);
}
@ -145,17 +155,28 @@ public class World {
}
private static CompoundTag parseLevelDat(Path path) throws IOException {
Tag nbt = NBTIO.readTag(new GZIPInputStream(Files.newInputStream(path)));
if (nbt instanceof CompoundTag)
return (CompoundTag) nbt;
else
throw new IOException("level.dat malformed");
try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
Tag nbt = NBTIO.readTag(is);
if (nbt instanceof CompoundTag)
return (CompoundTag) nbt;
else
throw new IOException("level.dat malformed");
}
}
public static List<World> getWorlds(Path worldDir) throws IOException {
public static List<World> getWorlds(Path savesDir) {
List<World> worlds = new ArrayList<>();
for (Path world : Files.newDirectoryStream(worldDir)) {
worlds.add(new World(world));
try {
if (Files.exists(savesDir))
for (Path world : Files.newDirectoryStream(savesDir)) {
try {
worlds.add(new World(world));
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Failed to read world " + world, e);
}
}
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Failed to read saves", e);
}
return worlds;
}

View File

@ -301,7 +301,13 @@ public abstract class Task {
return finalized(scheduler, (variables, isDependentsSucceeded) -> {
if (isDependentsSucceeded) {
if (success != null)
success.accept(variables);
try {
success.accept(variables);
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Failed to execute " + success, e);
if (failure != null)
failure.accept(e);
}
}
else {
if (failure != null)

View File

@ -73,6 +73,8 @@ public final class CompressingUtils {
} catch (ZipError error) {
// Since Java 8 throws ZipError stupidly
throw new ZipException(error.getMessage());
} catch (UnsupportedOperationException ex) {
throw new IOException("Not a zip file");
}
}