新增“原理图管理”界面 (#3951)

This commit is contained in:
Glavo
2025-05-31 21:53:16 +08:00
committed by GitHub
parent 6124b6711f
commit caa98c8956
10 changed files with 930 additions and 2 deletions

View File

@@ -526,6 +526,10 @@ public class DefaultGameRepository implements GameRepository {
return getRunDirectory(id).toPath().resolve("backups");
}
public Path getSchematicsDirectory(String id) {
return getRunDirectory(id).toPath().resolve("schematics");
}
@Override
public String toString() {
return new ToStringBuilder(this)

View File

@@ -0,0 +1,163 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.schematic;
import com.github.steveice10.opennbt.NBTIO;
import com.github.steveice10.opennbt.tag.builtin.*;
import javafx.geometry.Point3D;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.zip.GZIPInputStream;
/**
* @author Glavo
* @see <a href="https://litemapy.readthedocs.io/en/v0.9.0b0/litematics.html">The Litematic file format</a>
*/
public final class LitematicFile {
private static int tryGetInt(Tag tag) {
return tag instanceof IntTag ? ((IntTag) tag).getValue() : 0;
}
private static @Nullable Instant tryGetLongTimestamp(Tag tag) {
if (tag instanceof LongTag) {
return Instant.ofEpochMilli(((LongTag) tag).getValue());
}
return null;
}
private static @Nullable String tryGetString(Tag tag) {
return tag instanceof StringTag ? ((StringTag) tag).getValue() : null;
}
public static LitematicFile load(Path file) throws IOException {
CompoundTag root;
try (InputStream in = new GZIPInputStream(Files.newInputStream(file))) {
root = (CompoundTag) NBTIO.readTag(in);
}
Tag versionTag = root.get("Version");
if (versionTag == null)
throw new IOException("Version tag not found");
else if (!(versionTag instanceof IntTag))
throw new IOException("Version tag is not an integer");
Tag metadataTag = root.get("Metadata");
if (metadataTag == null)
throw new IOException("Metadata tag not found");
else if (!(metadataTag instanceof CompoundTag))
throw new IOException("Metadata tag is not a compound tag");
return new LitematicFile(file, root);
}
private final @NotNull Path file;
private final @NotNull CompoundTag root;
private LitematicFile(@NotNull Path file, @NotNull CompoundTag root) {
this.file = file;
this.root = root;
}
private @NotNull CompoundTag getMetadata() {
return root.get("Metadata");
}
public @NotNull Path getFile() {
return file;
}
public int getVersion() {
return root.<IntTag>get("Version").getValue();
}
public int getSubVersion() {
return tryGetInt(root.get("SubVersion"));
}
public int getMinecraftDataVersion() {
return tryGetInt(root.get("MinecraftDataVersion"));
}
public int[] getPreviewImageData() {
Tag previewImageData = getMetadata().get("PreviewImageData");
if (previewImageData instanceof IntArrayTag) {
return ((IntArrayTag) previewImageData).getValue().clone();
} else {
return null;
}
}
public String getName() {
return tryGetString(getMetadata().get("Name"));
}
public String getAuthor() {
return tryGetString(getMetadata().get("Author"));
}
public String getDescription() {
return tryGetString(getMetadata().get("Description"));
}
public Instant getTimeCreated() {
return tryGetLongTimestamp(getMetadata().get("TimeCreated"));
}
public Instant getTimeModified() {
return tryGetLongTimestamp(getMetadata().get("TimeModified"));
}
public int getTotalBlocks() {
return tryGetInt(getMetadata().get("TotalBlocks"));
}
public int getTotalVolume() {
return tryGetInt(getMetadata().get("TotalVolume"));
}
public Point3D getEnclosingSize() {
Tag enclosingSizeTag = getMetadata().get("EnclosingSize");
if (enclosingSizeTag instanceof CompoundTag) {
CompoundTag list = (CompoundTag) enclosingSizeTag;
int x = tryGetInt(list.get("x"));
int y = tryGetInt(list.get("y"));
int z = tryGetInt(list.get("z"));
if (x >= 0 && y >= 0 && z >= 0)
return new Point3D(x, y, z);
}
return null;
}
public int getRegionCount() {
Tag regions = root.get("Regions");
if (regions instanceof CompoundTag)
return ((CompoundTag) regions).size();
else return 0;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.schematic;
import javafx.geometry.Point3D;
import org.jackhuang.hmcl.game.CrashReportAnalyzerTest;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.assertEquals;
public final class LitematicFileTest {
private static LitematicFile load(String name) throws IOException, URISyntaxException {
URL resource = CrashReportAnalyzerTest.class.getResource(name);
if (resource == null)
throw new IOException("Resource not found: " + name);
return LitematicFile.load(Paths.get(resource.toURI()));
}
@Test
public void test() throws Exception {
LitematicFile file = load("/schematics/test.litematic");
assertEquals("刷石机一桶岩浆下推爆破8.3万每小时", file.getName());
assertEquals("hsds", file.getAuthor());
assertEquals("", file.getDescription());
assertEquals(Instant.ofEpochMilli(1746443586433L), file.getTimeCreated());
assertEquals(Instant.ofEpochMilli(1746443586433L), file.getTimeModified());
assertEquals(1334, file.getTotalBlocks());
assertEquals(5746, file.getTotalVolume());
assertEquals(new Point3D(17, 26, 13), file.getEnclosingSize());
assertEquals(1, file.getRegionCount());
}
}

Binary file not shown.