feat: 优化世界管理界面和世界信息界面 (#4823)
Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
This commit is contained in:
@@ -18,10 +18,7 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
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 com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.util.io.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
@@ -48,13 +45,10 @@ public final class World {
|
||||
|
||||
private final Path file;
|
||||
private String fileName;
|
||||
private String worldName;
|
||||
private GameVersionNumber gameVersion;
|
||||
private long lastPlayed;
|
||||
private CompoundTag levelData;
|
||||
private Image icon;
|
||||
private Long seed;
|
||||
private boolean largeBiomes;
|
||||
private boolean isLocked;
|
||||
private Path levelDataPath;
|
||||
|
||||
public World(Path file) throws IOException {
|
||||
this.file = file;
|
||||
@@ -67,10 +61,100 @@ public final class World {
|
||||
throw new IOException("Path " + file + " cannot be recognized as a Minecraft world");
|
||||
}
|
||||
|
||||
public Path getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
StringTag levelNameTag = data.get("LevelName");
|
||||
return levelNameTag.getValue();
|
||||
}
|
||||
|
||||
public void setWorldName(String worldName) throws IOException {
|
||||
if (levelData.get("Data") instanceof CompoundTag data && data.get("LevelName") instanceof StringTag levelNameTag) {
|
||||
levelNameTag.setValue(worldName);
|
||||
writeLevelDat(levelData);
|
||||
}
|
||||
}
|
||||
|
||||
public Path getLevelDatFile() {
|
||||
return file.resolve("level.dat");
|
||||
}
|
||||
|
||||
public Path getSessionLockFile() {
|
||||
return file.resolve("session.lock");
|
||||
}
|
||||
|
||||
public CompoundTag getLevelData() {
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public long getLastPlayed() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
LongTag lastPlayedTag = data.get("LastPlayed");
|
||||
return lastPlayedTag.getValue();
|
||||
}
|
||||
|
||||
public @Nullable GameVersionNumber getGameVersion() {
|
||||
if (levelData.get("Data") instanceof CompoundTag data &&
|
||||
data.get("Version") instanceof CompoundTag versionTag &&
|
||||
versionTag.get("Name") instanceof StringTag nameTag) {
|
||||
return GameVersionNumber.asGameVersion(nameTag.getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable Long getSeed() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag && worldGenSettingsTag.get("seed") instanceof LongTag seedTag) { //Valid after 1.16
|
||||
return seedTag.getValue();
|
||||
} else if (data.get("RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16
|
||||
return seedTag.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isLargeBiomes() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data.get("generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16
|
||||
return "largeBiomes".equals(generatorNameTag.getValue());
|
||||
} else {
|
||||
if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag
|
||||
&& worldGenSettingsTag.get("dimensions") instanceof CompoundTag dimensionsTag
|
||||
&& dimensionsTag.get("minecraft:overworld") instanceof CompoundTag overworldTag
|
||||
&& overworldTag.get("generator") instanceof CompoundTag generatorTag) {
|
||||
if (generatorTag.get("biome_source") instanceof CompoundTag biomeSourceTag
|
||||
&& biomeSourceTag.get("large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2
|
||||
return largeBiomesTag.getValue() == (byte) 1;
|
||||
} else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2
|
||||
return "minecraft:large_biomes".equals(settingsTag.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Image getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return isLocked;
|
||||
}
|
||||
|
||||
private void loadFromDirectory() throws IOException {
|
||||
fileName = FileUtils.getName(file);
|
||||
Path levelDat = file.resolve("level.dat");
|
||||
loadWorldInfo(levelDat);
|
||||
if (!Files.exists(levelDat)) { // version 20w14infinite
|
||||
levelDat = file.resolve("special_level.dat");
|
||||
}
|
||||
loadAndCheckLevelDat(levelDat);
|
||||
this.levelDataPath = levelDat;
|
||||
isLocked = isLocked(getSessionLockFile());
|
||||
|
||||
Path iconFile = file.resolve("icon.png");
|
||||
@@ -85,56 +169,16 @@ public final class World {
|
||||
}
|
||||
}
|
||||
|
||||
public Path getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
return worldName;
|
||||
}
|
||||
|
||||
public Path getLevelDatFile() {
|
||||
return file.resolve("level.dat");
|
||||
}
|
||||
|
||||
public Path getSessionLockFile() {
|
||||
return file.resolve("session.lock");
|
||||
}
|
||||
|
||||
public long getLastPlayed() {
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
public @Nullable GameVersionNumber getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public @Nullable Long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
public boolean isLargeBiomes() {
|
||||
return largeBiomes;
|
||||
}
|
||||
|
||||
public Image getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return isLocked;
|
||||
}
|
||||
|
||||
private void loadFromZipImpl(Path root) throws IOException {
|
||||
Path levelDat = root.resolve("level.dat");
|
||||
if (!Files.exists(levelDat))
|
||||
throw new IOException("Not a valid world zip file since level.dat cannot be found.");
|
||||
if (!Files.exists(levelDat)) { //version 20w14infinite
|
||||
levelDat = root.resolve("special_level.dat");
|
||||
}
|
||||
if (!Files.exists(levelDat)) {
|
||||
throw new IOException("Not a valid world zip file since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
|
||||
loadWorldInfo(levelDat);
|
||||
loadAndCheckLevelDat(levelDat);
|
||||
|
||||
Path iconFile = root.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
@@ -167,50 +211,22 @@ public final class World {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadWorldInfo(Path levelDat) throws IOException {
|
||||
CompoundTag nbt = parseLevelDat(levelDat);
|
||||
|
||||
CompoundTag data = nbt.get("Data");
|
||||
private void loadAndCheckLevelDat(Path levelDat) throws IOException {
|
||||
this.levelData = parseLevelDat(levelDat);
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data == null)
|
||||
throw new IOException("level.dat missing Data");
|
||||
|
||||
if (data.get("LevelName") instanceof StringTag)
|
||||
worldName = data.<StringTag>get("LevelName").getValue();
|
||||
else
|
||||
if (!(data.get("LevelName") instanceof StringTag))
|
||||
throw new IOException("level.dat missing LevelName");
|
||||
|
||||
if (data.get("LastPlayed") instanceof LongTag)
|
||||
lastPlayed = data.<LongTag>get("LastPlayed").getValue();
|
||||
else
|
||||
if (!(data.get("LastPlayed") instanceof LongTag))
|
||||
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 = GameVersionNumber.asGameVersion(version.<StringTag>get("Name").getValue());
|
||||
}
|
||||
|
||||
Tag worldGenSettings = data.get("WorldGenSettings");
|
||||
if (worldGenSettings instanceof CompoundTag) {
|
||||
Tag seedTag = ((CompoundTag) worldGenSettings).get("seed");
|
||||
if (seedTag instanceof LongTag) {
|
||||
seed = ((LongTag) seedTag).getValue();
|
||||
}
|
||||
}
|
||||
if (seed == null) {
|
||||
Tag seedTag = data.get("RandomSeed");
|
||||
if (seedTag instanceof LongTag) {
|
||||
seed = ((LongTag) seedTag).getValue();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Only work for 1.15 and below
|
||||
if (data.get("generatorName") instanceof StringTag) {
|
||||
largeBiomes = "largeBiomes".equals(data.<StringTag>get("generatorName").getValue());
|
||||
} else {
|
||||
largeBiomes = false;
|
||||
public void reloadLevelDat() throws IOException {
|
||||
if (levelDataPath != null) {
|
||||
loadAndCheckLevelDat(this.levelDataPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,10 +235,9 @@ public final class World {
|
||||
throw new IOException("Not a valid world directory");
|
||||
|
||||
// Change the name recorded in level.dat
|
||||
CompoundTag nbt = readLevelDat();
|
||||
CompoundTag data = nbt.get("Data");
|
||||
CompoundTag data = levelData.get("Data");
|
||||
data.put(new StringTag("LevelName", newName));
|
||||
writeLevelDat(nbt);
|
||||
writeLevelDat(levelData);
|
||||
|
||||
// then change the folder's name
|
||||
Files.move(file, file.resolveSibling(newName));
|
||||
@@ -283,11 +298,19 @@ public final class World {
|
||||
FileUtils.forceDelete(file);
|
||||
}
|
||||
|
||||
public CompoundTag readLevelDat() throws IOException {
|
||||
if (!Files.isDirectory(file))
|
||||
public void copy(String newName) throws IOException {
|
||||
if (!Files.isDirectory(file)) {
|
||||
throw new IOException("Not a valid world directory");
|
||||
}
|
||||
|
||||
return parseLevelDat(getLevelDatFile());
|
||||
if (isLocked()) {
|
||||
throw new WorldLockedException("The world " + getFile() + " has been locked");
|
||||
}
|
||||
|
||||
Path newPath = file.resolveSibling(newName);
|
||||
FileUtils.copyDirectory(file, newPath, path -> !path.contains("session.lock"));
|
||||
World newWorld = new World(newPath);
|
||||
newWorld.rename(newName);
|
||||
}
|
||||
|
||||
public FileChannel lock() throws WorldLockedException {
|
||||
|
||||
Reference in New Issue
Block a user