TileEntityReplacementManager API
This API provides a suite of tools for transforming blocks and items in the game, allowing developers to replace or modify game elements based on specific conditions.
Features
- Tile Entity Transformation: Replace or modify tile entities.
- Block Replacement: Replace normal blocks with another normal block.
- ItemStack Replacement: Modify or replace items.
- Simple Migrations: An API to register large amounts of simple stack and block transformations which can even replace things from a removed mod.
- Missing Mapping Replacement API: An API to register transformations that can be resolved at the FMLMissingMapping event.
- Numeric ID Identification for Removed Content: A way to identify content that has ceased to exist and is in need of a transformation.
Examples
1. Tile Entity to Regular Block Transformation
The TileEntityReplacementManager class can be used to register tile entity transformer functions for specific tile
entities. In this example, we look for any tile entity with the ID of GT_TileEntity_Ores and replace it with are
regular block with no tile entities. This is specified by returning a BlockInfo that has no nbt transformation
function.
Note
Tile entity transformers are always applied before any block transformers.
Caution
These transformers are executed as the chunk data is being read and so the world being passed isn't fully loaded. If you try to perform any kind of block access using it, it will throw an error.
If you need to perform any kind of block access inside of tile entity transformer, Use the endpoints provided by
BlockAccessCompat. These endpoints will fetch the ID and meta value of the block where your tile entity is located. You can then compare it to a value obtained from a callback registered usingBlockReplacementManager.registerIDResolverto match the block in question.If you try to use the chunk, to get a value, you may get an incorrect value if the block that was previously located at the location of the tile entity no longer exists. previously mentioned API endpoints are able to overcome this issue and will allow you to content that no longer exists without the use of a FMLMissingMapping remap.
Caution
It's considered bad practice to try to access any other block than the one at the location of the tile entity being transformed. This is because the block you're trying to access may not be in the chunk passed to the transformer or the subchunk currently being processed, will result in an error when you try to access it.
Code example:
public abstract class TEToBlockExample {
public static void postLoad() {
TileEntityReplacementManager.tileEntityTransformer("GT_TileEntity_Ores", (tag, world, chunk) -> {
if (world.provider.dimensionId == -1) {
// nether gets orange wool
return new BlockInfo(Blocks.wool, 1);
}
if (world.provider.dimensionId == 1) {
// the end gets magenta wool
return new BlockInfo(Blocks.wool, 2);
}
// all other dimensions get light blue wool
return new BlockInfo(Blocks.wool, 3);
});
}
}
2. Tile Entity Transformations
If you just need to edit an existing tile entity, Postea can also do this. Postea is only ran once per chunk so it's
technically more efficient to use postea to do data migrations since your TE's own code won't have to do extra checks
every time it reads its own NBT data. This can be achieved by modifying the data in the transformation lambda and
returning a null value from the transformer lambda.
Note
Technically you can also return a
BlockInfowith the same block and meta value, and a pass though function for the NBT transformer, but this is considered bad practice for pure NBT transformations. Returningnulltells Postea that your transformer didn't modify the block, and that any other transformers also targetting that tile entity should also be applied to it.You should also note that when multiple tile entity transformers are registered to a single tile entity ID, they will always be executed in order of their registrations.
Important
Remember that unlike items, tile entities manually store every tag of their NBT data individually, meaning that any additional tags will be discarded and cannot be used to store additional data unless it is already a part of the new tile entity.
Code example:
public abstract class TEModifyExample {
private static int reedsId = -1;
private static int diamondId = -1;
private static int stoneId = -1;
private static int record11Id = -1;
public static void postLoad() {
// pre caching ids like this will lead to faster transformers
ItemStackReplacementManager.registerIDResolver("minecraft:reeds", i -> reedsId = i);
ItemStackReplacementManager.registerIDResolver("minecraft:diamond", i -> diamondId = i);
ItemStackReplacementManager.registerIDResolver("minecraft:stone", i -> stoneId = i);
TileEntityReplacementManager.tileEntityTransformer("Chest", (tag, world, chunk) -> {
// This transformer replaces all sugar cane stacks inside a chest with
// stone, unless it's in the 13th slot at which point it's replaced with
// diamonds. All transformations are done without affecting the stack size.
NBTTagList slots = tag.getTagList("Items", 10);
for (int i = 0; i < slots.tagCount(); i++) {
NBTTagCompound slot = slots.getCompoundTagAt(i);
// only target the removed item
if (IDExtenderCompat.getItemStackID(slot) != reedsId) continue;
if (slot.getByte("Slot") == 13) {
// replace it with diamonds if it's in slot 13
IDExtenderCompat.setItemStackID(slot, diamondId);
}
else {
// else replace it with stone.
IDExtenderCompat.setItemStackID(slot, stoneId);
}
}
return null;
});
// this tile entity transformer also executes because the other transformer returned null
ItemStackReplacementManager.registerIDResolver("minecraft:record_11", i -> record11Id = i);
byte targetSLot = (byte) 12;
TileEntityReplacementManager.tileEntityTransformer("Chest", (tag, world, chunk) -> {
// This transformer looks for the 12th slot of the chest and replaces
// or sets it's content to a new stack of record 11 with a stack size of 1.
NBTTagList slots = tag.getTagList("Items", 10);
NBTTagCompound toEdit = null;
for (int i = 0; i < slots.tagCount(); i++) {
NBTTagCompound slot = slots.getCompoundTagAt(i);
if (slot.getByte("Slot") != targetSLot) continue;
toEdit = slot;
break;
}
if (toEdit == null) {
toEdit = new NBTTagCompound();
toEdit.setByte("Slot", (byte) targetSLot);
slots.appendTag(toEdit);
}
toEdit.setByte("Count", (byte) 1);
toEdit.setShort("Damage", (short) 0);
IDExtenderCompat.setItemStackID(toEdit, stoneId);
return null;
});
}
}
3. Full Tile Entity Replacements
If you need to fully replace a tile entity with another one, your tile entity transformer should return a BlockInfo
that includes an NBT transformation callback. When you're not doing a full replacement, doing NBT outside the NBT
transformation callback is fine, but if you're doing a full replacement it's considered bad practice to do so.
Tip
If your NBT transformer returns
null, the previous tile entity data will simply be removed. Then what ever block you specified can now simply create its own tile entity from scratch when it's loaded.
Code example:
public abstract class TEFullReplacementExample {
public static int litFurnaceId = -1;
public static int unlitFurnaceId = -1;
public static int fireChargeId = -1;
public static void postLoad() {
BlockReplacementManager.registerIDResolver("minecraft:lit_furnace", i -> litFurnaceId = i);
BlockReplacementManager.registerIDResolver("minecraft:furnace", i -> unlitFurnaceId = i);
ItemStackReplacementManager.registerIDResolver("minecraft:fire_charge", i -> fireChargeId = i);
TileEntityReplacementManager.tileEntityTransformer("Furnace", TEFullReplacementExample::transformFurnace);
}
/**
* This transformer transforms a furnace into a chest, and adds a fire charge in the 4th slot of the chest if
* the block where the tile entity once stood was a lit furnace. If it's not either a lit or unlit furnace,
* the block is simply transformed into air.
*
* @param tag The NBT data of the tile entity being transformed.
* @param world The world in which the tile entity is located.
* @param chunk The chunk in which the tile entity is located.
* @return Null if we didn't update the block, else a BlockInfo with the transformation data you wish to
* execute.
*/
private static BlockInfo transformFurnace(NBTTagCompound tag, World world, Chunk chunk) {
int[] blockIdAndMeta = BlockAccessCompat.getBlockIDAndMetaAtTE(tag, chunk);
int blockId = blockIdAndMeta[0];
int meta = blockIdAndMeta[1];
// we return a value this time because we are actually replacing the block and tile entity.
// we copy the meta value since that preserves the orientation for these blocks.
return new BlockInfo(Blocks.chest, meta, oldTag -> transformFurnaceNBT(oldTag, blockId));
}
/**
* Moves the content of the furnace to the chest and adds a fire charge in the 4th slot if the furnace was lit.
*
* @param oldTag The current NBT data ofthe tile entity being transformed.
* @return null if we want the tile entity to be erased.
*/
private static NBTTagCompound transformFurnaceNBT(NBTTagCompound oldTag, int blockId) {
// if the block wasn't a furnace, clear the current tile entity and let the chest spawn a new one.
if (blockId != litFurnaceId && blockId != unlitFurnaceId) return null;
// use this function to create a new tile entity tag with the same positional data as the old one.
NBTTagCompound newTag = TileEntityReplacementManager.createTETagAtSamePosition("Chest", oldTag);
NBTTagList newSlots = (NBTTagList) oldTag.getTagList("Items", 10)
.copy();
// if the block used to be a lit furnace add a fire charge in the 4th slot.
if (blockId == litFurnaceId) {
NBTTagCompound fireChargeStack = new NBTTagCompound();
IDExtenderCompat.setItemStackID(fireChargeStack, fireChargeId);
fireChargeStack.setByte("Slot", (byte) 4);
fireChargeStack.setByte("Count", (byte) 1);
fireChargeStack.setShort("Damage", (short) 0);
newSlots.appendTag(fireChargeStack);
}
newTag.setTag("Items", newSlots);
return newTag;
}
}
4. Simple Block and Item Replacements
If you need to transform an existing block or item into something else, Postea now features a set of API end points that let you register "Simple Transformations". When applicable, we recommend using these endpoints as they will generally result in a faster execution time than if you were to implement your own handler for any given item. Another advantage is that multiple, completely distinct, mods can now target the same item id for a simple transformation with zero impact to compatibility.
Simple Transformers can be used to perform one of 4 types of transformations:
- Block/Item with any meta/damage value -> some Block/Item, preserving the meta or damage value.
- Behaves similarly to an FMLMissingMapping remap, but is more effective in the long term as it doesn't cause an ID aliasing.
- Block/Item with any meta/damage value -> some Block/Item with a specific meta or damage value.
- Block/Item with a specific meda/damage value -> some Block/Item, preserving the meta or damage value.
- Block/Item with a specific meta/damage value -> some Block/Item, with a specific meta/damage value.
Tip
These simple transformers can be used to target blocks and items that no longer exist in the world due to a content or mod removal. Postea can Identify the numeric ID of any content that was once registered to a world's ID map. Even if postea wasn't installed when those mods were installed or removed.
This also applies to the
BlockReplacementManager.registerIDResolverandItemStackReplacementManager.registerIDResolverfunctions. The callbacks registered by these function will return with a value of -1 which is impossible to get from theBlockAccessCompatfunctions that return block IDs.This means that technically it's better to use simple transformation to migrate removed content as it removes the need for forge to create any kind of ID aliases which usually need to be resolved at runtime.
Tip
OreDictionary.WILDCARD_VALUEhas a meaning when registering simple transformations:When used as the source Meta/Damage value, Postea will use the transformation being specified as a fallback if nothing has specified a Meta/Damage transformation for the Block/Item's current Meta/Damage value.
When used as the target Meta/Damage value, Postea will preserve the existing value instead of over-writing it.
Tip
When registering block or item conversions, Postea will automatically detect if the given Item or Block has a Block or Item counterpart (respectively). You can disable this behaviour by adding a
falseargument to end of all simple transformation endpoints.
Note
Simple transformations are usually executed last.
Code example
public abstract class SimpleTransformationExample {
public static void postLoad() {
// item/block -> item/block, preserving the Meta/Damage value.
//
// remap all block and items of colored stone blocks with wool of the same color
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:colorStoneBrick", Blocks.wool);
// Item/Block with meta -> Item/Block, meta preserved
//
// angel ring:3 to disk 11
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:angelRing", 3, new ItemStack(Items.record_11));
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:angelRing", 4, new ItemStack(Items.record_13));
// Item/Block with meta -> Item/Block with specific meta
//
// remap all compressed cobble to oak planks
//
// When using a stack as a target the passed stack size is ignored, only the contained item and meta value are kept.
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 0, new ItemStack(Blocks.planks, 0, 0));
// block only with meta -> block with specific meta value.
//
// remap blocks of double compressed cobble to cobble.
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 1, Blocks.cobblestone, 0, true);
// item only with damage -> item only with specific damage value.
//
// remap stacks of double compressed cobble to oak saplings.
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 1,new ItemStack(Blocks.sapling, 1, 0), true);
// Block/Item with any Meta/Damage -> Block/Item with specific Meta/Damage value.
//
// Using OreDictionary.WILDCARD_VALUE will tell postea to use this mapping as a fallback when there is no
// transformer targeting the specific meta/damage value of the block/item.
//
// remap all other instances of compressed blocks to endstone
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", OreDictionary.WILDCARD_VALUE, Blocks.end_stone, 0);
// Block/Item with any Meta/Damage -> Block/Item, preserving the Meta/Damage value.
//
// When passing OreDictionary.WILDCARD_VALUE as the target Meta/Damage value, Postea will preserve any existing
// Meta/Damage value. This is also the default value when you don't specify a target meta value.
//
// remap all other instances of compressed blocks to endstone
BlockReplacementManager.addSimpleReplacement(
"ExtraUtilities:cobblestone_compressed",
OreDictionary.WILDCARD_VALUE,
Blocks.end_stone,
OreDictionary.WILDCARD_VALUE);
}
}
5. Complex Block Transformers
If you need some custom logic in order to perform a block conversion, you can register a custom transformer using
BlockReplacementManager.addTransformationHandler.
Tip
Returning
truetells Postea to stop looking for any other handlers for the current block, and to replace the block being transformed using theblockIDandmetadatacurrently stored in theBlockConversionInfoparameter.
Caution
Just like for Tile Entity transformers, trying to perform block access using the world provided by the BlockTransformationInfo parameter will result in an error as the transformers are executed before the world's content are properly initialized.
public abstract class ComplexBlockTransformer {
private static int woolId = -1;
public static void postLoad() {
// This transformer replaces all grass blocks with even x-coordinates with orange wool.
BlockReplacementManager.registerIDResolver("minecraft:wool", i -> woolId = i);
BlockReplacementManager.addTransformationHandler("minecraft:grass", ComplexBlockTransformer::transformGrass);
}
private static boolean transformGrass(BlockConversionInfo info) {
// only affect blocks that are on even x coordinates
if (info.x % 2 != 0) return false;
// transform all these blocks into orange wool.
info.blockID = woolId;
info.metadata = 1;
return true;
}
}
6. Stack Transformation
If you need some custom logic in order to perform an item conversion, you can register a custom transformer using
ItemStackReplacementManager.addTransformationHandler.
Code example:
public abstract class ComplexItemTransformer {
private static int writableBookId = -1;
private static int woolId = -1;
private static void postLoad() {
ItemStackReplacementManager.registerIDResolver("minecraft:writable_book", i -> writableBookId = i);
ItemStackReplacementManager.registerIDResolver("minecraft:wool", i -> woolId = i);
ItemStackReplacementManager.addTransformationHandler("minecraft:written_book", ComplexItemTransformer::unsignBook);
}
/**
* This transformer transforms a written book into either an unsigned book or orange wool depending on it's title.
*
* @param name The name of the item being transformed, even if that item doesn't exist anymore.
* @param nbt The NBT data of the stack item being transformed.
* @return True if we've actually transformed the item.
*/
private static boolean unsignBook(String name, NBTTagCompound nbt) {
// check if the book has a title.
if (!nbt.hasKey("tag",10)) return false;
NBTTagCompound tag = nbt.getCompoundTag("tag");
if (!tag.hasKey("title", 8)) return false;
String title = tag.getString("title");
if (title.equalsIgnoreCase("unsign me")) {
// unsign the book
IDExtenderCompat.setItemStackID(nbt, writableBookId);
nbt.setShort("Damage", (short) 0);
nbt.setByte("Count", (byte) 1);
// clear the signed book nbt
tag.removeTag("title");
tag.removeTag("author");
return true;
}
else if (title.equalsIgnoreCase("orange wool")) {
// Transform it into orange wool.
IDExtenderCompat.setItemStackID(nbt, woolId);
nbt.setShort("Damage", (short) 1);
nbt.setByte("Count", (byte) 1);
// removes the NBT tag from the stack.
nbt.removeTag("tag");
return true;
}
return false;
}
}
7. Remapping Missing Mappings using FML
Postea provides a few API endpoints to quickly register re-mappings for any given Item or Block that has been removed.
Note
It's generally more advisable to use the simple item transformers instead as they will create less overhead in the long term.
Caution
If you try to remap something to a block that already has ever existed at a prior date inside a world, (eg: remapping to a block of dirt), or something from your own mod that has existed for a while, FML will throw an error when it loads the world and tell you that your world is corrupted.
Simple Transformers are generally safer in that regard and do not care if an thing has ever existed in a world before. Therefore we generally recommend using them instead of FML re-mappings.
Code example:
public abstract class FMLReMappingExample {
// You have to make your own block class since the block constructor is protected.
public static class MyBlock extends Block {
protected MyBlock() {
super(Material.ground);
}
}
public static void postLoad() {
// this only works when the thing you're replacing with has never been registered to a world's
// id map. If you try to run this with a block that already exists (eg good old dirt), FML error out and
// you that your world is corrupted beyond repair, and advise you to undo what ever you just did.
//
// Postea Simple Transformers don't have that issue and are more lag friendly in the long term. Consider
// using them when ever possible, instead of FML mappings.
Item item = new Item();
GameRegistry.registerItem(item, "my_item");
ItemStackReplacementManager.replaceMissingMapping("IC2:itemFertilizer", item);
// This also remaps the stacks of IC2:blockCrop
Block block = new MyBlock();
GameRegistry.registerBlock(block, "my_block");
BlockReplacementManager.replaceMissingMapping("IC2:blockCrop", block);
}
}
8. Ignoring Missing Mappings from FML
Postea provides a few API endpoints that let you tell FML to hide any missing mapping warnings regarding that ID when loading a world.
Code example:
public abstract class FMLIgnoreMissingMappingExample {
public static void postLoad() {
// It doesn't really matter which one you use, they both do the same thing under the hood.
ItemStackReplacementManager.ignoreMissingMapping("IC2:itemFertilizer");
BlockReplacementManager.ignoreMissingMapping("IC2:blockCrop");
}
}