diff --git a/lombok.config b/lombok.config new file mode 100644 --- /dev/null +++ b/lombok.config @@ -0,0 +1,1 @@ +lombok.accessors.fluent=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -dark-matter = "4.1.0-1.20.1-build.86" +dark-matter = "4.1.1-1.20.1-build.90" [libraries] fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version = "0.16.0" } diff --git a/src/main/java/me/melontini/commander/impl/Commander.java b/src/main/java/me/melontini/commander/impl/Commander.java --- a/src/main/java/me/melontini/commander/impl/Commander.java +++ b/src/main/java/me/melontini/commander/impl/Commander.java @@ -1,5 +1,6 @@ package me.melontini.commander.impl; +import static java.util.concurrent.CompletableFuture.*; import static net.minecraft.loot.context.LootContextParameters.*; import com.google.gson.JsonObject; @@ -10,12 +11,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; -import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; -import lombok.Cleanup; import lombok.Getter; import lombok.Setter; -import lombok.SneakyThrows; import lombok.experimental.Accessors; import lombok.extern.log4j.Log4j2; import me.melontini.commander.api.expression.LootContextParameterRegistry; @@ -33,7 +31,7 @@ import me.melontini.commander.impl.util.mappings.MappingKeeper; import me.melontini.commander.impl.util.mappings.MinecraftDownloader; import me.melontini.dark_matter.api.base.util.Exceptions; -import me.melontini.dark_matter.api.base.util.PrependingLogger; +import me.melontini.dark_matter.api.base.util.Result; import me.melontini.dark_matter.api.data.codecs.ExtraCodecs; import me.melontini.dark_matter.api.data.loading.ServerReloadersEvent; import me.melontini.dark_matter.api.minecraft.util.TextUtil; @@ -41,7 +39,6 @@ import net.fabricmc.fabric.api.attachment.v1.AttachmentType; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.minecraft.loot.condition.LootConditionType; import net.minecraft.loot.provider.number.LootNumberProviderType; import net.minecraft.loot.provider.number.LootNumberProviderTypes; @@ -57,8 +54,6 @@ @Accessors(fluent = true) @Log4j2 public class Commander { - - public static final PrependingLogger LOGGER = PrependingLogger.get(); public static final LootNumberProviderType ARITHMETICA_PROVIDER = LootNumberProviderTypes.register( @@ -141,7 +136,7 @@ if (BASE_PATH.getFileSystem().supportedFileAttributeViews().contains("dos")) Files.setAttribute(BASE_PATH, "dos:hidden", Boolean.TRUE, LinkOption.NOFOLLOW_LINKS); } catch (IOException ignored) { - LOGGER.warn("Failed to hide the .commander folder"); + log.warn("Failed to hide the .commander folder"); } } @@ -183,28 +178,34 @@ return; } - try { - CompletableFuture offTarget = - CompletableFuture.supplyAsync(MappingKeeper::loadOffTarget, Util.getMainWorkerExecutor()); - CompletableFuture offMojmap = CompletableFuture.runAsync( - MinecraftDownloader::downloadMappings, Util.getMainWorkerExecutor()) - .thenApplyAsync(unused -> MappingKeeper.loadOffMojmap(), Util.getMainWorkerExecutor()); - mappingKeeper = - new MappingKeeper(MappingKeeper.loadMojmapTarget(offMojmap.join(), offTarget.join())); - } catch (Throwable t) { - log.error( - "Failed to download and prepare mappings! Data access remapping will not work!!!", t); - mappingKeeper = - (cls, name) -> name; // Returning null will force it to traverse the hierarchy. - } + var offTarget = supplyAsync(MappingKeeper::loadOffTarget, Util.getMainWorkerExecutor()); + var offMojmap = runAsync(MinecraftDownloader::downloadMappings, Util.getMainWorkerExecutor()) + .thenApplyAsync(unused -> MappingKeeper.loadOffMojmap(), Util.getMainWorkerExecutor()); + + mappingKeeper = Exceptions.supplyAsResult(() -> + new MappingKeeper(MappingKeeper.loadMojmapTarget(offMojmap.join(), offTarget.join()))) + .ifErrPresent(t -> log.error( + "Failed to download and prepare mappings! Data access remapping will not work!!!", + Exceptions.unwrap(t))) + .flatmapErr(t -> Result.ok((cls, name) -> name)) + .value() + .orElseThrow(); } - @SneakyThrows(IOException.class) private static String getVersion() { - @Cleanup - var stream = new InputStreamReader( - MinecraftDownloader.class.getResourceAsStream("/version.json"), StandardCharsets.UTF_8); - JsonObject o = JsonParser.parseReader(stream).getAsJsonObject(); - return o.getAsJsonPrimitive("id").getAsString(); + return Exceptions.supplyAsResult(() -> { + try (var stream = new InputStreamReader( + MinecraftDownloader.class.getResourceAsStream("/version.json"), + StandardCharsets.UTF_8)) { + JsonObject o = JsonParser.parseReader(stream).getAsJsonObject(); + return o.getAsJsonPrimitive("id").getAsString(); + } + }) + .ifErrPresent(e -> { + throw new IllegalStateException( + "Failed to read 'version.json' included in the Minecraft jar!"); + }) + .value() + .orElseThrow(); } } diff --git a/src/main/java/me/melontini/commander/api/event/EventKey.java b/src/main/java/me/melontini/commander/api/event/EventKey.java --- a/src/main/java/me/melontini/commander/api/event/EventKey.java +++ b/src/main/java/me/melontini/commander/api/event/EventKey.java @@ -4,7 +4,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.experimental.Accessors; import me.melontini.commander.impl.Commander; import net.minecraft.loot.context.LootContext; import net.minecraft.util.Identifier; @@ -13,7 +12,6 @@ @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@Accessors(fluent = true) public final class EventKey { public static final EventKey LOOT_CONTEXT = create(Commander.id("loot_context")); diff --git a/src/main/java/me/melontini/commander/api/expression/Expression.java b/src/main/java/me/melontini/commander/api/expression/Expression.java --- a/src/main/java/me/melontini/commander/api/expression/Expression.java +++ b/src/main/java/me/melontini/commander/api/expression/Expression.java @@ -26,11 +26,21 @@ return this.eval(context, null); } + /** + * Evaluates expressions with additional parameters. + * Parameters must be consistent, if something is unavailable - use {@link Result#NULL}. + * Otherwise, expressions could start failing and using the {@code ?} operator will be impossible. + * + * @return The evaluation {@link Result}. + * @see #eval(LootContext) + */ Result eval(LootContext context, @Nullable Map parameters); String original(); interface Result { + + Result NULL = (Result) (Object) NullValue.of(); static Result convert(Object o) { return (Result) (Object) ReflectiveValueConverter.convert(o); diff --git a/src/main/java/me/melontini/commander/api/expression/LootContextParameterRegistry.java b/src/main/java/me/melontini/commander/api/expression/LootContextParameterRegistry.java --- a/src/main/java/me/melontini/commander/api/expression/LootContextParameterRegistry.java +++ b/src/main/java/me/melontini/commander/api/expression/LootContextParameterRegistry.java @@ -1,9 +1,9 @@ package me.melontini.commander.api.expression; +import lombok.NonNull; import me.melontini.commander.impl.event.data.types.ExtractionTypes; import net.minecraft.loot.context.LootContextParameter; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; /** * A registry for {@link LootContextParameter}. Parameters already come with an identifier, so specifying it separately is unnecessary. @@ -11,7 +11,7 @@ @ApiStatus.Experimental public class LootContextParameterRegistry { - public static void register(LootContextParameter @NotNull ... parameters) { + public static void register(LootContextParameter @NonNull ... parameters) { for (LootContextParameter parameter : parameters) { ExtractionTypes.register(parameter); } diff --git a/src/main/java/me/melontini/commander/api/util/EventExecutors.java b/src/main/java/me/melontini/commander/api/util/EventExecutors.java --- a/src/main/java/me/melontini/commander/api/util/EventExecutors.java +++ b/src/main/java/me/melontini/commander/api/util/EventExecutors.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Objects; import java.util.function.Supplier; +import lombok.NonNull; import lombok.experimental.UtilityClass; import me.melontini.commander.api.command.Command; import me.melontini.commander.api.event.EventContext; @@ -19,7 +20,7 @@ @UtilityClass public class EventExecutors { - public static void runVoid(EventType type, World world, Supplier supplier) { + public static void runVoid(EventType type, @NonNull World world, Supplier supplier) { if (world.isClient()) return; List subscribers = Objects.requireNonNull( @@ -34,7 +35,7 @@ } public static boolean runBoolean( - EventType type, boolean def, World world, Supplier supplier) { + EventType type, boolean def, @NonNull World world, Supplier supplier) { if (world.isClient()) return def; List subscribers = Objects.requireNonNull( @@ -53,12 +54,13 @@ return def; } - public static boolean runBoolean(EventType type, World world, Supplier supplier) { + public static boolean runBoolean( + EventType type, @NonNull World world, Supplier supplier) { return runBoolean(type, true, world, supplier); } public static > T runEnum( - EventType type, @Nullable T def, World world, Supplier supplier) { + EventType type, @Nullable T def, @NonNull World world, Supplier supplier) { if (world.isClient()) return def; List subscribers = Objects.requireNonNull( diff --git a/src/main/java/me/melontini/commander/impl/expression/LootContextDataAccessor.java b/src/main/java/me/melontini/commander/impl/expression/LootContextDataAccessor.java --- a/src/main/java/me/melontini/commander/impl/expression/LootContextDataAccessor.java +++ b/src/main/java/me/melontini/commander/impl/expression/LootContextDataAccessor.java @@ -31,9 +31,7 @@ public EvaluationValue getData(String variable, Token token, EvaluationContext context) throws EvaluationException { var supplier = varCache.get(variable); - if (supplier != null) - return supplier.apply( - (LootContext) context.context()[0]); + if (supplier != null) return supplier.apply((LootContext) context.context()[0]); var r = Identifier.validate(variable); if (r.error().isPresent()) { diff --git a/src/main/java/me/melontini/commander/impl/util/mappings/MappingKeeper.java b/src/main/java/me/melontini/commander/impl/util/mappings/MappingKeeper.java --- a/src/main/java/me/melontini/commander/impl/util/mappings/MappingKeeper.java +++ b/src/main/java/me/melontini/commander/impl/util/mappings/MappingKeeper.java @@ -1,15 +1,15 @@ package me.melontini.commander.impl.util.mappings; +import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.List; import java.util.Objects; import java.util.zip.InflaterInputStream; -import lombok.Cleanup; -import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; import me.melontini.commander.impl.Commander; +import me.melontini.dark_matter.api.base.util.MakeSure; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; @@ -24,58 +24,67 @@ public static final String NAMESPACE = FabricLoader.getInstance().getMappingResolver().getCurrentRuntimeNamespace(); - @SneakyThrows public static MemoryMappingTree loadMojmapTarget( MemoryMappingTree offMojmap, MemoryMappingTree offTarget) { log.info("Merging mappings..."); - var tree = new MemoryMappingTree(); - MemoryMappingTree temp = new MemoryMappingTree(); - offMojmap.accept(temp); - offTarget.accept(temp); - temp.accept(new MappingSourceNsSwitch(tree, "mojang", true)); - Objects.requireNonNull( - tree.getClass("net/minecraft/server/MinecraftServer"), + var merged = new MemoryMappingTree(); + try { + MemoryMappingTree temp = new MemoryMappingTree(); + offMojmap.accept(temp); + offTarget.accept(temp); + temp.accept(new MappingSourceNsSwitch(merged, "mojang", true)); + } catch (IOException e) { + throw new IllegalStateException("Failed to merge official and mojang mappings!", e); + } + + MakeSure.notNull( + merged.getClass("net/minecraft/server/MinecraftServer"), "No built-in MinecraftServer mapping?") - .setDstName("net/minecraft/server/MinecraftServer", tree.getNamespaceId(NAMESPACE)); - return tree; + .setDstName("net/minecraft/server/MinecraftServer", merged.getNamespaceId(NAMESPACE)); + return merged; } - @SneakyThrows @Nullable public static MemoryMappingTree loadOffMojmap() { log.info("Loading official->mojmap mappings..."); - var tree = new MemoryMappingTree(); - @Cleanup - var reader = new InputStreamReader( + var mappings = new MemoryMappingTree(); + try (var reader = new InputStreamReader( new InflaterInputStream( Files.newInputStream(Commander.COMMANDER_PATH.resolve("mappings/server_mappings.bin"))), - StandardCharsets.UTF_8); - MappingReader.read(reader, new MappingSourceNsSwitch(tree, "target")); - tree.setSrcNamespace("official"); - tree.setDstNamespaces(List.of("mojang")); - return tree; + StandardCharsets.UTF_8)) { + MappingReader.read(reader, new MappingSourceNsSwitch(mappings, "target")); + } catch (IOException e) { + throw new RuntimeException("Failed to load official-mojmap mappings!", e); + } + + mappings.setSrcNamespace("official"); + mappings.setDstNamespaces(List.of("mojang")); + return mappings; } - @SneakyThrows public static MemoryMappingTree loadOffTarget() { log.info("Loading official->{} mappings...", NAMESPACE); - var tree = new MemoryMappingTree(); - @Cleanup - var reader = new InputStreamReader( - Objects.requireNonNull( + try (var reader = new InputStreamReader( + MakeSure.notNull( MappingKeeper.class.getClassLoader().getResourceAsStream("mappings/mappings.tiny"), - "mappings/mappings.tiny is not available?"), - StandardCharsets.UTF_8); - MappingReader.read(reader, tree); - return tree; + "mappings/mappings.tiny is not available? Are you running a fork of Fabric?"), + StandardCharsets.UTF_8)) { + var tree = new MemoryMappingTree(); + MappingReader.read(reader, tree); + return tree; + } catch (IOException e) { + throw new IllegalStateException(("Failed to read official->%s mappings! " + + "Those mappings are provided by the loader and must be readable!") + .formatted(NAMESPACE)); + } } @Override public @Nullable String getFieldOrMethod(Class cls, String name) { - var clsData = - mojmapTarget.getClass(Type.getInternalName(cls), mojmapTarget.getNamespaceId(NAMESPACE)); + var clsData = mojmapTarget() + .getClass(Type.getInternalName(cls), mojmapTarget().getNamespaceId(NAMESPACE)); if (clsData == null) return null; for (MappingTree.MethodMapping method : clsData.getMethods()) { diff --git a/src/main/java/me/melontini/commander/impl/util/mappings/MinecraftDownloader.java b/src/main/java/me/melontini/commander/impl/util/mappings/MinecraftDownloader.java --- a/src/main/java/me/melontini/commander/impl/util/mappings/MinecraftDownloader.java +++ b/src/main/java/me/melontini/commander/impl/util/mappings/MinecraftDownloader.java @@ -1,8 +1,12 @@ package me.melontini.commander.impl.util.mappings; +import static me.melontini.dark_matter.api.base.util.Exceptions.runAsResult; +import static me.melontini.dark_matter.api.base.util.Exceptions.throwNow; + import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.URL; @@ -11,11 +15,10 @@ import java.nio.file.Path; import java.util.Objects; import java.util.zip.DeflaterOutputStream; -import lombok.Cleanup; -import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; import me.melontini.commander.impl.Commander; +import me.melontini.dark_matter.api.base.util.Exceptions; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.MappingWriter; import net.fabricmc.mappingio.format.MappingFormat; @@ -27,13 +30,19 @@ private static final URL MANIFEST = url("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"); + private static final String LICENSE = + """ + (c) 2020 Microsoft Corporation. + These mappings are provided "as-is" and you bear the risk of using them. + You may copy and use the mappings for development purposes, but you may not redistribute the mappings complete and unmodified. + Microsoft makes no warranties, express or implied, with respect to the mappings provided here. + Use and modification of this document or the source code (in any form) of Minecraft: Java Edition is governed by the Minecraft End User License Agreement available at https://account.mojang.com/documents/minecraft_eula. + """; - @SneakyThrows static URL url(String s) { - return new URL(s); + return Exceptions.process(s, URL::new); } - @SneakyThrows public static void downloadMappings() { Path mappings = Commander.COMMANDER_PATH.resolve("mappings/server_mappings.bin"); if (Files.exists(mappings)) return; @@ -44,29 +53,30 @@ .getAsString()); var parent = Objects.requireNonNull(mappings.getParent()); - Files.createDirectories(parent); + runAsResult(IOException.class, () -> Files.createDirectories(parent)) + .ifErrPresent( + e -> throwNow(new RuntimeException("Failed to create mapping directories!", e))); log.info("Downloading {}...", Objects.requireNonNull(mappings.getFileName()).toString()); - Files.writeString( - parent.resolve("LICENSE.txt"), - """ - (c) 2020 Microsoft Corporation. - These mappings are provided "as-is" and you bear the risk of using them. - You may copy and use the mappings for development purposes, but you may not redistribute the mappings complete and unmodified. - Microsoft makes no warranties, express or implied, with respect to the mappings provided here. - Use and modification of this document or the source code (in any form) of Minecraft: Java Edition is governed by the Minecraft End User License Agreement available at https://account.mojang.com/documents/minecraft_eula. - """); - - @Cleanup var reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8); - @Cleanup var outputStream = Files.newOutputStream(mappings); + runAsResult(IOException.class, () -> Files.writeString(parent.resolve("LICENSE.txt"), LICENSE)) + .ifErrPresent(e -> { + log.error("Failed to write license to file! Logging instead...", e); + log.info(LICENSE); + }); MemoryMappingTree tree = new MemoryMappingTree(); - MappingReader.read(reader, tree); + try (var reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) { + MappingReader.read(reader, tree); + } catch (IOException e) { + throw new RuntimeException("Failed to read downloaded mojang mappings! Corrupt download?", e); + } - @Cleanup - var output = - new OutputStreamWriter(new DeflaterOutputStream(outputStream), StandardCharsets.UTF_8); - tree.accept(MappingWriter.create(output, MappingFormat.TSRG_2_FILE)); + try (var output = new OutputStreamWriter( + new DeflaterOutputStream(Files.newOutputStream(mappings)), StandardCharsets.UTF_8)) { + tree.accept(MappingWriter.create(output, MappingFormat.TSRG_2_FILE)); + } catch (IOException e) { + throw new RuntimeException("Failed to write compressed mappings!", e); + } } public static JsonObject getManifest() { @@ -80,9 +90,11 @@ throw new IllegalStateException("Unknown version '%s'".formatted(Commander.MINECRAFT_VERSION)); } - @SneakyThrows private static JsonObject downloadObject(URL url) { - @Cleanup var reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8); - return JsonParser.parseReader(reader).getAsJsonObject(); + try (var reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) { + return JsonParser.parseReader(reader).getAsJsonObject(); + } catch (IOException e) { + throw new RuntimeException("Failed to download JSON object from %s".formatted(url)); + } } } diff --git a/src/main/java/me/melontini/commander/impl/event/data/types/ExtractionTypes.java b/src/main/java/me/melontini/commander/impl/event/data/types/ExtractionTypes.java --- a/src/main/java/me/melontini/commander/impl/event/data/types/ExtractionTypes.java +++ b/src/main/java/me/melontini/commander/impl/event/data/types/ExtractionTypes.java @@ -3,6 +3,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.mojang.serialization.Codec; +import lombok.NonNull; import me.melontini.dark_matter.api.data.codecs.ExtraCodecs; import net.minecraft.loot.context.LootContextParameter; import net.minecraft.util.Identifier; @@ -19,7 +20,7 @@ return KNOWN_PARAMETERS.get(identifier); } - public static void register(LootContextParameter parameter) { + public static void register(@NonNull LootContextParameter parameter) { var old = KNOWN_PARAMETERS.put(parameter.getId(), parameter); if (old != null) throw new IllegalStateException("Already registered context %s".formatted(parameter.getId())); diff --git a/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java b/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java --- a/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java +++ b/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java @@ -28,7 +28,7 @@ @Override public @Nullable EvaluationValue getData(String variable, Token token, EvaluationContext context) throws EvaluationException { - var attr = registryAccess.getCache().apply(variable); + var attr = registryAccess.cache().apply(variable); if (attr == null) return null; return NumberValue.of(BigDecimal.valueOf(container.getValue((EntityAttribute) attr))); }