diff --git a/src/main/java/com/gtnewhorizons/postea/api/BlockNameAndMeta.java b/src/main/java/com/gtnewhorizons/postea/api/BlockNameAndMeta.java
new file mode 100644
index 0000000..18f02ca
--- /dev/null
+++ b/src/main/java/com/gtnewhorizons/postea/api/BlockNameAndMeta.java
@@ -0,0 +1,22 @@
+package com.gtnewhorizons.postea.api;
+
+/**
+ * A block referenced by its namespaced id, together with a meta value. Returned by
+ * {@link BlockReplacementManager#transformBlock(String, int)}.
+ */
+public final class BlockNameAndMeta {
+
+ /**
+ * The namespaced id of the block.
+ */
+ public final String name;
+ /**
+ * The meta value of the block.
+ */
+ public final int metadata;
+
+ public BlockNameAndMeta(String name, int metadata) {
+ this.name = name;
+ this.metadata = metadata;
+ }
+}
diff --git a/src/main/java/com/gtnewhorizons/postea/api/BlockReplacementManager.java b/src/main/java/com/gtnewhorizons/postea/api/BlockReplacementManager.java
index 168362d..a25af50 100644
--- a/src/main/java/com/gtnewhorizons/postea/api/BlockReplacementManager.java
+++ b/src/main/java/com/gtnewhorizons/postea/api/BlockReplacementManager.java
@@ -3,6 +3,7 @@ package com.gtnewhorizons.postea.api;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import net.minecraft.block.Block;
import net.minecraftforge.oredict.OreDictionary;
@@ -54,6 +55,32 @@ public abstract class BlockReplacementManager {
// endregion custom transformer registration
+ // region lookup
+
+ /**
+ * Runs the block transformers registered for the given id on a block that isn't placed in the world, and returns
+ * what it would be transformed into.
+ *
+ * This is meant for mods that store blocks by their namespaced id inside their own NBT (e.g.: blocks that are
+ * made out of other blocks), which Postea can't reach while converting chunks. Calling this when such a block is
+ * loaded applies every migration registered to Postea to it.
+ *
+ * @apiNote Only call this while a world is loaded, transformers rely on the numeric ids resolved for that world.
+ * The transformers are given a {@link com.gtnewhorizons.postea.utility.BlockConversionInfo} without a
+ * world, and with all coordinates set to 0. Tile entity transformers are not run.
+ *
+ * @param name The namespaced id of the block.
+ * @param metadata The meta value of the block.
+ * @return The namespaced id and meta value of the block to use instead, or null if no transformer applies.
+ */
+ @SuppressWarnings("unused")
+ public static @Nullable BlockNameAndMeta transformBlock(@Nonnull String name, int metadata) {
+ if (name == null) throw new NullPointerException("name cannot be null");
+ return TransformerRegistry.transformBlock(name, metadata);
+ }
+
+ // endregion lookup
+
// region id resolving
// spotless:off
diff --git a/src/main/java/com/gtnewhorizons/postea/utility/TransformerRegistry.java b/src/main/java/com/gtnewhorizons/postea/utility/TransformerRegistry.java
index 8655e7c..1fbd0ae 100644
--- a/src/main/java/com/gtnewhorizons/postea/utility/TransformerRegistry.java
+++ b/src/main/java/com/gtnewhorizons/postea/utility/TransformerRegistry.java
@@ -6,6 +6,7 @@ import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
@@ -13,6 +14,7 @@ import net.minecraft.world.chunk.Chunk;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.LinkedListMultimap;
+import com.gtnewhorizons.postea.api.BlockNameAndMeta;
import com.gtnewhorizons.postea.api.IBlockTransformationHandler;
import com.gtnewhorizons.postea.api.IDExtenderCompat;
import com.gtnewhorizons.postea.api.IItemStackTransformationHandler;
@@ -20,6 +22,7 @@ import com.gtnewhorizons.postea.api.TriFunction;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.LoaderState;
+import cpw.mods.fml.common.registry.GameData;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
/**
@@ -156,6 +159,32 @@ public class TransformerRegistry {
return null;
}
+ public static @Nullable BlockNameAndMeta transformBlock(@Nonnull String name, int metadata) {
+ List handlers = BLOCK_REPLACEMENT_MAP.get(name);
+ if (handlers.isEmpty()) return null;
+ // there is no position nor world, the block isn't placed anywhere
+ BlockConversionInfo blockConversionInfo = new BlockConversionInfo(
+ name,
+ IDRegistry.getBlockId(name),
+ metadata,
+ 0,
+ 0,
+ 0,
+ null);
+ for (IBlockTransformationHandler transformer : handlers) {
+ if (transformer.apply(blockConversionInfo)) {
+ Block block = GameData.getBlockRegistry()
+ .getRaw(blockConversionInfo.blockID);
+ if (block == null) return null;
+ return new BlockNameAndMeta(
+ GameData.getBlockRegistry()
+ .getNameForObject(block),
+ blockConversionInfo.metadata);
+ }
+ }
+ return null;
+ }
+
public static void transformItem(NBTTagCompound tag) {
// abort early if tag is bad.
if (tag.hasNoTags() || !tag.hasKey("id")) return;