From 904ebf1361f4d26df52af40e3b6a3b711f058b12 Mon Sep 17 00:00:00 2001 From: KnightMiner Date: Sun, 14 Apr 2024 21:59:51 -0400 Subject: [PATCH] Move ModuleWithHooks to ModifierHookMap and make generic Will allow reuse with tool modules later --- .../library/modifiers/ModifierHooks.java | 11 ++ .../modifiers/dynamic/ComposableModifier.java | 114 ++++-------------- .../modifiers/modules/ModifierModule.java | 104 +--------------- .../modifiers/util/ModifierHookMap.java | 52 +++++++- 4 files changed, 90 insertions(+), 191 deletions(-) diff --git a/src/main/java/slimeknights/tconstruct/library/modifiers/ModifierHooks.java b/src/main/java/slimeknights/tconstruct/library/modifiers/ModifierHooks.java index 20f10fcab3..f32349bb72 100644 --- a/src/main/java/slimeknights/tconstruct/library/modifiers/ModifierHooks.java +++ b/src/main/java/slimeknights/tconstruct/library/modifiers/ModifierHooks.java @@ -1,6 +1,8 @@ package slimeknights.tconstruct.library.modifiers; import net.minecraft.resources.ResourceLocation; +import slimeknights.mantle.data.loadable.Loadables; +import slimeknights.mantle.data.loadable.primitive.StringLoadable; import javax.annotation.Nullable; import java.util.Collection; @@ -18,6 +20,15 @@ private ModifierHooks() {} /** Unmodifiable view of the hook map */ private static final Collection HOOK_IDS = Collections.unmodifiableCollection(HOOKS.keySet()); + /** Loadable instance for hooks */ + public static final StringLoadable> LOADABLE = Loadables.RESOURCE_LOCATION.comapFlatMap((id, error) -> { + ModifierHook hook = getHook(id); + if (hook == null) { + throw error.create("Unknown modifier hook " + id); + } + return hook; + }, ModifierHook::getName); + /* Registry */ diff --git a/src/main/java/slimeknights/tconstruct/library/modifiers/dynamic/ComposableModifier.java b/src/main/java/slimeknights/tconstruct/library/modifiers/dynamic/ComposableModifier.java index 155ddf0e79..ec880ba8f3 100644 --- a/src/main/java/slimeknights/tconstruct/library/modifiers/dynamic/ComposableModifier.java +++ b/src/main/java/slimeknights/tconstruct/library/modifiers/dynamic/ComposableModifier.java @@ -1,19 +1,15 @@ package slimeknights.tconstruct.library.modifiers.dynamic; import com.google.common.collect.ImmutableList; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import io.netty.handler.codec.DecoderException; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; -import net.minecraft.util.GsonHelper; -import slimeknights.mantle.data.registry.GenericLoaderRegistry.IGenericLoader; -import slimeknights.mantle.util.JsonHelper; +import slimeknights.mantle.data.loadable.ErrorFactory; +import slimeknights.mantle.data.loadable.primitive.EnumLoadable; +import slimeknights.mantle.data.loadable.primitive.IntLoadable; +import slimeknights.mantle.data.loadable.record.RecordLoadable; import slimeknights.tconstruct.TConstruct; import slimeknights.tconstruct.library.modifiers.Modifier; import slimeknights.tconstruct.library.modifiers.ModifierEntry; @@ -21,19 +17,27 @@ import slimeknights.tconstruct.library.modifiers.TinkerHooks; import slimeknights.tconstruct.library.modifiers.impl.BasicModifier; import slimeknights.tconstruct.library.modifiers.modules.ModifierModule; -import slimeknights.tconstruct.library.modifiers.modules.ModifierModule.ModuleWithHooks; +import slimeknights.tconstruct.library.modifiers.util.ModifierHookMap; +import slimeknights.tconstruct.library.modifiers.util.ModifierHookMap.WithHooks; import slimeknights.tconstruct.library.modifiers.util.ModifierLevelDisplay; import slimeknights.tconstruct.library.tools.nbt.IToolStackView; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.stream.Collectors; /** Modifier consisting of many composed hooks, used in datagen as a serialized modifier. */ public class ComposableModifier extends BasicModifier { - private final List modules; + public static final RecordLoadable LOADER = RecordLoadable.create( + ModifierLevelDisplay.LOADER.defaultField("level_display", true, m -> m.levelDisplay), + new EnumLoadable<>(TooltipDisplay.class).defaultField("tooltip_display", TooltipDisplay.ALWAYS, true, m -> m.tooltipDisplay), + IntLoadable.ANY_FULL.defaultField("priority", Integer.MIN_VALUE, m -> m.priority), + ModifierModule.WITH_HOOKS.list(0).defaultField("modules", List.of(), m -> m.modules), + ErrorFactory.FIELD, + (level, tooltip, priority, modules, error) -> new ComposableModifier(level, tooltip, priority == Integer.MIN_VALUE ? computePriority(modules) : priority, modules, error)); + + private final List> modules; /** * Creates a new instance @@ -42,8 +46,8 @@ public class ComposableModifier extends BasicModifier { * @param priority If the value is {@link Integer#MIN_VALUE}, assumed unset for datagen * @param modules Modules for this modifier */ - protected ComposableModifier(ModifierLevelDisplay levelDisplay, TooltipDisplay tooltipDisplay, int priority, List modules) { - super(ModifierModule.createMap(modules), levelDisplay, tooltipDisplay, priority); + protected ComposableModifier(ModifierLevelDisplay levelDisplay, TooltipDisplay tooltipDisplay, int priority, List> modules, ErrorFactory error) { + super(ModifierHookMap.createMap(modules, error), levelDisplay, tooltipDisplay, priority); this.modules = modules; } @@ -53,7 +57,7 @@ public static Builder builder() { } @Override - public IGenericLoader getLoader() { + public RecordLoadable getLoader() { return LOADER; } @@ -63,10 +67,10 @@ public Component getDisplayName(IToolStackView tool, ModifierEntry entry) { } /** Computes the recommended priority for a set of modifier modules */ - private static int computePriority(List modules) { + private static int computePriority(List> modules) { // poll all modules to find who has a priority preference List priorityModules = new ArrayList<>(); - for (ModuleWithHooks module : modules) { + for (WithHooks module : modules) { if (module.module().getPriority() != null) { priorityModules.add(module.module()); } @@ -91,74 +95,6 @@ private static int computePriority(List modules) { return Modifier.DEFAULT_PRIORITY; } - public static IGenericLoader LOADER = new IGenericLoader<>() { - @Override - public ComposableModifier deserialize(JsonObject json) { - ModifierLevelDisplay level_display = ModifierLevelDisplay.LOADER.getIfPresent(json, "level_display"); - TooltipDisplay tooltipDisplay = TooltipDisplay.ALWAYS; - if (json.has("tooltip_display")) { - tooltipDisplay = JsonHelper.getAsEnum(json, "tooltip_display", TooltipDisplay.class); - } - List modules = JsonHelper.parseList(json, "modules", ModuleWithHooks::deserialize); - int priority; - if (json.has("priority")) { - priority = GsonHelper.getAsInt(json, "priority"); - } else { - priority = computePriority(modules); - } - - // convert illegal argument to json syntax, bit more expected in this context - // TODO: figure out the best way to do this in loadables - try { - return new ComposableModifier(level_display, tooltipDisplay, priority, modules); - } catch (IllegalArgumentException e) { - throw new JsonSyntaxException(e.getMessage(), e); - } - } - - @Override - public void serialize(ComposableModifier object, JsonObject json) { - json.add("level_display", ModifierLevelDisplay.LOADER.serialize(object.levelDisplay)); - json.addProperty("tooltip_display", object.tooltipDisplay.name().toLowerCase(Locale.ROOT)); - if (object.priority != Integer.MIN_VALUE) { - json.addProperty("priority", object.priority); - } - JsonArray modules = new JsonArray(); - for (ModuleWithHooks module : object.modules) { - modules.add(module.serialize()); - } - json.add("modules", modules); - } - - @Override - public ComposableModifier fromNetwork(FriendlyByteBuf buffer) { - ModifierLevelDisplay levelDisplay = ModifierLevelDisplay.LOADER.decode(buffer); - TooltipDisplay tooltipDisplay = buffer.readEnum(TooltipDisplay.class); - int priority = buffer.readInt(); - int moduleCount = buffer.readVarInt(); - ImmutableList.Builder builder = ImmutableList.builder(); - for (int i = 0; i < moduleCount; i++) { - builder.add(ModuleWithHooks.fromNetwork(buffer)); - } - try { - return new ComposableModifier(levelDisplay, tooltipDisplay, priority, builder.build()); - } catch (IllegalArgumentException e) { - throw new DecoderException(e.getMessage(), e); - } - } - - @Override - public void toNetwork(ComposableModifier object, FriendlyByteBuf buffer) { - ModifierLevelDisplay.LOADER.encode(buffer, object.levelDisplay); - buffer.writeEnum(object.tooltipDisplay); - buffer.writeInt(object.priority); - buffer.writeVarInt(object.modules.size()); - for (ModuleWithHooks module : object.modules) { - module.toNetwork(buffer); - } - } - }; - /** Builder for a composable modifier instance */ @SuppressWarnings("UnusedReturnValue") // it's a builder @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @@ -171,11 +107,11 @@ public static class Builder { /** {@link Integer#MIN_VALUE} is an internal value used to represent unset for datagen, to distinguish unset from {@link Modifier#DEFAULT_PRIORITY} */ @Setter private int priority = Integer.MIN_VALUE; - private final ImmutableList.Builder modules = ImmutableList.builder(); + private final ImmutableList.Builder> modules = ImmutableList.builder(); /** Adds a module to the builder */ public final Builder addModule(ModifierModule module) { - modules.add(new ModuleWithHooks(module, Collections.emptyList())); + modules.add(new WithHooks<>(module, Collections.emptyList())); return this; } @@ -191,18 +127,18 @@ public final Builder addModules(ModifierModule... modules) { @SuppressWarnings("UnusedReturnValue") @SafeVarargs public final Builder addModule(T object, ModifierHook... hooks) { - modules.add(new ModuleWithHooks(object, List.of(hooks))); + modules.add(new WithHooks<>(object, List.of(hooks))); return this; } /** Builds the final instance */ public ComposableModifier build() { - List modules = this.modules.build(); + List> modules = this.modules.build(); if (priority == Integer.MIN_VALUE) { // call computePriority if we did not set one so we get the warning if multiple modules wish to set the priority computePriority(modules); } - return new ComposableModifier(levelDisplay, tooltipDisplay, priority, modules); + return new ComposableModifier(levelDisplay, tooltipDisplay, priority, modules, ErrorFactory.RUNTIME); } } } diff --git a/src/main/java/slimeknights/tconstruct/library/modifiers/modules/ModifierModule.java b/src/main/java/slimeknights/tconstruct/library/modifiers/modules/ModifierModule.java index 526cb59df1..5f9f35c2f5 100644 --- a/src/main/java/slimeknights/tconstruct/library/modifiers/modules/ModifierModule.java +++ b/src/main/java/slimeknights/tconstruct/library/modifiers/modules/ModifierModule.java @@ -1,29 +1,19 @@ package slimeknights.tconstruct.library.modifiers.modules; -import com.google.common.collect.ImmutableList; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import io.netty.handler.codec.DecoderException; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; +import slimeknights.mantle.data.loadable.record.RecordLoadable; import slimeknights.mantle.data.registry.GenericLoaderRegistry; import slimeknights.mantle.data.registry.GenericLoaderRegistry.IHaveLoader; -import slimeknights.mantle.util.JsonHelper; import slimeknights.tconstruct.library.modifiers.Modifier; -import slimeknights.tconstruct.library.modifiers.ModifierHook; -import slimeknights.tconstruct.library.modifiers.ModifierHooks; -import slimeknights.tconstruct.library.modifiers.util.ModifierHookMap; +import slimeknights.tconstruct.library.modifiers.util.ModifierHookMap.WithHooks; import javax.annotation.Nullable; -import java.util.Collections; -import java.util.List; /** Interface for a module in a composable modifier. This is the serializable version of {@link ModifierHookProvider}. */ public interface ModifierModule extends IHaveLoader, ModifierHookProvider { /** Loader instance to register new modules. Note that loaders should not use the key "hooks" else composable modifiers will not parse */ GenericLoaderRegistry LOADER = new GenericLoaderRegistry<>("Modifier Module", false); + /** Loadable for modules including hooks */ + RecordLoadable> WITH_HOOKS = WithHooks.makeLoadable(LOADER); /** * Gets the priority for this module. @@ -40,90 +30,4 @@ public interface ModifierModule extends IHaveLoader, ModifierHookProvider { default Integer getPriority() { return null; } - - /** Represents a modifier module with a list of hooks */ - record ModuleWithHooks(ModifierModule module, List> hooks) { - /** Gets the list of hooks to use for this module */ - public List> getModuleHooks() { - if (hooks.isEmpty()) { - return module.getDefaultHooks(); - } - return hooks; - } - - /** Serializes this to a JSON object */ - public JsonObject serialize() { - JsonElement json = LOADER.serialize(module); - if (!json.isJsonObject()) { - throw new JsonSyntaxException("Serializers for modifier modules must return json objects"); - } - JsonObject object = json.getAsJsonObject(); - if (!this.hooks.isEmpty()) { - JsonArray hooks = new JsonArray(); - for (ModifierHook hook : this.hooks) { - hooks.add(hook.getName().toString()); - } - object.add("hooks", hooks); - } - return object; - } - - /** Deserializes a module with hooks from a JSON object */ - public static ModuleWithHooks deserialize(JsonObject json) { - // if there are no hooks in JSON, we use the default list from the module - List> hooks = Collections.emptyList(); - if (json.has("hooks")) { - hooks = JsonHelper.parseList(json, "hooks", (element, key) -> { - ResourceLocation name = JsonHelper.convertToResourceLocation(element, key) ; - ModifierHook hook = ModifierHooks.getHook(name); - if (hook == null) { - throw new JsonSyntaxException("Unknown modifier hook " + name); - } - return hook; - }); - } - ModifierModule module = LOADER.deserialize(json); - return new ModuleWithHooks(module, hooks); - } - - /** Writes this module to the buffer */ - public void toNetwork(FriendlyByteBuf buffer) { - buffer.writeVarInt(hooks.size()); - for (ModifierHook hook : hooks) { - buffer.writeResourceLocation(hook.getName()); - } - LOADER.encode(buffer, module); - } - - /** Reads this module from the buffer */ - public static ModuleWithHooks fromNetwork(FriendlyByteBuf buffer) { - int hookCount = buffer.readVarInt(); - ImmutableList.Builder> hooks = ImmutableList.builder(); - for (int i = 0; i < hookCount; i++) { - ResourceLocation location = buffer.readResourceLocation(); - ModifierHook hook = ModifierHooks.getHook(location); - if (hook == null) { - throw new DecoderException("Unknown modifier hook " + location); - } - hooks.add(hook); - } - ModifierModule module = LOADER.decode(buffer); - return new ModuleWithHooks(module, hooks.build()); - } - } - - /** - * Creates a modifier hook map from the given module list - * @param modules List of modules - * @return Modifier hook map - */ - static ModifierHookMap createMap(List modules) { - ModifierHookMap.Builder builder = ModifierHookMap.builder(); - for (ModuleWithHooks module : modules) { - for (ModifierHook hook : module.getModuleHooks()) { - builder.addHookChecked(module.module(), hook); - } - } - return builder.build(); - } } diff --git a/src/main/java/slimeknights/tconstruct/library/modifiers/util/ModifierHookMap.java b/src/main/java/slimeknights/tconstruct/library/modifiers/util/ModifierHookMap.java index 6573c6ab7c..3247675a22 100644 --- a/src/main/java/slimeknights/tconstruct/library/modifiers/util/ModifierHookMap.java +++ b/src/main/java/slimeknights/tconstruct/library/modifiers/util/ModifierHookMap.java @@ -3,14 +3,19 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.LinkedHashMultimap; import lombok.RequiredArgsConstructor; -import slimeknights.tconstruct.library.modifiers.Modifier; +import slimeknights.mantle.data.loadable.ErrorFactory; +import slimeknights.mantle.data.loadable.record.RecordLoadable; +import slimeknights.mantle.data.registry.GenericLoaderRegistry; +import slimeknights.mantle.data.registry.GenericLoaderRegistry.IHaveLoader; import slimeknights.tconstruct.library.modifiers.ModifierHook; +import slimeknights.tconstruct.library.modifiers.ModifierHooks; import slimeknights.tconstruct.library.modifiers.impl.BasicModifier; import slimeknights.tconstruct.library.modifiers.modules.ModifierHookProvider; import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -24,6 +29,24 @@ public class ModifierHookMap { /** Internal map of modifier hook to object. It's the caller's responsibility to make sure the object is valid for the hook */ private final Map,Object> modules; + /** + * Creates a modifier hook map from the given module list + * @param modules List of modules + * @return Modifier hook map + */ + public static ModifierHookMap createMap(List> modules, ErrorFactory error) { + if (modules.isEmpty()) { + return EMPTY; + } + Builder builder = builder(); + for (WithHooks module : modules) { + for (ModifierHook hook : module.getModuleHooks()) { + builder.addHookChecked(module.module(), hook, error); + } + } + return builder.build(); + } + /** Checks if a module is registered for the given hook */ public boolean hasHook(ModifierHook hook) { return modules.containsKey(hook); @@ -57,6 +80,7 @@ public static ModifierHookMap.Builder builder() { @SuppressWarnings("UnusedReturnValue") public static class Builder { + private final ErrorFactory ILLEGAL_ARGUMENT = IllegalArgumentException::new; /** Builder for the final map */ private final LinkedHashMultimap,Object> modules = LinkedHashMultimap.create(); @@ -67,10 +91,18 @@ private Builder() {} * @throws IllegalArgumentException if the hook type is invalid */ public Builder addHookChecked(Object object, ModifierHook hook) { + return addHookChecked(object, hook, ILLEGAL_ARGUMENT); + } + + /** + * Adds a module to the builder, validating it at runtime. Used for JSON parsing + * @throws RuntimeException if the hook is type in invalid matching the given factory + */ + public Builder addHookChecked(Object object, ModifierHook hook, ErrorFactory error) { if (hook.isValid(object)) { modules.put(hook, object); } else { - throw new IllegalArgumentException("Object " + object + " is invalid for hook " + hook); + throw error.create("Object " + object + " is invalid for hook " + hook); } return this; } @@ -140,4 +172,20 @@ public BasicModifier.Builder modifier() { return BasicModifier.Builder.builder(build()); } } + + /** Represents a modifier module with a list of hooks */ + public record WithHooks(T module, List> hooks) { + /** Gets the list of hooks to use for this module */ + public List> getModuleHooks() { + if (hooks.isEmpty()) { + return module.getDefaultHooks(); + } + return hooks; + } + + /** Makes a loadable for a module with hooks */ + public static RecordLoadable> makeLoadable(GenericLoaderRegistry loaderRegistry) { + return RecordLoadable.create(loaderRegistry.directField(WithHooks::module), ModifierHooks.LOADABLE.list(0).defaultField("hooks", List.of(), WithHooks::hooks), WithHooks::new); + } + } }