mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-13 05:46:59 -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.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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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=目前目錄
|
||||||
|
@ -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=当前目录
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user