Skip to content

Commit

Permalink
Move ModuleWithHooks to ModifierHookMap and make generic
Browse files Browse the repository at this point in the history
Will allow reuse with tool modules later
  • Loading branch information
KnightMiner committed Apr 15, 2024
1 parent 1a50db9 commit 904ebf1
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 191 deletions.
@@ -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;
Expand All @@ -18,6 +20,15 @@ private ModifierHooks() {}
/** Unmodifiable view of the hook map */
private static final Collection<ResourceLocation> HOOK_IDS = Collections.unmodifiableCollection(HOOKS.keySet());

/** Loadable instance for hooks */
public static final StringLoadable<ModifierHook<?>> 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 */

Expand Down
@@ -1,39 +1,43 @@
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;
import slimeknights.tconstruct.library.modifiers.ModifierHook;
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<ModuleWithHooks> modules;
public static final RecordLoadable<ComposableModifier> 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<WithHooks<ModifierModule>> modules;

/**
* Creates a new instance
Expand All @@ -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<ModuleWithHooks> modules) {
super(ModifierModule.createMap(modules), levelDisplay, tooltipDisplay, priority);
protected ComposableModifier(ModifierLevelDisplay levelDisplay, TooltipDisplay tooltipDisplay, int priority, List<WithHooks<ModifierModule>> modules, ErrorFactory error) {
super(ModifierHookMap.createMap(modules, error), levelDisplay, tooltipDisplay, priority);
this.modules = modules;
}

Expand All @@ -53,7 +57,7 @@ public static Builder builder() {
}

@Override
public IGenericLoader<? extends Modifier> getLoader() {
public RecordLoadable<ComposableModifier> getLoader() {
return LOADER;
}

Expand All @@ -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<ModuleWithHooks> modules) {
private static int computePriority(List<WithHooks<ModifierModule>> modules) {
// poll all modules to find who has a priority preference
List<ModifierModule> priorityModules = new ArrayList<>();
for (ModuleWithHooks module : modules) {
for (WithHooks<ModifierModule> module : modules) {
if (module.module().getPriority() != null) {
priorityModules.add(module.module());
}
Expand All @@ -91,74 +95,6 @@ private static int computePriority(List<ModuleWithHooks> modules) {
return Modifier.DEFAULT_PRIORITY;
}

public static IGenericLoader<ComposableModifier> 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<ModuleWithHooks> 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<ModuleWithHooks> 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)
Expand All @@ -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<ModuleWithHooks> modules = ImmutableList.builder();
private final ImmutableList.Builder<WithHooks<ModifierModule>> 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;
}

Expand All @@ -191,18 +127,18 @@ public final Builder addModules(ModifierModule... modules) {
@SuppressWarnings("UnusedReturnValue")
@SafeVarargs
public final <T extends ModifierModule> Builder addModule(T object, ModifierHook<? super T>... 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<ModuleWithHooks> modules = this.modules.build();
List<WithHooks<ModifierModule>> 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);
}
}
}
@@ -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<ModifierModule> LOADER = new GenericLoaderRegistry<>("Modifier Module", false);
/** Loadable for modules including hooks */
RecordLoadable<WithHooks<ModifierModule>> WITH_HOOKS = WithHooks.makeLoadable(LOADER);

/**
* Gets the priority for this module.
Expand All @@ -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<ModifierHook<?>> hooks) {
/** Gets the list of hooks to use for this module */
public List<ModifierHook<?>> 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<ModifierHook<?>> 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<ModifierHook<?>> 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<ModuleWithHooks> modules) {
ModifierHookMap.Builder builder = ModifierHookMap.builder();
for (ModuleWithHooks module : modules) {
for (ModifierHook<?> hook : module.getModuleHooks()) {
builder.addHookChecked(module.module(), hook);
}
}
return builder.build();
}
}

0 comments on commit 904ebf1

Please sign in to comment.