diff --git a/src/main/java/org/geysermc/packgenerator/CodecUtil.java b/src/main/java/org/geysermc/packgenerator/CodecUtil.java new file mode 100644 index 0000000..4896a91 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/CodecUtil.java @@ -0,0 +1,52 @@ +package org.geysermc.packgenerator; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.network.chat.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class CodecUtil { + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public static RecordCodecBuilder unitVerifyCodec(Codec codec, String field, T value) { + return codec.validate(read -> { + if (!read.equals(value)) { + return DataResult.error(() -> field + " must equal " + value); + } + return DataResult.success(read); + }).fieldOf(field).forGetter(object -> value); + } + + public static void trySaveJson(Codec codec, T object, Path path) throws IOException { + JsonElement json = codec.encodeStart(JsonOps.INSTANCE, object).getOrThrow(); + + try { + ensureDirectoryExists(path.getParent()); + Files.writeString(path, GSON.toJson(json)); + } catch (IOException exception) { + GeyserMappingsGenerator.LOGGER.warn("Failed to write file " + path + "!", exception); + throw exception; + } + } + + public static void ensureDirectoryExists(Path directory) throws IOException { + if (!Files.isDirectory(directory)) { + try { + Files.createDirectories(directory); + } catch (IOException exception) { + GeyserMappingsGenerator.LOGGER.warn("Failed to create directory!", exception); + throw exception; + } + } + } +} diff --git a/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java b/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java index 325b2d1..4e04352 100644 --- a/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java +++ b/src/main/java/org/geysermc/packgenerator/GeyserMappingsGenerator.java @@ -1,6 +1,7 @@ package org.geysermc.packgenerator; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.logging.LogUtils; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; @@ -9,6 +10,8 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import org.slf4j.Logger; +import java.io.IOException; + public class GeyserMappingsGenerator implements ClientModInitializer { public static final Logger LOGGER = LogUtils.getLogger(); @@ -22,7 +25,11 @@ public class GeyserMappingsGenerator implements ClientModInitializer { .then(ClientCommandManager.argument("name", StringArgumentType.word()) .executes(context -> { String name = StringArgumentType.getString(context, "name"); - PackManager.getInstance().startPack(name); + try { + PackManager.getInstance().startPack(name); + } catch (IOException exception) { + throw new SimpleCommandExceptionType(Component.literal(exception.getMessage())).create(); + } context.getSource().sendFeedback(Component.literal("Created pack with name " + name)); return 0; }) @@ -50,7 +57,11 @@ public class GeyserMappingsGenerator implements ClientModInitializer { ) .then(ClientCommandManager.literal("finish") .executes(context -> { - PackManager.getInstance().finish(); + try { + PackManager.getInstance().finish(); + } catch (IOException exception) { + throw new SimpleCommandExceptionType(Component.literal(exception.getMessage())).create(); + } context.getSource().sendFeedback(Component.literal("Wrote pack to disk")); return 0; }) diff --git a/src/main/java/org/geysermc/packgenerator/PackManager.java b/src/main/java/org/geysermc/packgenerator/PackManager.java index 9f6d0f9..9247044 100644 --- a/src/main/java/org/geysermc/packgenerator/PackManager.java +++ b/src/main/java/org/geysermc/packgenerator/PackManager.java @@ -1,39 +1,43 @@ package org.geysermc.packgenerator; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import com.mojang.serialization.JsonOps; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import org.geysermc.packgenerator.mappings.GeyserMappings; +import org.geysermc.packgenerator.pack.PackManifest; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; public final class PackManager { - public static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir() - .resolve("geyser"); + private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve("geyser"); + private static final Function PACK_DIRECTORY = path -> path.resolve("pack"); private static final PackManager INSTANCE = new PackManager(); private String currentPackName; private Path exportPath; + private Path packPath; private GeyserMappings mappings; + private PackManifest manifest; private PackManager() {} - public void startPack(String name) throws CommandSyntaxException { + public void startPack(String name) throws CommandSyntaxException, IOException { if (currentPackName != null) { throw new SimpleCommandExceptionType(Component.literal("Already started a pack with name " + currentPackName)).create(); } currentPackName = name; exportPath = createPackDirectory(currentPackName); + packPath = PACK_DIRECTORY.apply(exportPath); mappings = new GeyserMappings(); + manifest = new PackManifest(new PackManifest.Header(name, "PLACEHOLDER", UUID.randomUUID(), "PLACEHOLDER"), + List.of(new PackManifest.Module(new PackManifest.Header(name, "PLACEHOLDER", UUID.randomUUID(), "PLACEHOLDER")))); } public boolean map(ItemStack stack, boolean throwOnModelMissing) throws CommandSyntaxException { @@ -51,19 +55,11 @@ public final class PackManager { } } - public void finish() throws CommandSyntaxException { + public void finish() throws CommandSyntaxException, IOException { ensurePackIsCreated(); - JsonElement savedMappings = GeyserMappings.CODEC.encodeStart(JsonOps.INSTANCE, mappings) - .getOrThrow(error -> new SimpleCommandExceptionType(Component.literal("Failed to encode Geyser mappings! " + error)).create()); - - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - try { - Files.writeString(exportPath.resolve("geyser_mappings.json"), gson.toJson(savedMappings)); - } catch (IOException exception) { - GeyserMappingsGenerator.LOGGER.warn("Failed to write Geyser mappings to pack!", exception); - throw new SimpleCommandExceptionType(Component.literal("Failed to write Geyser mappings to pack!")).create(); - } + CodecUtil.trySaveJson(GeyserMappings.CODEC, mappings, exportPath.resolve("geyser_mappings.json")); + CodecUtil.trySaveJson(PackManifest.CODEC, manifest, packPath.resolve("manifest.json")); currentPackName = null; } @@ -74,16 +70,9 @@ public final class PackManager { } } - private static Path createPackDirectory(String name) throws CommandSyntaxException { + private static Path createPackDirectory(String name) throws IOException { Path path = EXPORT_DIRECTORY.resolve(name); - if (!Files.isDirectory(path)) { - try { - Files.createDirectories(path); - } catch (IOException exception) { - GeyserMappingsGenerator.LOGGER.warn("Failed to create pack export directory!", exception); - throw new SimpleCommandExceptionType(Component.literal("Failed to create pack export directory for pack " + name)).create(); - } - } + CodecUtil.ensureDirectoryExists(path); return path; } diff --git a/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java b/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java index c12e7d9..beadaf2 100644 --- a/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java +++ b/src/main/java/org/geysermc/packgenerator/mappings/GeyserMappings.java @@ -9,6 +9,7 @@ import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import org.geysermc.packgenerator.CodecUtil; import java.util.ArrayList; import java.util.Collection; @@ -22,7 +23,7 @@ public class GeyserMappings { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Codec.INT.fieldOf("format_version").forGetter(mappings -> 2), + CodecUtil.unitVerifyCodec(Codec.INT, "format_version", 2), MAPPINGS_CODEC.fieldOf("items").forGetter(GeyserMappings::mappings) ).apply(instance, (format, mappings) -> new GeyserMappings(mappings)) ); diff --git a/src/main/java/org/geysermc/packgenerator/pack/PackManifest.java b/src/main/java/org/geysermc/packgenerator/pack/PackManifest.java new file mode 100644 index 0000000..6ea1edf --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/PackManifest.java @@ -0,0 +1,44 @@ +package org.geysermc.packgenerator.pack; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.UUIDUtil; +import org.geysermc.packgenerator.CodecUtil; + +import java.util.List; +import java.util.UUID; + +// TODO metadata +public record PackManifest(Header header, List modules) { + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + CodecUtil.unitVerifyCodec(Codec.INT, "format_version", 2), + Header.CODEC.fieldOf("header").forGetter(PackManifest::header), + Module.CODEC.listOf().fieldOf("modules").forGetter(PackManifest::modules) + ).apply(instance, (formatVersion, header, modules) -> new PackManifest(header, modules)) + ); + + public record Header(String name, String description, UUID uuid, String version) { + public static final MapCodec
MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group( + Codec.STRING.fieldOf("name").forGetter(Header::name), + Codec.STRING.fieldOf("description").forGetter(Header::description), + UUIDUtil.STRING_CODEC.fieldOf("uuid").forGetter(Header::uuid), + Codec.STRING.fieldOf("version").forGetter(Header::version) + ).apply(instance, Header::new) + ); + public static final Codec
CODEC = MAP_CODEC.codec(); + } + + public record Module(Header header) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + CodecUtil.unitVerifyCodec(Codec.STRING, "type", "resources"), + Header.MAP_CODEC.forGetter(Module::header) + ).apply(instance, (type, header) -> new Module(header)) + ); + } +} +