diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java index 127c74c..b748f96 100644 --- a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystem.java @@ -37,19 +37,24 @@ public class MchFileSystem extends FileSystem { private final MchFileSystemProvider provider; private final MchRepository repository; private TrackedWorld trackedWorld; - private World world; + private String dimensionKey; + private Dimension dimension; private final Path root; private Set restoredPaths; - public MchFileSystem(MchFileSystemProvider provider, Path root, MchRepository repository, TrackedWorld trackedWorld, World world) throws IOException { + public MchFileSystem(MchFileSystemProvider provider, Path root, MchRepository repository, TrackedWorld trackedWorld, String dimensionKey, Dimension dimension) { if (root.getFileSystem() instanceof MchFileSystem) { - throw new IllegalArgumentException("path must correspond to a non-mch file system."); + throw new IllegalArgumentException("root path must correspond to a non-mch file system."); + } + if (!root.isAbsolute()) { + throw new IllegalArgumentException("root path must be absolute."); } this.provider = provider; this.root = root; this.repository = repository; this.trackedWorld = trackedWorld; - this.world = world; + this.dimensionKey = dimensionKey; + this.dimension = dimension; this.restoredPaths = new HashSet<>(); } @@ -57,11 +62,13 @@ public MchFileSystem(MchFileSystemProvider provider, Path root, MchRepository re * Set the {@link World} object that this file system reads from. * * @param trackedWorld The tracked world. - * @param world The world snapshot. + * @param dimensionKey The dimension key. + * @param dimension The dimension snapshot. */ - public void setWorld(TrackedWorld trackedWorld, World world) { + public void setWorld(TrackedWorld trackedWorld, String dimensionKey, Dimension dimension) { this.trackedWorld = trackedWorld; - this.world = world; + this.dimensionKey = dimensionKey; + this.dimension = dimension; this.restoredPaths = new HashSet<>(); } @@ -83,21 +90,8 @@ public void restore(@NotNull MchPath mchPath) throws IOException { System.out.println("DEBUG Restoring " + mchPath); Path relative = this.root.relativize(mchPath.path.toAbsolutePath()); - // TODO custom dimensions - String dimensionKey = switch (relative.getName(0).getFileName().toString()) { - case Util.NETHER_FOLDER -> Dimension.NETHER; - case Util.THE_END_FOLDER -> Dimension.THE_END; - default -> Dimension.OVERWORLD; - }; - int nameIndex = Dimension.OVERWORLD.equals(dimensionKey) ? 0 : 1; - - Reference20 dimensionRef = this.world.getDimension(dimensionKey); - if (dimensionRef == null) { - return; - } - Dimension dimension = dimensionRef.resolve(this.repository); - if ("region".equals(relative.getName(nameIndex).getFileName().toString())) { + if ("region".equals(relative.getName(0).getFileName().toString())) { String fileName = relative.getFileName().toString(); if ("region".equals(fileName)) { // lazy but works @@ -117,11 +111,11 @@ public void restore(@NotNull MchPath mchPath) throws IOException { } Path regionStoragePath = RegionStorageVisitor.getPath( - this.repository, this.trackedWorld, dimensionKey, regionX, regionZ + this.repository, this.trackedWorld, this.dimensionKey, regionX, regionZ ); Path mchRegionFilePath = MchRegionFile.getPath( - this.repository, this.trackedWorld, dimensionKey, regionX, regionZ + this.repository, this.trackedWorld, this.dimensionKey, regionX, regionZ ); Path mcRegionFilePath = mchPath.path; @@ -142,7 +136,7 @@ public void restore(@NotNull MchPath mchPath) throws IOException { // Non-region file. Tree tree = dimension.getMiscellaneousFiles().resolve(this.repository); - for (int i = nameIndex; i < relative.getNameCount() - 1; i++) { + for (int i = 0; i < relative.getNameCount() - 1; i++) { String name = relative.getName(i).toString(); Reference20 subTreeRef = tree.getSubTrees().get(name); if (subTreeRef == null) { @@ -172,22 +166,9 @@ public DirectoryStream newDirectoryStream(@NotNull MchPath dir, DirectoryS // Thread.dumpStack(); Path relative = this.root.relativize(dir.path.toAbsolutePath().normalize()); - // TODO custom dimensions - String dimensionKey = switch (relative.getName(0).getFileName().toString()) { - case Util.NETHER_FOLDER -> Dimension.NETHER; - case Util.THE_END_FOLDER -> Dimension.THE_END; - default -> Dimension.OVERWORLD; - }; - int nameIndex = Dimension.OVERWORLD.equals(dimensionKey) ? 0 : 1; - - Reference20 dimensionRef = this.world.getDimension(dimensionKey); - if (dimensionRef == null) { - throw new NoSuchFileException(dir.toString()); - } - Dimension dimension = dimensionRef.resolve(this.repository); - if ("region".equals(relative.getName(nameIndex).getFileName().toString())) { - if (relative.getNameCount() != nameIndex + 1) { + if ("region".equals(relative.getName(0).getFileName().toString())) { + if (relative.getNameCount() != 1) { // No files in subdirectories of region. return new DirectoryStream<>() { @Override @@ -215,7 +196,7 @@ public void close() {} // Non-region file. Tree tree = dimension.getMiscellaneousFiles().resolve(this.repository); - for (int i = nameIndex; i < relative.getNameCount(); i++) { + for (int i = 0; i < relative.getNameCount(); i++) { String name = relative.getName(i).toString(); if (name.isEmpty() || ".".equals(name)) { continue; diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java index ab137da..df69c2f 100644 --- a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchFileSystemProvider.java @@ -1,6 +1,6 @@ package ca.bkaw.mch.fs; -import ca.bkaw.mch.object.world.World; +import ca.bkaw.mch.object.dimension.Dimension; import ca.bkaw.mch.repository.MchRepository; import ca.bkaw.mch.repository.TrackedWorld; import org.jetbrains.annotations.NotNull; @@ -16,7 +16,6 @@ import java.nio.file.DirectoryStream; import java.nio.file.FileStore; import java.nio.file.FileSystem; -import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.OpenOption; @@ -26,7 +25,6 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; -import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -34,51 +32,39 @@ public class MchFileSystemProvider extends FileSystemProvider { public static final MchFileSystemProvider INSTANCE = new MchFileSystemProvider(); public static final String SCHEME = "mch"; - public static final String REPO_ENV_KEY = "mch_repository"; - public static final String WORLD_ENV_KEY = "mch_world"; - public static final String TRACKED_WORLD_ENV_KEY = "mch_tracked_world"; - - final Map fileSystems = new HashMap<>(); @Override public String getScheme() { return SCHEME; } - @Override - public FileSystem newFileSystem(URI uri, Map env) throws IOException { - synchronized (this.fileSystems) { - if (!(env.get(REPO_ENV_KEY) instanceof MchRepository repository)) { - throw new IllegalArgumentException("Please provide the mch repository as the \"" + REPO_ENV_KEY + "\" env key."); - } - if (!(env.get(WORLD_ENV_KEY) instanceof World world)) { - throw new IllegalArgumentException("Please provide the mch world object as the \"" + WORLD_ENV_KEY + "\" env key."); - } - if (!(env.get(TRACKED_WORLD_ENV_KEY) instanceof TrackedWorld trackedWorld)) { - throw new IllegalArgumentException("Please provide the tracked world as the \"" + TRACKED_WORLD_ENV_KEY + "\" env key."); - } - if (!SCHEME.equals(uri.getScheme())) { - throw new IllegalArgumentException("Expected \"" + SCHEME + "\" as the URI scheme."); - } - String path = uri.getPath(); - Path root = Path.of(path); - if (!Files.isDirectory(root)) { - throw new IllegalArgumentException("Please make sure the provided path is a directory: " + root); - } - - MchFileSystem fileSystem = this.fileSystems.get(path); - if (fileSystem != null) { - throw new FileSystemAlreadyExistsException(path); - } - fileSystem = new MchFileSystem(this, root, repository, trackedWorld, world); - this.fileSystems.put(path, fileSystem); - return fileSystem; + /** + * Create a new {@link MchFileSystem} that wraps paths on the real file system and + * provides files on-demand from the mch repository. + * + * @param root The root path to wrap, must be a directory that exists. + * @param repository The mch repository. + * @param trackedWorld The tracked world. + * @param dimensionKey The dimension key. + * @param dimension The dimension snapshot. + * @return The file system. + */ + @NotNull + public MchFileSystem newFileSystem(Path root, MchRepository repository, TrackedWorld trackedWorld, String dimensionKey, Dimension dimension) { + if (!Files.isDirectory(root)) { + throw new IllegalArgumentException("Please make sure the provided path is a directory: " + root); } + return new MchFileSystem(this, root, repository, trackedWorld, dimensionKey, dimension); + } + + @Override + public MchFileSystem newFileSystem(URI uri, Map env) { + throw new UnsupportedOperationException("Please use the other newFileSystem method."); } @Override public FileSystem getFileSystem(URI uri) { - return this.fileSystems.get(uri.getPath()); + throw new UnsupportedOperationException(); } @Override @@ -161,7 +147,7 @@ public boolean isHidden(Path path) throws IOException { } @Override - public FileStore getFileStore(Path path) throws IOException { + public FileStore getFileStore(Path path) { throw new UnsupportedOperationException(); } diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java index d97f0f9..ef7b3bc 100644 --- a/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java +++ b/mch-fs/src/main/java/ca/bkaw/mch/fs/MchPath.java @@ -52,7 +52,7 @@ public MchPath(@NotNull MchFileSystem fileSystem, @NotNull Path path) { * @return The non-mch path. */ @Contract("!null -> !null; null -> null") - private @Nullable Path unwrap(@Nullable Path path) { + public static @Nullable Path unwrap(@Nullable Path path) { if (path == null) { return null; } diff --git a/mch-fs/src/main/java/ca/bkaw/mch/fs/TestMain.java b/mch-fs/src/main/java/ca/bkaw/mch/fs/TestMain.java deleted file mode 100644 index deac14f..0000000 --- a/mch-fs/src/main/java/ca/bkaw/mch/fs/TestMain.java +++ /dev/null @@ -1,61 +0,0 @@ -package ca.bkaw.mch.fs; - -import ca.bkaw.mch.Sha1; -import ca.bkaw.mch.object.ObjectStorageTypes; -import ca.bkaw.mch.object.Reference20; -import ca.bkaw.mch.object.commit.Commit; -import ca.bkaw.mch.object.world.World; -import ca.bkaw.mch.object.worldcontainer.WorldContainer; -import ca.bkaw.mch.repository.MchRepository; -import ca.bkaw.mch.repository.TrackedWorld; - -import java.io.IOException; -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; - -public class TestMain { - public static void main(String[] args) throws IOException { - Path a = Path.of("/Users/Alvin/Documents/Programmering/mch-viewer-fabric-test/run/world/advancements/idontexist"); - - Files.newDirectoryStream(a).iterator().forEachRemaining((path) -> System.out.println(path)); - } - public static void main2(String[] args) throws IOException { - MchRepository repository = new MchRepository(Path.of("/Users/Alvin/Documents/mch/test2/mch")); - repository.readConfiguration(); - TrackedWorld trackedWorld = repository.getConfiguration().getTrackedWorld(Sha1.fromString("40a9e3d33e815f6da8c0054ba895b13280fe8683")); - - Reference20 commitRef = new Reference20<>(ObjectStorageTypes.COMMIT, Sha1.fromString("280742e4698392d1475de4dbe407917f440933d2")); - Commit commit = commitRef.resolve(repository); - - WorldContainer worldContainer = commit.getWorldContainer().resolve(repository); - - Reference20 worldRef = worldContainer.getWorld(trackedWorld.getId()); - World world = worldRef.resolve(repository); - - Path testPath = Path.of("run/mch-fs-test").toAbsolutePath(); - Files.createDirectories(testPath); - if (Files.list(testPath).count() > 0) { - System.err.println("Please empty " + testPath + " before running test."); - return; - } - - Map env = Map.of( - MchFileSystemProvider.REPO_ENV_KEY, repository, - MchFileSystemProvider.TRACKED_WORLD_ENV_KEY, trackedWorld, - MchFileSystemProvider.WORLD_ENV_KEY, world - ); - - URI uri = URI.create("mch:" + testPath); - - FileSystem fileSystem = FileSystems.newFileSystem(uri, env); - - Path path = fileSystem.getPath("."); - - System.out.println("Result:"); - System.out.println(Files.list(path).map(p -> p.toString()).toList()); - } -} diff --git a/mch-viewer/fabric/build.gradle.kts b/mch-viewer/fabric/build.gradle.kts index 072d975..673a9ce 100644 --- a/mch-viewer/fabric/build.gradle.kts +++ b/mch-viewer/fabric/build.gradle.kts @@ -1,6 +1,7 @@ plugins { - id("fabric-loom") version "1.5-SNAPSHOT" id("java") + id("fabric-loom") version "1.5-SNAPSHOT" + id("com.github.johnrengelman.shadow") version "7.0.0" } base { @@ -21,11 +22,11 @@ dependencies { modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") // Fantasy for runtime worlds - modImplementation("xyz.nucleoid:fantasy:${property("fantasy_version")}") + include(modImplementation("xyz.nucleoid:fantasy:${property("fantasy_version")}")!!) // mch dependencies - implementation(project(":mch")) - implementation(project(":mch-fs")) + implementation(project(":mch"))!! + implementation(project(":mch-fs"))!! compileOnly("org.jetbrains:annotations:24.0.1") } @@ -36,4 +37,35 @@ tasks { expand(mutableMapOf("version" to project.version)) } } -} \ No newline at end of file + + shadowJar { + archiveBaseName.set("mch-viewer-fabric-dev") + archiveClassifier.set("") + + dependencies { + exclude("net.fabricmc:.*") + exclude("xyz.nucleoid:fantasy") + include(dependency("ca.bkaw.mch:.*")) + include(dependency("com.github.luben:zstd-jni:.*")) + include(dependency("commons-net:commons-net:.*")) + include(dependency("com.hierynomus:sshj:.*")) + exclude("/mappings/*") + } + } + + val remappedShadowJar by registering(net.fabricmc.loom.task.RemapJarTask::class) { + dependsOn(shadowJar) + input = shadowJar.get().archiveFile + addNestedDependencies = true + archiveBaseName.set("mch-viewer-fabric") + } + + assemble { + dependsOn(remappedShadowJar) + } + + artifacts { + archives(remappedShadowJar) + shadow(shadowJar) + } +} diff --git a/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/HistoryCommand.java b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/HistoryCommand.java new file mode 100644 index 0000000..44f9132 --- /dev/null +++ b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/HistoryCommand.java @@ -0,0 +1,88 @@ +package ca.bkaw.mch.viewer.fabric; + +import ca.bkaw.mch.Sha1; +import ca.bkaw.mch.repository.MchRepository; +import ca.bkaw.mch.repository.TrackedWorld; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.Level; + +import java.io.IOException; +import java.nio.file.Path; + +public class HistoryCommand { + public static final SimpleCommandExceptionType NOT_VIEWING_HISTORY = new SimpleCommandExceptionType(Component.literal( + "You are not viewing the history right now." + )); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register( + Commands.literal("history") + .requires(ctx -> ctx.hasPermission(2)) + .executes(ctx -> { + try { + return test(ctx); + } catch (Throwable e) { + ctx.getSource().sendFailure(Component.literal(e.toString())); + e.printStackTrace(); + return 0; + } + }) + .then( + Commands.literal("log") + .executes(ctx -> { + try { + return log(ctx); + } catch (Throwable e) { + ctx.getSource().sendFailure(Component.literal(e.toString())); + e.printStackTrace(); + return 0; + } + }) + ) + ); + } + + public static HistoryView getHistoryView(CommandContext ctx) throws CommandSyntaxException { + MchViewerFabric mchViewer = MchViewerFabric.getInstance(); + ResourceKey levelKey = ctx.getSource().getLevel().dimension(); + HistoryView historyView = mchViewer.getHistoryView(levelKey); + if (historyView == null) { + throw NOT_VIEWING_HISTORY.create(); + } + return historyView; + } + + public static int test(CommandContext ctx) throws IOException { + MchViewerFabric mchViewer = MchViewerFabric.getInstance(); + + MinecraftServer server = ctx.getSource().getServer(); + MchRepository repository = new MchRepository(Path.of("/root/test/mch")); + repository.readConfiguration(); + TrackedWorld trackedWorld = repository.getConfiguration().getTrackedWorld(Sha1.fromString("d6cd92545d49add9a5157a6bc7647a59ea4b1c38")); + + HistoryView view = mchViewer.view(server, repository, trackedWorld); + + ctx.getSource().sendSuccess(() -> Component.literal( + "Created dimension with key " + view.getWorldHandle().getRegistryKey() + ), false); + + return 1; + } + + private static int log(CommandContext ctx) throws CommandSyntaxException { + HistoryView historyView = getHistoryView(ctx); + + // TODO + + return 1; + } + +} diff --git a/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/HistoryView.java b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/HistoryView.java new file mode 100644 index 0000000..e019e9f --- /dev/null +++ b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/HistoryView.java @@ -0,0 +1,155 @@ +package ca.bkaw.mch.viewer.fabric; + +import ca.bkaw.mch.Sha1; +import ca.bkaw.mch.fs.MchFileSystem; +import ca.bkaw.mch.fs.MchFileSystemProvider; +import ca.bkaw.mch.fs.MchPath; +import ca.bkaw.mch.object.ObjectStorageTypes; +import ca.bkaw.mch.object.Reference20; +import ca.bkaw.mch.object.commit.Commit; +import ca.bkaw.mch.object.dimension.Dimension; +import ca.bkaw.mch.object.world.World; +import ca.bkaw.mch.object.worldcontainer.WorldContainer; +import ca.bkaw.mch.repository.MchRepository; +import ca.bkaw.mch.repository.TrackedWorld; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.LevelStorageSource; +import xyz.nucleoid.fantasy.RuntimeWorldHandle; +import xyz.nucleoid.fantasy.mixin.MinecraftServerAccess; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class HistoryView { + private final MchRepository repository; + private final TrackedWorld trackedWorld; + private RuntimeWorldHandle worldHandle; + private ResourceLocation dimensionKey; + private Commit commit; + private Dimension dimensionView; + private MchFileSystem fileSystem; + private Path rootPath; + + public HistoryView(MinecraftServer server, MchRepository repository, TrackedWorld trackedWorld) throws IOException { + this.repository = repository; + this.trackedWorld = trackedWorld; + + // Reference20 headCommitRef = this.repository.getHeadCommit(); + // if (headCommitRef == null) { + // throw new IllegalArgumentException("Repository is empty"); + // } + + Reference20 commitRef = new Reference20<>(ObjectStorageTypes.COMMIT, Sha1.fromString("d73512c201f7358b34a77bce302f16f9b4a97d6d")); + + this.commit = commitRef.resolve(this.repository); + + this.setDimensionKey(Level.OVERWORLD.location()); + } + + /** + * Set the key of the dimension to view. + * + * @param dimensionKey The dimension key. + * @throws IOException If an I/O error occurs. + * @throws IllegalArgumentException If the dimension key was not present in the commit. + */ + public void setDimensionKey(ResourceLocation dimensionKey) throws IOException { + this.dimensionKey = dimensionKey; + World world = this.getWorld(); + Reference20 dimensionRef = world.getDimension(dimensionKey.toString()); + if (dimensionRef == null) { + throw new IllegalArgumentException( + "The dimension " + dimensionKey + " was not present in the commit." + ); + } + this.dimensionView = dimensionRef.resolve(this.repository); + this.update(); + } + + /** + * Get the {@link World} object from the current commit. + * + * @return The world object. + * @throws IOException If an I/O error occurs. + */ + public World getWorld() throws IOException { + WorldContainer worldContainer = this.commit.getWorldContainer().resolve(this.repository); + Reference20 worldRef = worldContainer.getWorld(this.trackedWorld.getId()); + if (worldRef == null) { + throw new IllegalStateException( + "The world " + this.trackedWorld.getId() + " (" + + this.trackedWorld.getName() + + ") was not present in the commit." + ); + } + return worldRef.resolve(this.repository); + } + + /** + * The commit currently being viewed. + * + * @return The commit. + */ + public Commit getCommit() { + return this.commit; + } + + /** + * Set the Fantasy {@link RuntimeWorldHandle} that this history view uses to render + * the history. + * + * @param world The world handle. + */ + public void setWorldHandle(RuntimeWorldHandle world) { + if (this.worldHandle != null) { + throw new IllegalStateException("Cannot change world handle."); + } + this.worldHandle = world; + } + + private void update() { + // TODO cause the game to clear chunk caches etc. + } + + public Path wrapPath(Path original) { + // We basically want to check of the "original" path starts with the root path. + // First, the "original" path needs to be made absolute since the root path is + // also absolute. + // The paths are also from different file system providers (default and mch), + // so we unwrap the root path (mch provider) to the default file system, so + // that they can be compared with the regular startsWith method. + // + Path normalized = original.toAbsolutePath().normalize(); + Path unwrapped = MchPath.unwrap(this.rootPath); + if (normalized.startsWith(unwrapped)) { + // The original path should be wrapped + return new MchPath(this.fileSystem, original); + } + return original; + } + + public void setupMchFs(MinecraftServer server, ResourceKey levelKey) throws IOException { + LevelStorageSource.LevelStorageAccess session = ((MinecraftServerAccess) server).getSession(); + + Path dimensionPath = session.getDimensionPath(levelKey).toAbsolutePath(); + Files.createDirectories(dimensionPath); + + this.fileSystem = MchFileSystemProvider.INSTANCE.newFileSystem( + dimensionPath, this.repository, this.trackedWorld, + this.dimensionKey.toString(), this.dimensionView + ); + this.rootPath = this.fileSystem.getPath(".").toAbsolutePath().normalize(); + } + + public boolean isReady() { + return this.fileSystem != null; + } + + public RuntimeWorldHandle getWorldHandle() { + return this.worldHandle; + } +} diff --git a/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/MchViewerFabric.java b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/MchViewerFabric.java index 040b7c9..75666a7 100644 --- a/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/MchViewerFabric.java +++ b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/MchViewerFabric.java @@ -1,10 +1,83 @@ package ca.bkaw.mch.viewer.fabric; +import ca.bkaw.mch.repository.MchRepository; +import ca.bkaw.mch.repository.TrackedWorld; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; +import xyz.nucleoid.fantasy.Fantasy; +import xyz.nucleoid.fantasy.RuntimeWorld; +import xyz.nucleoid.fantasy.RuntimeWorldConfig; +import xyz.nucleoid.fantasy.RuntimeWorldHandle; +import xyz.nucleoid.fantasy.util.VoidChunkGenerator; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.HashMap; +import java.util.Map; public class MchViewerFabric implements ModInitializer { + private static MchViewerFabric instance; + + private final Map, HistoryView> historyViews = new HashMap<>(); + @Override public void onInitialize() { + instance = this; + + CommandRegistrationCallback.EVENT.register( + (dispatcher, registryAccess, environment) -> HistoryCommand.register(dispatcher) + ); + } + + public static MchViewerFabric getInstance() { + return instance; + } + + public HistoryView view(MinecraftServer server, MchRepository repository, TrackedWorld trackedWorld) throws IOException { + HistoryView view = new HistoryView(server, repository, trackedWorld); + + Fantasy fantasy = Fantasy.get(server); + RuntimeWorldConfig config = new RuntimeWorldConfig() + .setGameRule(GameRules.RULE_DAYLIGHT, false) + .setGenerator(new VoidChunkGenerator( + server.registryAccess().registryOrThrow(Registries.BIOME) + )); + + RuntimeWorld.Constructor constructor = config.getWorldConstructor(); + config.setWorldConstructor((minecraftServer, levelKey, runtimeWorldConfig, style) -> { + // Now that we know the random key that we were given by Fantasy, we can + // get the dimension path. This path needs to be wrapped by mch-fs. + try { + view.setupMchFs(minecraftServer, levelKey); + } catch (IOException e) { + throw new UncheckedIOException("Failed to set up mch-fs", e); + } + this.historyViews.put(levelKey, view); + // Call the normal constructor and create the world. + return constructor.createWorld(minecraftServer, levelKey, runtimeWorldConfig, style); + }); + + // Create the world. + RuntimeWorldHandle worldHandle = fantasy.openTemporaryWorld(config); + view.setWorldHandle(worldHandle); + + return view; + } + /** + * Get a {@link HistoryView} associated with a level key. + * + * @param levelKey The level key. + * @return The history view. + */ + @Nullable + public HistoryView getHistoryView(ResourceKey levelKey) { + return this.historyViews.get(levelKey); } } \ No newline at end of file diff --git a/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/mixin/LevelStorageAccessMixin.java b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/mixin/LevelStorageAccessMixin.java index ba675a2..d94d237 100644 --- a/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/mixin/LevelStorageAccessMixin.java +++ b/mch-viewer/fabric/src/main/java/ca/bkaw/mch/viewer/fabric/mixin/LevelStorageAccessMixin.java @@ -1,21 +1,31 @@ package ca.bkaw.mch.viewer.fabric.mixin; +import ca.bkaw.mch.viewer.fabric.HistoryView; +import ca.bkaw.mch.viewer.fabric.MchViewerFabric; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import net.minecraft.world.level.storage.LevelStorageSource; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.nio.file.Path; @Mixin(LevelStorageSource.LevelStorageAccess.class) public class LevelStorageAccessMixin { - @Inject(method = "getDimensionPath", at = @At("RETURN")) - public void injected(ResourceKey resourceKey, CallbackInfoReturnable cir) { - Path path = cir.getReturnValue(); - System.out.println("getDimensionPath() = " + path); - Thread.dumpStack(); + @ModifyReturnValue(method = "getDimensionPath", at = @At("RETURN")) + public Path injected(Path original, ResourceKey levelKey) { + MchViewerFabric mchViewer = MchViewerFabric.getInstance(); + HistoryView historyView = mchViewer.getHistoryView(levelKey); + System.out.println(levelKey + ": " + (historyView == null ? "null" : historyView.isReady())); + if (historyView == null || !historyView.isReady()) { + return original; + } + + // The game requested the path of the dimension, but the dimension + // is an mch-viewer world. + + return historyView.wrapPath(original); } + }