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.game.World;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider; 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.io.File;
import java.util.Date; import java.util.Date;
@ -24,7 +26,7 @@ public class WorldListItem extends Control {
this.world = world; this.world = world;
title.set(world.getWorldName()); 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 @Override
@ -58,6 +60,12 @@ public class WorldListItem extends Control {
} }
public void manageDatapacks() { 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())); 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 javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.setting.Profile; 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.Controllers;
import org.jackhuang.hmcl.ui.ListPage; import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; 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) { public void loadVersion(Profile profile, String id) {
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves"); this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
itemsProperty().clear(); itemsProperty().setAll(World.getWorlds(savesDir).stream()
try { .map(WorldListItem::new).collect(Collectors.toList()));
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);
}
} }
@Override @Override
public void add() { public void add() {
FileChooser chooser = new FileChooser(); 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")); chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("world.extension"), "*.zip"));
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage()); List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
if (res == null) return; if (res == null || res.isEmpty()) return;
res.forEach(it -> {
try { try {
World world = new World(it.toPath()); // Only accept one world file because user is required to confirm the new world name
world.install(savesDir, world.getWorldName()); // Or too many input dialogs are popped.
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(world.getWorldName())))); 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) { } catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, 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 datapack.remove=Remove
world=Worlds/Datapacks world=Worlds/Datapacks
world.choose_world=Choose the save zip to be imported
world.datapack=Manage data packs 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.description=%s. Last played time: %s. Game version: %s.
world.export=Export this world world.export=Export this world
world.export.title=Choose a file location to hold your 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.export.wizard=Export world %s
world.extension=World zip world.extension=World zip
world.game_version=Game Version 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=World Name
world.name.enter=Enter the world name
profile=Game Directories profile=Game Directories
profile.default=Current directory profile.default=Current directory

View File

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

View File

@ -251,8 +251,8 @@ datapack.title=世界 %s - 数据包
datapack.remove=删除 datapack.remove=删除
world=世界/数据包 world=世界/数据包
world.choose_world=选择要导入的存档压缩包
world.datapack=管理数据包 world.datapack=管理数据包
world.datapack.1_13=仅 Minecraft 1.13 及之后的版本支持数据包
world.description=%s. 上一次游戏时间: %s. 游戏版本: %s world.description=%s. 上一次游戏时间: %s. 游戏版本: %s
world.export=导出此世界 world.export=导出此世界
world.export.title=选择该世界的存储位置 world.export.title=选择该世界的存储位置
@ -260,7 +260,11 @@ world.export.location=保存到
world.export.wizard=导出世界 %s world.export.wizard=导出世界 %s
world.extension=世界压缩包 world.extension=世界压缩包
world.game_version=游戏版本 world.game_version=游戏版本
world.import.already_exists=此世界已经存在
world.import.choose=选择要导入的存档压缩包
world.import.failed=无法导入此世界:%s
world.name=世界名称 world.name=世界名称
world.name.enter=输入世界名称
profile=游戏目录 profile=游戏目录
profile.default=当前目录 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.LongTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag; import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.jackhuang.hmcl.util.CompressingUtils; import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Unzipper;
import org.jackhuang.hmcl.util.Zipper;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@ -27,7 +27,7 @@ public class World {
private String fileName; private String fileName;
private String worldName; private String worldName;
private String gameVersion; private String gameVersion;
private long lastPlayed, seed; private long lastPlayed;
public World(Path file) throws IOException { public World(Path file) throws IOException {
this.file = file; this.file = file;
@ -62,10 +62,6 @@ public class World {
return lastPlayed; return lastPlayed;
} }
public long getSeed() {
return seed;
}
public String getGameVersion() { public String getGameVersion() {
return gameVersion; return gameVersion;
} }
@ -88,12 +84,23 @@ public class World {
CompoundTag nbt = parseLevelDat(levelDat); CompoundTag nbt = parseLevelDat(levelDat);
CompoundTag data = nbt.get("Data"); CompoundTag data = nbt.get("Data");
String name = data.<StringTag>get("LevelName").getValue(); 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(); lastPlayed = data.<LongTag>get("LastPlayed").getValue();
seed = data.<LongTag>get("RandomSeed").getValue(); else
throw new IOException("level.dat missing LastPlayed");
gameVersion = null;
if (data.get("Version") instanceof CompoundTag) {
CompoundTag version = data.get("Version"); CompoundTag version = data.get("Version");
if (version.get("Name") instanceof StringTag)
gameVersion = version.<StringTag>get("Name").getValue(); gameVersion = version.<StringTag>get("Name").getValue();
worldName = name; }
} }
public void rename(String newName) throws IOException { public void rename(String newName) throws IOException {
@ -106,7 +113,9 @@ public class World {
CompoundTag data = nbt.get("Data"); CompoundTag data = nbt.get("Data");
data.put(new StringTag("LevelName", newName)); 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 // then change the folder's name
Files.move(file, file.resolveSibling(newName)); Files.move(file, file.resolveSibling(newName));
@ -127,9 +136,10 @@ public class World {
} }
subDirectoryName = FileUtils.getName(subDirs.get(0)); subDirectoryName = FileUtils.getName(subDirs.get(0));
} }
new Unzipper(file, savesDir) new Unzipper(file, worldDir)
.setSubDirectory("/" + subDirectoryName + "/") .setSubDirectory("/" + subDirectoryName + "/")
.unzip(); .unzip();
new World(worldDir).rename(name);
} else if (Files.isDirectory(file)) { } else if (Files.isDirectory(file)) {
FileUtils.copyDirectory(file, worldDir); FileUtils.copyDirectory(file, worldDir);
} }
@ -145,17 +155,28 @@ public class World {
} }
private static CompoundTag parseLevelDat(Path path) throws IOException { private static CompoundTag parseLevelDat(Path path) throws IOException {
Tag nbt = NBTIO.readTag(new GZIPInputStream(Files.newInputStream(path))); try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
Tag nbt = NBTIO.readTag(is);
if (nbt instanceof CompoundTag) if (nbt instanceof CompoundTag)
return (CompoundTag) nbt; return (CompoundTag) nbt;
else else
throw new IOException("level.dat malformed"); 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<>(); List<World> worlds = new ArrayList<>();
for (Path world : Files.newDirectoryStream(worldDir)) { try {
if (Files.exists(savesDir))
for (Path world : Files.newDirectoryStream(savesDir)) {
try {
worlds.add(new World(world)); 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; return worlds;
} }

View File

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

View File

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