From 328bbbbeb441a18ccd13023aad4dffd21d789b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 9 Feb 2021 18:12:00 +0100 Subject: [PATCH] Add data driven item stack tag filtering when exposing them to VM via serialization. Closes #5. --- .../oc2/api/bus/device/object/Callbacks.java | 1 - .../java/li/cil/oc2/common/CommonSetup.java | 24 ++---- .../common/bus/device/data/FileSystems.java | 23 +++-- .../bus/device/rpc/RPCItemStackTagFilter.java | 83 +++++++++++++++++++ .../device/rpc/RPCItemStackTagFilters.java | 79 ++++++++++++++++++ .../rpc/RPCMethodParameterTypeAdapters.java | 9 ++ .../li/cil/oc2/common/integration/IMC.java | 11 ++- .../li/cil/oc2/common/network/Network.java | 2 +- .../serializers/ItemStackJsonSerializer.java | 7 +- .../cil/oc2/common/util/ServerScheduler.java | 2 +- .../item_tag_filters/minecraft/common.json | 9 ++ .../oc2/item_tag_filters/oc2/hard_drive.json | 6 ++ .../data/oc2/item_tag_filters/oc2/memory.json | 6 ++ 13 files changed, 224 insertions(+), 38 deletions(-) create mode 100644 src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilter.java create mode 100644 src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilters.java create mode 100644 src/main/resources/data/oc2/item_tag_filters/minecraft/common.json create mode 100644 src/main/resources/data/oc2/item_tag_filters/oc2/hard_drive.json create mode 100644 src/main/resources/data/oc2/item_tag_filters/oc2/memory.json diff --git a/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java b/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java index 2d1ffcdc..498cbe25 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java +++ b/src/main/java/li/cil/oc2/api/bus/device/object/Callbacks.java @@ -81,7 +81,6 @@ public final class Callbacks { public static boolean hasMethods(final Object object) { if (object instanceof Class) { return !getMethods((Class) object).isEmpty(); - } else { return !getMethods(object.getClass()).isEmpty(); } diff --git a/src/main/java/li/cil/oc2/common/CommonSetup.java b/src/main/java/li/cil/oc2/common/CommonSetup.java index 4c3163fe..c934c7fa 100644 --- a/src/main/java/li/cil/oc2/common/CommonSetup.java +++ b/src/main/java/li/cil/oc2/common/CommonSetup.java @@ -1,38 +1,33 @@ package li.cil.oc2.common; import li.cil.oc2.common.bus.device.data.FileSystems; +import li.cil.oc2.common.bus.device.rpc.RPCItemStackTagFilters; import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters; import li.cil.oc2.common.capabilities.Capabilities; import li.cil.oc2.common.integration.IMC; import li.cil.oc2.common.network.Network; import li.cil.oc2.common.serialization.BlobStorage; -import li.cil.oc2.common.serialization.serializers.DirectionJsonSerializer; -import li.cil.oc2.common.serialization.serializers.ItemStackJsonSerializer; import li.cil.oc2.common.util.ServerScheduler; import li.cil.oc2.common.vm.Allocator; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Direction; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent; import net.minecraftforge.fml.event.server.FMLServerStoppedEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; public final class CommonSetup { @SubscribeEvent public static void handleSetupEvent(final FMLCommonSetupEvent event) { Capabilities.initialize(); + FileSystems.initialize(); + IMC.initialize(); + Network.initialize(); + RPCItemStackTagFilters.initialize(); + RPCMethodParameterTypeAdapters.initialize(); + ServerScheduler.initialize(); - Network.setup(); - - FMLJavaModLoadingContext.get().getModEventBus().addListener(IMC::handleIMCMessages); MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerAboutToStart); MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerStopped); - MinecraftForge.EVENT_BUS.addListener(FileSystems::handleAddReloadListenerEvent); - ServerScheduler.register(); - - addBuiltinRPCMethodParameterTypeAdapters(); } /////////////////////////////////////////////////////////////////// @@ -46,9 +41,4 @@ public final class CommonSetup { Allocator.resetAndCheckLeaks(); FileSystems.reset(); } - - private static void addBuiltinRPCMethodParameterTypeAdapters() { - RPCMethodParameterTypeAdapters.addTypeAdapter(ItemStack.class, new ItemStackJsonSerializer()); - RPCMethodParameterTypeAdapters.addTypeAdapter(Direction.class, new DirectionJsonSerializer()); - } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/data/FileSystems.java b/src/main/java/li/cil/oc2/common/bus/device/data/FileSystems.java index 156452bc..5dc928e4 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/data/FileSystems.java +++ b/src/main/java/li/cil/oc2/common/bus/device/data/FileSystems.java @@ -2,7 +2,6 @@ package li.cil.oc2.common.bus.device.data; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import li.cil.oc2.api.API; import li.cil.oc2.common.vm.fs.LayeredFileSystem; import li.cil.oc2.common.vm.fs.ResourceFileSystem; import li.cil.sedna.fs.FileSystem; @@ -11,6 +10,7 @@ import net.minecraft.resources.IFutureReloadListener; import net.minecraft.resources.IResource; import net.minecraft.resources.IResourceManager; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.AddReloadListenerEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -26,6 +26,10 @@ public final class FileSystems { /////////////////////////////////////////////////////////////////// + public static void initialize() { + MinecraftForge.EVENT_BUS.addListener(FileSystems::handleAddReloadListenerEvent); + } + public static FileSystem getLayeredFileSystem() { return LAYERED_FILE_SYSTEM; } @@ -34,12 +38,12 @@ public final class FileSystems { LAYERED_FILE_SYSTEM.clear(); } - public static void handleAddReloadListenerEvent(final AddReloadListenerEvent event) { - event.addListener(FileSystemsReloadListener.INSTANCE); - } - /////////////////////////////////////////////////////////////////// + private static void handleAddReloadListenerEvent(final AddReloadListenerEvent event) { + event.addListener(ReloadListener.INSTANCE); + } + private static void reload(final IResourceManager resourceManager) { reset(); @@ -83,13 +87,8 @@ public final class FileSystems { /////////////////////////////////////////////////////////////////// - private static final class FileSystemsReloadListener implements IFutureReloadListener { - public static final FileSystemsReloadListener INSTANCE = new FileSystemsReloadListener(); - - @Override - public String getSimpleName() { - return API.MOD_ID + ":file_system"; - } + private static final class ReloadListener implements IFutureReloadListener { + public static final ReloadListener INSTANCE = new ReloadListener(); @Override public CompletableFuture reload(final IFutureReloadListener.IStage stage, final IResourceManager resourceManager, final IProfiler preparationsProfiler, final IProfiler reloadProfiler, final Executor backgroundExecutor, final Executor gameExecutor) { diff --git a/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilter.java b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilter.java new file mode 100644 index 00000000..ebaf2076 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilter.java @@ -0,0 +1,83 @@ +package li.cil.oc2.common.bus.device.rpc; + +import li.cil.oc2.common.util.NBTTagIds; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StringUtils; + +import javax.annotation.Nullable; +import java.util.Objects; + +public final class RPCItemStackTagFilter { + public ResourceLocation item; + public String[] tags; + + private String[][] paths; // Cache of resolved paths specified in tags. + + /////////////////////////////////////////////////////////////////// + + @Nullable + public CompoundNBT apply(final ItemStack stack, final CompoundNBT tag) { + if (stack.isEmpty() || tags == null) { + return null; + } + + if (item != null && !Objects.equals(stack.getItem().getRegistryName(), item)) { + return null; + } + + validatePaths(); + + final CompoundNBT filtered = new CompoundNBT(); + for (final String[] path : paths) { + final CompoundNBT filteredByPath = filterPath(path, tag); + if (filteredByPath != null) { + filtered.merge(filteredByPath); + } + } + + return filtered; + } + + /////////////////////////////////////////////////////////////////// + + @Nullable + private CompoundNBT filterPath(final String[] path, final CompoundNBT source) { + if (path.length == 0) { + return null; + } + + final CompoundNBT result = new CompoundNBT(); + + CompoundNBT currentSource = source; + CompoundNBT currentTarget = result; + for (int j = 0; j < path.length - 1; j++) { + final String segment = path[j]; + if (currentSource.contains(segment, NBTTagIds.TAG_COMPOUND)) { + currentSource = currentSource.getCompound(segment); + currentTarget.put(segment, new CompoundNBT()); + currentTarget = currentTarget.getCompound(segment); + } else { + return null; // Path mismatch, inner element is not a compound tag. + } + } + + if (!currentSource.contains(path[path.length - 1])) { + return null; // Cannot find tag at path. + } + + currentTarget.put(path[path.length - 1], currentSource.get(path[path.length - 1])); + + return result; + } + + private void validatePaths() { + paths = new String[tags.length][]; + for (int i = 0; i < tags.length; i++) { + if (!StringUtils.isNullOrEmpty(tags[i])) { + paths[i] = tags[i].split("\\."); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilters.java b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilters.java new file mode 100644 index 00000000..47ff1523 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCItemStackTagFilters.java @@ -0,0 +1,79 @@ +package li.cil.oc2.common.bus.device.rpc; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import net.minecraft.client.resources.JsonReloadListener; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.profiler.IProfiler; +import net.minecraft.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.AddReloadListenerEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Map; + +public final class RPCItemStackTagFilters { + private static final Logger LOGGER = LogManager.getLogger(); + private static final ArrayList FILTERS = new ArrayList<>(); + + /////////////////////////////////////////////////////////////////// + + public static void initialize() { + MinecraftForge.EVENT_BUS.addListener(RPCItemStackTagFilters::handleAddReloadListenerEvent); + } + + @Nullable + public static CompoundNBT getFilteredTag(final ItemStack stack, final CompoundNBT tag) { + final CompoundNBT result = new CompoundNBT(); + for (final RPCItemStackTagFilter filter : FILTERS) { + final CompoundNBT filtered = filter.apply(stack, tag); + if (filtered != null) { + result.merge(filtered); + } + } + + return result; + } + + /////////////////////////////////////////////////////////////////// + + private static void handleAddReloadListenerEvent(final AddReloadListenerEvent event) { + event.addListener(ReloadListener.INSTANCE); + } + + /////////////////////////////////////////////////////////////////// + + private static final class ReloadListener extends JsonReloadListener { + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(ResourceLocation.class, new ResourceLocation.Serializer()) + .create(); + + public static final ReloadListener INSTANCE = new ReloadListener(); + + public ReloadListener() { + super(GSON, "item_tag_filters"); + } + + @Override + protected void apply(final Map objects, final IResourceManager resourceManager, final IProfiler profiler) { + FILTERS.clear(); + + objects.forEach((location, element) -> { + try { + final RPCItemStackTagFilter filter = GSON.fromJson(element, RPCItemStackTagFilter.class); + if (filter != null) { + FILTERS.add(filter); + } + } catch (final Exception e) { + LOGGER.error("Failed loading item tag filter [{}].", location, e); + } + }); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCMethodParameterTypeAdapters.java b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCMethodParameterTypeAdapters.java index 3cd256c5..8d3aca2e 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCMethodParameterTypeAdapters.java +++ b/src/main/java/li/cil/oc2/common/bus/device/rpc/RPCMethodParameterTypeAdapters.java @@ -2,6 +2,10 @@ package li.cil.oc2.common.bus.device.rpc; import com.google.gson.GsonBuilder; import li.cil.oc2.api.imc.RPCMethodParameterTypeAdapter; +import li.cil.oc2.common.serialization.serializers.DirectionJsonSerializer; +import li.cil.oc2.common.serialization.serializers.ItemStackJsonSerializer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; import java.util.ArrayList; @@ -10,6 +14,11 @@ public final class RPCMethodParameterTypeAdapters { /////////////////////////////////////////////////////////////////// + public static void initialize() { + addTypeAdapter(ItemStack.class, new ItemStackJsonSerializer()); + addTypeAdapter(Direction.class, new DirectionJsonSerializer()); + } + public static void addTypeAdapter(final Class type, final Object typeAdapter) { addTypeAdapter(new RPCMethodParameterTypeAdapter(type, typeAdapter)); } diff --git a/src/main/java/li/cil/oc2/common/integration/IMC.java b/src/main/java/li/cil/oc2/common/integration/IMC.java index 3f6fb139..6dae49a9 100644 --- a/src/main/java/li/cil/oc2/common/integration/IMC.java +++ b/src/main/java/li/cil/oc2/common/integration/IMC.java @@ -6,6 +6,7 @@ import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters; import net.minecraft.util.Util; import net.minecraftforge.fml.InterModComms; import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,7 +25,15 @@ public final class IMC { return map; }); - public static void handleIMCMessages(final InterModProcessEvent event) { + /////////////////////////////////////////////////////////////////// + + public static void initialize() { + FMLJavaModLoadingContext.get().getModEventBus().addListener(IMC::handleIMCMessages); + } + + /////////////////////////////////////////////////////////////////// + + private static void handleIMCMessages(final InterModProcessEvent event) { event.getIMCStream().forEach(message -> { final Consumer method = METHODS.get(message.getMethod()); if (method != null) { diff --git a/src/main/java/li/cil/oc2/common/network/Network.java b/src/main/java/li/cil/oc2/common/network/Network.java index 90aeeb96..7f9fbbdb 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -26,7 +26,7 @@ public final class Network { /////////////////////////////////////////////////////////////////// - public static void setup() { + public static void initialize() { INSTANCE.messageBuilder(ComputerTerminalOutputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) .encoder(ComputerTerminalOutputMessage::toBytes) .decoder(ComputerTerminalOutputMessage::new) diff --git a/src/main/java/li/cil/oc2/common/serialization/serializers/ItemStackJsonSerializer.java b/src/main/java/li/cil/oc2/common/serialization/serializers/ItemStackJsonSerializer.java index fc37da27..31f5ebce 100644 --- a/src/main/java/li/cil/oc2/common/serialization/serializers/ItemStackJsonSerializer.java +++ b/src/main/java/li/cil/oc2/common/serialization/serializers/ItemStackJsonSerializer.java @@ -4,6 +4,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import li.cil.oc2.common.bus.device.rpc.RPCItemStackTagFilters; import li.cil.oc2.common.serialization.NBTToJsonConverter; import net.minecraft.item.ItemStack; @@ -16,10 +17,6 @@ public final class ItemStackJsonSerializer implements JsonSerializer return JsonNull.INSTANCE; } - final JsonElement json = NBTToJsonConverter.convert(src.serializeNBT()); - - // TODO Postprocessing, filter out tags that should not be visible to the VM. - - return json; + return NBTToJsonConverter.convert(RPCItemStackTagFilters.getFilteredTag(src, src.serializeNBT())); } } diff --git a/src/main/java/li/cil/oc2/common/util/ServerScheduler.java b/src/main/java/li/cil/oc2/common/util/ServerScheduler.java index 7d1adf5d..f4d27c6d 100644 --- a/src/main/java/li/cil/oc2/common/util/ServerScheduler.java +++ b/src/main/java/li/cil/oc2/common/util/ServerScheduler.java @@ -21,7 +21,7 @@ public final class ServerScheduler { /////////////////////////////////////////////////////////////////// - public static void register() { + public static void initialize() { MinecraftForge.EVENT_BUS.register(EventHandler.class); } diff --git a/src/main/resources/data/oc2/item_tag_filters/minecraft/common.json b/src/main/resources/data/oc2/item_tag_filters/minecraft/common.json new file mode 100644 index 00000000..9dd71933 --- /dev/null +++ b/src/main/resources/data/oc2/item_tag_filters/minecraft/common.json @@ -0,0 +1,9 @@ +{ + "tags": [ + "id", + "Count", + "tag.display", + "tag.Enchantments", + "tag.RepairCost" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/oc2/item_tag_filters/oc2/hard_drive.json b/src/main/resources/data/oc2/item_tag_filters/oc2/hard_drive.json new file mode 100644 index 00000000..47f8b9f4 --- /dev/null +++ b/src/main/resources/data/oc2/item_tag_filters/oc2/hard_drive.json @@ -0,0 +1,6 @@ +{ + "item": "oc2:hard_drive", + "tags": [ + "tag.oc2.size" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/oc2/item_tag_filters/oc2/memory.json b/src/main/resources/data/oc2/item_tag_filters/oc2/memory.json new file mode 100644 index 00000000..9e6a99c8 --- /dev/null +++ b/src/main/resources/data/oc2/item_tag_filters/oc2/memory.json @@ -0,0 +1,6 @@ +{ + "item": "oc2:memory", + "tags": [ + "tag.oc2.size" + ] +} \ No newline at end of file