mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-12 13:26:53 -04:00
Fix bugs with world.
This commit is contained in:
parent
9d5da5a39d
commit
67b6a60382
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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=目前目錄
|
||||
|
@ -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=当前目录
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user