package io.gitlab.jfronny.respackopts; import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import io.gitlab.jfronny.commons.throwable.ThrowingConsumer; import io.gitlab.jfronny.commons.throwable.ThrowingSupplier; import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.context.ExprUtils; import io.gitlab.jfronny.muscript.core.LocationalException; import io.gitlab.jfronny.muscript.data.additional.DataExprMapper; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.parser.Parser; import io.gitlab.jfronny.muscript.runtime.Runtime; import io.gitlab.jfronny.muscript.serialize.Decompiler; import io.gitlab.jfronny.respackopts.muscript.ScopeVersion; import io.gitlab.jfronny.respackopts.util.MetaCache; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.Version; import net.minecraft.client.MinecraftClient; import net.minecraft.command.argument.IdentifierArgumentType; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import org.apache.commons.io.IOUtils; import java.io.*; import java.nio.file.*; import java.util.concurrent.CompletableFuture; import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asString; import static io.gitlab.jfronny.muscript.serialize.Decompiler.decompile; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; public class RpoClientCommand { private static final Version VERSION = FabricLoader.getInstance().getModContainer(Respackopts.ID).orElseThrow().getMetadata().getVersion(); public static void register() { ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { Command getVersion = ctx -> { ctx.getSource().sendFeedback(Text.translatable("respackopts.versionText", VERSION, Respackopts.META_VERSION)); return 1; }; Command dumpConfig = ctx -> { MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.toString(), branch.packId() + ".txt"))); return 1; }; Command dumpGlsl = ctx -> { ctx.getSource().sendFeedback(dump(RespackoptsClient.getShaderImportSource(), "frex.glsl")); return 1; }; Command dumpScope = ctx -> { ctx.getSource().sendFeedback(dump(MetaCache.getScope(Respackopts.META_VERSION), "_root.mu")); MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.executionScope().getOverrides(), branch.packId() + ".mu"))); return 1; }; Command dumpScopeVersioned = ctx -> { ctx.getSource().sendFeedback(dump(MetaCache.getScope(IntegerArgumentType.getInteger(ctx, "version")), "_root.mu")); MetaCache.forEach((id, branch) -> ctx.getSource().sendFeedback(dump(branch.executionScope().getOverrides(), branch.packId() + ".mu"))); return 1; }; Command dumpAsset = ctx -> { Identifier id = ctx.getArgument("asset", Identifier.class); ctx.getSource().sendFeedback(dump(() -> MinecraftClient.getInstance().getResourceManager().open(id), id.getNamespace() + "/" + id.getPath())); return 1; }; Command execute = ctx -> { String snippet = StringArgumentType.getString(ctx, "snippet"); try { int ver = IntegerArgumentType.getInteger(ctx, "version"); String result = Runtime.evaluate(asString(Parser.parse(ScopeVersion.by(ver).muScriptVersion, snippet, "snippet")), MetaCache.getScope(ver)); ctx.getSource().sendFeedback(Text.translatable("respackopts.snippet.success", result)); } catch (LocationalException | Parser.ParseException e) { Respackopts.LOGGER.error("Could not execute snippet", e); ctx.getSource().sendError(Text.translatable("respackopts.snippet.failed", e.getMessage())); } return 1; }; Command reload = ctx -> { MetaCache.clear(); CompletableFuture.allOf(RespackoptsClient.forceReloadResources(), RespackoptsClient.reloadIntegratedServerData()) .thenRun(() -> { ctx.getSource().sendFeedback(Text.translatable("respackopts.reloadSucceeded")); }).exceptionally(e -> { Respackopts.LOGGER.error("Could not reload resources", e); ctx.getSource().sendError(Text.translatable("respackopts.reloadFailed")); return null; }); return 1; }; dispatcher.register(literal("rpoc").executes(getVersion) .then(literal("dump").executes(dumpConfig) .then(literal("config").executes(dumpConfig)) .then(literal("scope").executes(dumpScope).then(argument("version", IntegerArgumentType.integer(1, Respackopts.META_VERSION)).executes(dumpScopeVersioned))) .then(literal("glsl").executes(dumpGlsl)) .then(literal("asset").then(argument("asset", IdentifierArgumentType.identifier()).executes(dumpAsset)))) .then(literal("execute").then(argument("version", IntegerArgumentType.integer(1, Respackopts.META_VERSION)).then(argument("snippet", StringArgumentType.greedyString()).executes(execute)))) .then(literal("version").executes(getVersion)) .then(literal("reload").executes(reload))); }); } private static Text dump(Dynamic dynamic, String fileName) { return dump(DataExprMapper.map(dynamic), fileName); } private static Text dump(Expr expr, String fileName) { return dump(decompile(expr), fileName); } private static final Path dumpPath = FabricLoader.getInstance().getGameDir().resolve("respackopts").toAbsolutePath(); private static Text dump(String text, String fileName) { return dump(path -> Files.writeString(path, text, StandardOpenOption.CREATE), fileName); } private static Text dump(ThrowingSupplier content, String fileName) { return dump(path -> { try (InputStream is = content.get(); OutputStream os = Files.newOutputStream(path, StandardOpenOption.CREATE)) { IOUtils.copy(is, os); } }, fileName); } private static Text dump(ThrowingConsumer content, String fileName) { try { Path filePath = dumpPath.resolve(fileName).normalize(); if (!filePath.startsWith(dumpPath)) throw new IOException("Illegal path"); Files.createDirectories(filePath.getParent()); content.accept(filePath); return Text.translatable("respackopts.dumpSucceeded", filePath.toAbsolutePath()); } catch (Throwable e) { Respackopts.LOGGER.error("Could not dump resource", e); return Text.translatable("respackopts.dumpFailed"); } } }