any mentions of nuke dummies, add new interfaces that better serve good practices.

This commit is contained in:
Guillaume Mercier committed 2026-01-27 16:46:05 -05:00
1 parent 78e3f792b7
commit 3868d131a7
30 files changed
+1218 -487

No files matched your search

@@ -1,5 +1,9 @@
package com.gtnewhorizons.postea;
import net.minecraftforge.event.world.ChunkEvent;
import com.gtnewhorizons.postea.utility.ChunkFixerUtility;
import com.gtnewhorizons.postea.utility.IDRegistry;
import com.gtnewhorizons.postea.utility.MissingMappingHandler;
import com.gtnewhorizons.postea.utility.SimpleTransformationRegistry;
import com.gtnewhorizons.postea.utility.TransformerRegistry;
@@ -9,7 +13,6 @@ import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.event.FMLModIdMappingEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
@Mod(
modid = Postea.MODID,
@@ -26,10 +29,16 @@ public class Postea {
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {}
@Mod.EventHandler
public void chunkLoaded(ChunkEvent.Load event) {
ChunkFixerUtility.onChunkLoaded(event.getChunk());
}
@Mod.EventHandler
public void onIdMappingsChanged(FMLModIdMappingEvent event) {
SimpleTransformationRegistry.onIdMappingsChanged();
TransformerRegistry.onIdMappingsChanged();
IDRegistry.onMappingUpdated();
}
@Mod.EventHandler
@@ -40,12 +49,6 @@ public class Postea {
@Mod.EventHandler
public void onLoadCompleted(FMLLoadCompleteEvent event) {
SimpleTransformationRegistry.onLoadCompleted();
MissingMappingHandler.onLoadCompleted();
}
@Mod.EventHandler
public void onServerStopping(FMLServerStoppingEvent event) {
TransformerRegistry.onServerStopping();
}
}
@@ -0,0 +1,95 @@
package com.gtnewhorizons.postea.api;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import com.gtnewhorizons.postea.compat.Compat;
import com.gtnewhorizons.postea.compat.SubChunkAccess;
/**
* Public API for accessing information related to a block located at the location of a tile entity inside a
* tile entity transformer registered via
* {@link TileEntityReplacementManager#tileEntityTransformer(String, TriFunction)}
*/
@SuppressWarnings("unused")
public abstract class BlockAccessCompat {
/**
* Fetches the id of the block that is (or used to be) at the position of the TE stored in the given tag.
*
* @apiNote This method is intended for use withing TileEntityTransformers declared via
* {@link TileEntityReplacementManager#tileEntityTransformer(String, TriFunction)}
*
* @param tag The tag for the tile entity
* @param chunk The chunk containing the tile entity
* @return The id of the block that exists (or used to exist) at that location.
*/
@SuppressWarnings("unused")
public static int getBlockIDAtTE(NBTTagCompound tag, Chunk chunk) {
// fetch the block accessor
ExtendedBlockStorage[] ebsArray = chunk.getBlockStorageArray();
// abort if y is oob
int y = tag.getInteger("y");
if (y >> 4 >= ebsArray.length) return 0;
SubChunkAccess access = Compat.getSubChunkAccess(ebsArray[y >> 4]);
// identify the block
int x = tag.getInteger("x") & 0xf;
y &= 0xf;
int z = tag.getInteger("z") & 0xf;
return access.getBlockId(x, y, z);
}
/**
* Fetches the meta of the block that is (or used to be) at the position of the TE stored in the given tag.
*
* @apiNote This method is intended for use withing TileEntityTransformers declared via
* {@link TileEntityReplacementManager#tileEntityTransformer(String, TriFunction)}
*
* @param tag The tag for the tile entity
* @param chunk The chunk containing the tile entity
* @return The id of the block that exists (or used to exist) at that location.
*/
@SuppressWarnings("unused")
public static int getBlockMetaAtTE(NBTTagCompound tag, Chunk chunk) {
// fetch the block accessor
ExtendedBlockStorage[] ebsArray = chunk.getBlockStorageArray();
// abort if y is oob
int y = tag.getInteger("y");
if (y >> 4 >= ebsArray.length) return 0;
SubChunkAccess access = Compat.getSubChunkAccess(ebsArray[y >> 4]);
// identify the block
int x = tag.getInteger("x") & 0xf;
y &= 0xf;
int z = tag.getInteger("z") & 0xf;
return access.getMeta(x, y, z);
}
/**
* Fetches the id and meta of the block that is (or used to be) at the position of the TE stored in the given tag.
*
* @apiNote This method is intended for use withing TileEntityTransformers declared via
* {@link TileEntityReplacementManager#tileEntityTransformer(String, TriFunction)}
*
* @param tag The tag for the tile entity
* @param chunk The chunk containing the tile entity
* @return An array with th eid of the block that exists (or used to exist) at that location.
*/
@SuppressWarnings("unused")
public static int[] getBlockIDAndMetaAtTE(NBTTagCompound tag, Chunk chunk) {
// fetch the block accessor
ExtendedBlockStorage[] ebsArray = chunk.getBlockStorageArray();
// abort if y is oob
int y = tag.getInteger("y");
if (y >> 4 >= ebsArray.length) return new int[] { 0, 0 };
SubChunkAccess access = Compat.getSubChunkAccess(ebsArray[y >> 4]);
// identify the block
int x = tag.getInteger("x") & 0xf;
y &= 0xf;
int z = tag.getInteger("z") & 0xf;
return new int[] { access.getBlockId(x, y, z), access.getMeta(x, y, z) };
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.postea.api;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -10,11 +11,19 @@ import net.minecraft.world.World;
import net.minecraftforge.oredict.OreDictionary;
import com.gtnewhorizons.postea.utility.BlockConversionInfo;
import com.gtnewhorizons.postea.utility.IDRegistry;
import com.gtnewhorizons.postea.utility.MissingMappingHandler;
import com.gtnewhorizons.postea.utility.SimpleTransformationRegistry;
import com.gtnewhorizons.postea.utility.TransformerRegistry;
public class BlockReplacementManager {
/**
* Public API used to register block transformers via postea's handler system.<br>
* <br>
* Block transformers are executed after Tile Entity transformers, when chunks being loading into the world.
*/
public abstract class BlockReplacementManager {
// region custom transformer registration
/**
* Adds a custom transformer for a given id.
@@ -27,11 +36,11 @@ public class BlockReplacementManager {
* <br>
* If you need to perform a simple transformations consider using the following methods instead:
* <ul>
* <li>block -> block (with same meta): {@link #addBlockReplacement(String, Block)}</li>
* <li>block -> block with specific meta: {@link #addBlockReplacement(String, Block, int)}</li>
* <li>block with meta -> block (with same meta): {@link #addBlockReplacement(String, int, Block)}</li>
* <li>block -> block (with same meta): {@link #addSimpleReplacement(String, Block)}</li>
* <li>block -> block with specific meta: {@link #addSimpleReplacement(String, Block, int)}</li>
* <li>block with meta -> block (with same meta): {@link #addSimpleReplacement(String, int, Block)}</li>
* <li>block with meta -> block with specific meta:
* {@link #addBlockReplacement(String, int, Block, int)}</li>
* {@link #addSimpleReplacement(String, int, Block, int)}</li>
* </ul>
*
* @implNote For performance reasons, Postea assumes that the BlockConversionInfo that was passed to the handler
@@ -59,11 +68,11 @@ public class BlockReplacementManager {
* <br>
* If you need to perform a simple transformations consider using the following methods instead:
* <ul>
* <li>block -> block (with same meta): {@link #addBlockReplacement(String, Block)}</li>
* <li>block -> block with specific meta: {@link #addBlockReplacement(String, Block, int)}</li>
* <li>block with meta -> block (with same meta): {@link #addBlockReplacement(String, int, Block)}</li>
* <li>block -> block (with same meta): {@link #addSimpleReplacement(String, Block)}</li>
* <li>block -> block with specific meta: {@link #addSimpleReplacement(String, Block, int)}</li>
* <li>block with meta -> block (with same meta): {@link #addSimpleReplacement(String, int, Block)}</li>
* <li>block with meta -> block with specific meta:
* {@link #addBlockReplacement(String, int, Block, int)}</li>
* {@link #addSimpleReplacement(String, int, Block, int)}</li>
* </ul>
*
* @implNote For performance reasons, Postea assumes that the BlockConversionInfo that was passed to the handler
@@ -81,13 +90,179 @@ public class BlockReplacementManager {
TransformerRegistry.addBlockTransformer(originalId, transformer);
}
// endregion custom transformer registration
// region id resolving
// spotless:off
/**
* Registers a callback function that will be provided the numeric ID of the given namespaced ID when a world is
* first loaded. Since forge doesn't remap missing block IDs to 0 when loading a world for performance reasons,
* instead opting to treat any block ID as Air, you can store that value into a static field, and use it to
* compare against an id provided to you during the execution of a transformation handler.<br>
* <br>
* This function is mostly intended to be used in partnership with tile entity transformers, and is intended to
* overcome the need to create dummy blocks and items in order to identify blocks inside a custom transformer.<br>
* <br>
* For compatibility reasons, it's better not to register dummies to take the place of dummy items. If one mod was
* to only convert blocks with a specific meta while another handled the rest, the other mod would either need
* additional work to detect your own dummy blocks or even worst, fail to identify the original blocks as it's not
* aware of your dummy blocks.
*
* @apiNote Here is an example implementation:
* <pre>{@code
* import net.minecraft.block.Block;
* import net.minecraft.block.material.Material;
* import net.minecraft.nbt.NBTTagCompound;
* import net.minecraft.world.World;
* import net.minecraft.world.chunk.Chunk;
*
* import com.gtnewhorizons.postea.api.BlockAccessCompat;
* import com.gtnewhorizons.postea.api.BlockReplacementManager;
* import com.gtnewhorizons.postea.api.TileEntityReplacementManager;
* import com.gtnewhorizons.postea.utility.BlockInfo;
* import com.gtnewhorizons.postea.utility.PosteaUtilities;
*
* import cpw.mods.fml.common.Mod;
* import cpw.mods.fml.common.event.FMLPostInitializationEvent;
* import cpw.mods.fml.common.event.FMLPreInitializationEvent;
* import cpw.mods.fml.common.registry.GameRegistry;
*
* @Mod(
* modid = "mymod",
* version = "1.0.0",
* name = "Example Mod",
* acceptedMinecraftVersions = "[1.7.10]",
* dependencies = " after:otherMod;")
* @SuppressWarnings("unused")
* public class ExampleMod {
*
* private static int targetBlock1 = -1;
* private static int targetBlock2 = -1;
* private static int targetBlock3 = -1;
*
* private static class MyBlocks {
* public static Block block1;
* public static Block block2;
* public static Block block3;
* public static Block block4;
* public static Block block5;
* public static Block block6;
* }
*
* private static class TestBlock extends Block {
* public TestBlock(String id) {
* super(Material.ground);
* GameRegistry.registerBlock(this, id);
* }
* }
*
* @Mod.EventHandler
* @SuppressWarnings("unused")
* public static void preInit(FMLPreInitializationEvent event) {
* MyBlocks.block1 = new TestBlock("block1");
* MyBlocks.block2 = new TestBlock("block2");
* MyBlocks.block3 = new TestBlock("block3");
* MyBlocks.block4 = new TestBlock("block4");
* MyBlocks.block5 = new TestBlock("block5");
* MyBlocks.block6 = new TestBlock("block6");
* }
*
* @Mod.EventHandler
* @SuppressWarnings("unused")
* public static void postLoad(FMLPostInitializationEvent event) {
* BlockReplacementManager.registerIDResolver("otherMod:block1", i -> targetBlock1 = i);
* BlockReplacementManager.registerIDResolver("otherMod:block2", i -> targetBlock2 = i);
* BlockReplacementManager.registerIDResolver("otherMod:block3", i -> targetBlock3 = i);
* TileEntityReplacementManager.tileEntityTransformer("otherModTE", ExampleMod::transform);
* // If you were to have all of these in one mod,
* // the general recommendation would be to merge the handlers,
* // but let's pretend that these are set up by other mods
* // for the sake of example.
* TileEntityReplacementManager.tileEntityTransformer("otherModTE", ExampleMod::transformOther);
* BlockReplacementManager.addBlockReplacement("otherMod:block1", 0, MyBlocks.block1, 0);
* }
*
* public static BlockInfo transform(NBTTagCompound tag, World world, Chunk chunk) {
* int oldBlock = BlockAccessCompat.getBlockIDAtTE(tag, chunk);
* int x = tag.getInteger("x") & 0xF;
* int y = tag.getInteger("y");
* int z = tag.getInteger("z") & 0xF;
* int meta = chunk.getBlockMetadata(x, y, z);
* Block newBlock;
* String targetType;
* if (oldBlock == targetBlock1) {
* // block transformer executes after TE transformers
* // so we sill need to check for meta 0
* if (meta == 0) return null;
* newBlock = MyBlocks.block2;
* targetType = "stone";
* } else if (oldBlock == targetBlock2) {
* // another mod would be handling the transformation for 0
* if (meta != 1 && meta != 2) return null;
* newBlock = meta == 1 ? MyBlocks.block3 : MyBlocks.block4;
* targetType = "iron";
* } else {
* // Return null to tell postea that you can't handle via this transformer.
* // Maybe another transformer can handle it.
* return null;
* }
* return new BlockInfo(newBlock, 0, originalNBT -> {
* NBTTagCompound newNBT = PosteaUtilities.cleanseNBT("myTE", originalNBT);
* newNBT.setString("type", targetType);
* newNBT.setInteger("level", meta);
* newNBT.setInteger("fuelLevel", originalNBT.getInteger("storage") * 20);
* return newNBT;
* });
* }
*
* public static BlockInfo transformOther(NBTTagCompound tag, World world, Chunk chunk) {
* int oldBlock = BlockAccessCompat.getBlockIDAtTE(tag, chunk);
* int x = tag.getInteger("x") & 0xF;
* int y = tag.getInteger("y");
* int z = tag.getInteger("z") & 0xF;
* int meta = chunk.getBlockMetadata(x, y, z);
* if (oldBlock == targetBlock2) {
* if (meta != 3) return null;
* // that doesn't need to be a TE anymore.
* return new BlockInfo(MyBlocks.block5, 0, null);
* } else if (oldBlock == targetBlock3) {
* // transfer the TE correctly
* return new BlockInfo(MyBlocks.block6, meta, originalNBT -> {
* NBTTagCompound newNBT = PosteaUtilities.cleanseNBT("myOtherTE", originalNBT);
* newNBT.setInteger("remaining", originalNBT.getInteger("storage"));
* return newNBT;
* });
* }
* // can't handle this
* return null;
* }
* }
* }</pre>
*
*
* @param originalId The namespaced ID of a block you wish to identify in one of your transformers.
* @param resolver The callback executed to provide the numeric ID of the given namespaced ID.
*/
// spotless:on
@SuppressWarnings("unused")
public static void registerIDResolver(String originalId, Consumer<Integer> resolver) {
if (originalId == null) throw new NullPointerException("originalId cannot be null");
if (resolver == null) throw new NullPointerException("resolver cannot be null");
IDRegistry.registerBlockIDResolver(originalId, resolver);
}
// endregion id resolving
// region missing mapping replacements
/**
* Adds a mapping to remap stacks and block instances of a specific removed block to the given block whilst, whilst
* maintaining its meta value.
* maintaining its meta value.<br>
* <br>
* <b><u>IMPORTANT: This only works if the world does not have an existing id set for the target block. If you need
* to transform a missing block into something that was previously given an id (e.g.: Transforming a removed block
* into dirt), use {@link #addBlockReplacement(String, Block)} with both meta parameters set to
* into dirt), use {@link #addSimpleReplacement(String, Block)} with both meta parameters set to
* OreDictionary.WILDCARD_VALUE.</u></b>
*
* @param originalId The id of the block to remap.
@@ -97,7 +272,7 @@ public class BlockReplacementManager {
public static void replaceMissingBlockWithNewBlock(String originalId, Block block) {
if (originalId == null) throw new IllegalArgumentException("original id is null");
if (block == null) throw new IllegalArgumentException("block is null");
MissingMappingHandler.addSimpleReplacement(originalId, block);
MissingMappingHandler.addBlockMapping(originalId, block);
}
/**
@@ -110,6 +285,10 @@ public class BlockReplacementManager {
MissingMappingHandler.addIgnore(originalId);
}
// endregion missing mapping replacements
// region simple replacements
// block -> block
/**
@@ -124,8 +303,8 @@ public class BlockReplacementManager {
* @param block The block to remap to.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, @Nonnull Block block) {
addBlockReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, OreDictionary.WILDCARD_VALUE, false);
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block) {
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, OreDictionary.WILDCARD_VALUE, false);
}
/**
@@ -141,8 +320,8 @@ public class BlockReplacementManager {
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, @Nonnull Block block, boolean skipStackRemap) {
addBlockReplacement(
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block, boolean skipStackRemap) {
addSimpleReplacement(
originalId,
OreDictionary.WILDCARD_VALUE,
block,
@@ -161,8 +340,8 @@ public class BlockReplacementManager {
* @param block The block to remap to.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block) {
addBlockReplacement(originalId, originalMeta, block, OreDictionary.WILDCARD_VALUE, false);
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block) {
addSimpleReplacement(originalId, originalMeta, block, OreDictionary.WILDCARD_VALUE, false);
}
/**
@@ -175,9 +354,9 @@ public class BlockReplacementManager {
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block,
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block,
boolean skipStackRemap) {
addBlockReplacement(originalId, originalMeta, block, OreDictionary.WILDCARD_VALUE, skipStackRemap);
addSimpleReplacement(originalId, originalMeta, block, OreDictionary.WILDCARD_VALUE, skipStackRemap);
}
// block -> block+meta
@@ -191,8 +370,8 @@ public class BlockReplacementManager {
* @param newMeta The meta of the block to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, @Nonnull Block block, int newMeta) {
addBlockReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, newMeta, false);
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block, int newMeta) {
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, newMeta, false);
}
/**
@@ -206,9 +385,9 @@ public class BlockReplacementManager {
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, @Nonnull Block block, int newMeta,
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block, int newMeta,
boolean skipStackRemap) {
addBlockReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, newMeta, skipStackRemap);
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, newMeta, skipStackRemap);
}
// block+meta -> block+meta
@@ -223,9 +402,9 @@ public class BlockReplacementManager {
* @param newMeta The meta of the block to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block,
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block,
int newMeta) {
addBlockReplacement(originalId, originalMeta, block, newMeta, false);
addSimpleReplacement(originalId, originalMeta, block, newMeta, false);
}
/**
@@ -240,10 +419,13 @@ public class BlockReplacementManager {
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
*/
@SuppressWarnings("unused")
public static void addBlockReplacement(String originalId, int originalMeta, Block block, int newMeta,
public static void addSimpleReplacement(String originalId, int originalMeta, Block block, int newMeta,
boolean skipStackRemap) {
if (originalId == null) throw new IllegalArgumentException("original id is null");
if (block == null) throw new IllegalArgumentException("block is null");
SimpleTransformationRegistry.addSimpleTransformer(originalId, originalMeta, block, newMeta, skipStackRemap);
}
// endregion simple replacements
}
@@ -0,0 +1,16 @@
package com.gtnewhorizons.postea.api;
import com.gtnewhorizons.postea.utility.BlockConversionInfo;
@FunctionalInterface
public interface IComplexBlockTransformer {
/**
* A custom transformer for blocks.
*
* @param info The information about the block.
* @return True if the block was replaced and no other block transformation handler should be executed for the
* associated original id;
*/
boolean transformBlock(BlockConversionInfo info);
}
@@ -0,0 +1,16 @@
package com.gtnewhorizons.postea.api;
import net.minecraft.nbt.NBTTagCompound;
@FunctionalInterface
public interface IComplexStackTransformer {
/**
* A custom transformer for items.
*
* @param originalId The namespaced id of the stack being passed to this transformer.
* @param tag The raw nbt data of the stack being transformed.
* @return A non-null value to prevent any future handlers from executing or if the stack was replaced.
*/
boolean transformStack(String originalId, NBTTagCompound tag);
}
@@ -5,8 +5,13 @@ import static net.minecraftforge.common.util.Constants.NBT.TAG_SHORT;
import net.minecraft.nbt.NBTTagCompound;
/**
* Public API used to query and update stack item IDs inside Postea transformers.
*/
@SuppressWarnings("unused")
public class IDExtenderCompat {
@SuppressWarnings("unused")
public static int getItemStackID(NBTTagCompound tag) {
if (tag.hasKey("idExt", TAG_INT)) return tag.getInteger("idExt");
else if (tag.hasKey("id", TAG_INT)) return tag.getInteger("id");
@@ -14,6 +19,7 @@ public class IDExtenderCompat {
else return 0;
}
@SuppressWarnings("unused")
public static void setItemStackID(NBTTagCompound tag, int id) {
if (id < 32000) {
tag.setShort("id", (short) id);
@@ -0,0 +1,10 @@
package com.gtnewhorizons.postea.api;
import net.minecraft.block.Block;
public interface ISimpleBlockTransformer {
Block getTargetBlock();
int getTargetBlockMeta();
}
@@ -0,0 +1,10 @@
package com.gtnewhorizons.postea.api;
import net.minecraft.item.Item;
public interface ISimpleItemTransformer {
Item getTargetItem();
int getTargetItemMeta();
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.postea.api;
import net.minecraft.item.ItemStack;
public interface ISimpleStackTransformer {
ItemStack getTargetStack();
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.postea.api;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -11,13 +12,21 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.oredict.OreDictionary;
import com.gtnewhorizons.postea.utility.IDRegistry;
import com.gtnewhorizons.postea.utility.MissingMappingHandler;
import com.gtnewhorizons.postea.utility.SimpleTransformationRegistry;
import com.gtnewhorizons.postea.utility.TransformerRegistry;
public class ItemStackReplacementManager {
/**
* Public API used to register item stack transformers via postea's handler system.<br>
* <br>
* Item stack transformers are applied when something tries to convert any NBT that has an "id" field into an item
* stack.
*/
@SuppressWarnings("unused")
public abstract class ItemStackReplacementManager {
// Public API for converting ItemStacks.
// region custom transformer registration
/**
* Adds a custom transformer for a given id.
@@ -30,10 +39,11 @@ public class ItemStackReplacementManager {
* <p>
* If you need to perform a simple transformations consider using the following methods instead:
* <ul>
* <li>item -> item (with same meta): {@link #addItemReplacement(String, Item)}</li>
* <li>item -> item with specific meta: {@link #addItemReplacement(String, Item, int)}</li>
* <li>item with meta -> item (with same meta): {@link #addItemReplacement(String, int, Item)}</li>
* <li>item with meta -> item with specific meta: {@link #addItemReplacement(String, int, Item, int)}</li>
* <li>item -> item (with same meta): {@link #addSimpleReplacement(String, Item)}</li>
* <li>item -> item with specific meta: {@link #addSimpleReplacement(String, Item, int)}</li>
* <li>item with meta -> item (with same meta): {@link #addSimpleReplacement(String, int, Item)}</li>
* <li>item with meta -> item with specific meta:
* {@link #addSimpleReplacement(String, int, Item, int)}</li>
* </ul>
*
* @implNote For performance reasons, Postea assumes that the tag that was passed to the handler hasn't been
@@ -61,10 +71,11 @@ public class ItemStackReplacementManager {
* <p>
* If you need to perform a simple transformations consider using the following methods instead:
* <ul>
* <li>item -> item (with same meta): {@link #addItemReplacement(String, Item)}</li>
* <li>item -> item with specific meta: {@link #addItemReplacement(String, Item, int)}</li>
* <li>item with meta -> item (with same meta): {@link #addItemReplacement(String, int, Item)}</li>
* <li>item with meta -> item with specific meta: {@link #addItemReplacement(String, int, Item, int)}</li>
* <li>item -> item (with same meta): {@link #addSimpleReplacement(String, Item)}</li>
* <li>item -> item with specific meta: {@link #addSimpleReplacement(String, Item, int)}</li>
* <li>item with meta -> item (with same meta): {@link #addSimpleReplacement(String, int, Item)}</li>
* <li>item with meta -> item with specific meta:
* {@link #addSimpleReplacement(String, int, Item, int)}</li>
* </ul>
*
* @implNote For performance reasons, Postea assumes that the tag that was passed to the handler hasn't been
@@ -83,12 +94,47 @@ public class ItemStackReplacementManager {
TransformerRegistry.addStackTransformer(originalId, transformer);
}
// endregion custom transformer registration
// region id resolving
/**
* Adds a mapping to remap stacks of a specific removed item to the given item.
* <p>
* Registers a callback function that will be provided the numeric ID of the given namespaced ID when a world is
* first loaded. Since forge doesn't discard stacks associated with missing IDs when loading a world for
* performance reasons, instead opting to null the result of the nbt data read, you can store that value into a
* static field, and use it to compare against an id provided to you during the execution of a transformation
* handler.<br>
* <br>
* This function is mostly intended to speed up larger transformer handlers since resolving the string id of a
* removed id inside a transformer will be slower than simply matching for it. Regular item transformers are
* already provided the string ID of the item they are transforming so, there is no reason not to do a switch case
* on those. This also overcomes the need to create dummy blocks and items in order to identify
* items inside a custom stack transformer.<br>
* <br>
* For compatibility reasons, it's better not to register dummies to take the place of dummy items. If one mod was
* to only convert blocks with a specific meta while another handled the rest, the other mod would either need
* additional work to detect your own dummy blocks or even worst, fail to identify the original blocks as it's not
* aware of your dummy blocks.
*
* @param originalId The namespaced ID of a block you wish to identify in one of your transformers.
* @param resolver The callback executed to provide the numeric ID of the given namespaced ID.
*/
@SuppressWarnings("unused")
public static void registerIDResolver(String originalId, Consumer<Integer> resolver) {
if (originalId == null) throw new NullPointerException("originalId cannot be null");
if (resolver == null) throw new NullPointerException("resolver cannot be null");
IDRegistry.registerItemIDResolver(originalId, resolver);
}
// endregion id resolving
// region missing mapping replacements
/**
* Adds a mapping to remap stacks of a specific removed item to the given item.<br>
* <br>
* <b><u>IMPORTANT: This only works if the world does not have an existing id set for the target item. If you need
* to transform a missing item into something that was previously given an id (e.g.: Transforming a removed item
* into a stick), use {@link #addItemReplacement(String, int, Item, int)} with both meta values set to
* into a stick), use {@link #addSimpleReplacement(String, int, Item, int)} with both meta values set to
* OreDictionary.WILDCARD_VALUE.</u></b>
*
* @param originalId The id of the item to remap.
@@ -98,7 +144,7 @@ public class ItemStackReplacementManager {
public static void replaceMissingItemWithNewItem(String originalId, Item item) {
if (originalId == null) throw new IllegalArgumentException("original id is null");
if (item == null) throw new IllegalArgumentException("item is null");
MissingMappingHandler.addSimpleReplacement(originalId, item);
MissingMappingHandler.addItemMapping(originalId, item);
}
/**
@@ -111,6 +157,10 @@ public class ItemStackReplacementManager {
MissingMappingHandler.addIgnore(originalId);
}
// endregion missing mapping replacements
// region simple replacements
// item -> item
/**
@@ -128,8 +178,8 @@ public class ItemStackReplacementManager {
* @param item The item to remap to.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, @Nonnull Item item) {
addItemReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, OreDictionary.WILDCARD_VALUE, false);
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item) {
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, OreDictionary.WILDCARD_VALUE, false);
}
/**
@@ -147,8 +197,8 @@ public class ItemStackReplacementManager {
* @param skipBlockRemap Set to true to skip auto-adding a block remapper if the item happens to be an ItemBlock.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, @Nonnull Item item, boolean skipBlockRemap) {
addItemReplacement(
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item, boolean skipBlockRemap) {
addSimpleReplacement(
originalId,
OreDictionary.WILDCARD_VALUE,
item,
@@ -170,8 +220,8 @@ public class ItemStackReplacementManager {
* @param newMeta The meta of the item to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, @Nonnull Item item, int newMeta) {
addItemReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, newMeta, false);
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item, int newMeta) {
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, newMeta, false);
}
/**
@@ -187,9 +237,9 @@ public class ItemStackReplacementManager {
* @param skipBlockRemap Set to true to skip auto-adding a block remapper if the item happens to be an ItemBlock.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, @Nonnull Item item, int newMeta,
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item, int newMeta,
boolean skipBlockRemap) {
addItemReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, newMeta, skipBlockRemap);
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, newMeta, skipBlockRemap);
}
// item -> stack
@@ -207,8 +257,8 @@ public class ItemStackReplacementManager {
* @param stack The item to remap to.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, @Nonnull ItemStack stack) {
addItemReplacement(
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull ItemStack stack) {
addSimpleReplacement(
originalId,
OreDictionary.WILDCARD_VALUE,
stack.getItem(),
@@ -229,9 +279,9 @@ public class ItemStackReplacementManager {
* @param skipBlockRemap Set to true to skip auto-adding a block remapper if the item happens to be an ItemBlock.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, @Nonnull ItemStack stack,
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull ItemStack stack,
boolean skipBlockRemap) {
addItemReplacement(
addSimpleReplacement(
originalId,
OreDictionary.WILDCARD_VALUE,
stack.getItem(),
@@ -253,8 +303,8 @@ public class ItemStackReplacementManager {
* @param item The item to remap to.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item) {
addItemReplacement(originalId, originalMeta, item, OreDictionary.WILDCARD_VALUE, false);
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item) {
addSimpleReplacement(originalId, originalMeta, item, OreDictionary.WILDCARD_VALUE, false);
}
/**
@@ -270,9 +320,9 @@ public class ItemStackReplacementManager {
* @param skipBlockRemap Set to true to skip auto-adding a item remapper if the .
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item,
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item,
boolean skipBlockRemap) {
addItemReplacement(originalId, originalMeta, item, OreDictionary.WILDCARD_VALUE, skipBlockRemap);
addSimpleReplacement(originalId, originalMeta, item, OreDictionary.WILDCARD_VALUE, skipBlockRemap);
}
// item+meta -> stack
@@ -291,8 +341,8 @@ public class ItemStackReplacementManager {
* @param stack A stack of the item to replace to.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, int originalMeta, @Nonnull ItemStack stack) {
addItemReplacement(originalId, originalMeta, stack, false);
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull ItemStack stack) {
addSimpleReplacement(originalId, originalMeta, stack, false);
}
/**
@@ -310,9 +360,9 @@ public class ItemStackReplacementManager {
* @param skipBlockRemap Set to true to skip auto-adding a block remapper if the item happens to be an ItemBlock.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, int originalMeta, @Nonnull ItemStack stack,
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull ItemStack stack,
boolean skipBlockRemap) {
addItemReplacement(originalId, originalMeta, stack.getItem(), Items.feather.getDamage(stack), skipBlockRemap);
addSimpleReplacement(originalId, originalMeta, stack.getItem(), Items.feather.getDamage(stack), skipBlockRemap);
}
// item+meta -> item+meta
@@ -330,9 +380,9 @@ public class ItemStackReplacementManager {
* @param newMeta The meta of the item to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
*/
@SuppressWarnings("unused")
public static void addItemReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item,
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item,
int newMeta) {
addItemReplacement(originalId, originalMeta, item, newMeta, false);
addSimpleReplacement(originalId, originalMeta, item, newMeta, false);
}
/**
@@ -350,10 +400,12 @@ public class ItemStackReplacementManager {
* @param skipBlockRemap Set to true to skip auto-adding a item remapper if the .
*/
@SuppressWarnings("unused")
public static void addItemReplacement(String originalId, int originalMeta, Item item, int newMeta,
public static void addSimpleReplacement(String originalId, int originalMeta, Item item, int newMeta,
boolean skipBlockRemap) {
if (originalId == null) throw new IllegalArgumentException("original id is null");
if (item == null) throw new IllegalArgumentException("item is null");
SimpleTransformationRegistry.addSimpleTransformer(originalId, originalMeta, item, newMeta, skipBlockRemap);
}
// endregion simple replacements
}
@@ -0,0 +1,79 @@
package com.gtnewhorizons.postea.api;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.oredict.OreDictionary;
import com.gtnewhorizons.postea.utility.BlockConversionInfo;
public abstract class PosteaBlockStackTransformer {
public String originalId;
public int originalMeta;
public PosteaBlockStackTransformer(String originalId) {
this(originalId, OreDictionary.WILDCARD_VALUE);
}
public PosteaBlockStackTransformer(String originalId, int originalMeta) {
this.originalId = originalId;
this.originalMeta = originalMeta;
}
public void register() {
if (this instanceof IComplexStackTransformer cst) {
if (this.originalMeta == OreDictionary.WILDCARD_VALUE) {
ItemStackReplacementManager.addTransformationHandler(
this.originalId,
(originalId, tag) -> complexStackTransformerCheckMeta(originalId, tag, cst));
} else {
ItemStackReplacementManager.addTransformationHandler(this.originalId, cst::transformStack);
}
}
if (this instanceof IComplexBlockTransformer cbt) {
if (this.originalMeta == OreDictionary.WILDCARD_VALUE) {
BlockReplacementManager
.addTransformationHandler(this.originalId, (info) -> complexBlockTransformerCheckMeta(info, cbt));
} else {
BlockReplacementManager.addTransformationHandler(this.originalId, cbt::transformBlock);
}
}
if (this instanceof ISimpleBlockTransformer sbt) {
BlockReplacementManager.addSimpleReplacement(
this.originalId,
this.originalMeta,
sbt.getTargetBlock(),
sbt.getTargetBlockMeta(),
true);
}
if (this instanceof ISimpleItemTransformer sit) {
ItemStackReplacementManager.addSimpleReplacement(
this.originalId,
this.originalMeta,
sit.getTargetItem(),
sit.getTargetItemMeta(),
true);
}
if (this instanceof ISimpleStackTransformer sst) {
ItemStack stack = sst.getTargetStack();
ItemStackReplacementManager.addSimpleReplacement(
this.originalId,
this.originalMeta,
stack.getItem(),
Items.feather.getDamage(stack),
true);
}
}
private boolean complexStackTransformerCheckMeta(String originalId, NBTTagCompound tag,
IComplexStackTransformer st) {
if (tag.getShort("Damage") != originalMeta) return false;
return st.transformStack(originalId, tag);
}
private boolean complexBlockTransformerCheckMeta(BlockConversionInfo info, IComplexBlockTransformer bt) {
if (info.metadata != originalMeta) return false;
return bt.transformBlock(info);
}
}
@@ -1,20 +1,23 @@
package com.gtnewhorizons.postea.api;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import com.google.common.collect.LinkedListMultimap;
import com.gtnewhorizons.postea.utility.BlockInfo;
import com.gtnewhorizons.postea.utility.PosteaUtilities;
import com.gtnewhorizons.postea.utility.TransformerRegistry;
/**
* Public API used to register tile entity transformers via postea's handler system.<br>
* <br>
* Tile entity transformers are executed before any block transformers, when chunks being loading into the world.
*/
@SuppressWarnings("unused")
public class TileEntityReplacementManager {
private static final LinkedListMultimap<String, TriFunction<NBTTagCompound, World, Chunk, BlockInfo>> tileEntityToNormalBlockTransformer = LinkedListMultimap
.create();
public abstract class TileEntityReplacementManager {
/**
* @deprecated Superseded by {@link #tileEntityTransformer(String, TriFunction)}.
@@ -22,26 +25,57 @@ public class TileEntityReplacementManager {
@Deprecated
public static void tileEntityTransformer(String tileEntityId,
BiFunction<NBTTagCompound, World, BlockInfo> transformerFunction) {
tileEntityToNormalBlockTransformer
.put(tileEntityId, (tag, world, chunk) -> transformerFunction.apply(tag, world));
if (transformerFunction == null) throw new NullPointerException("transformer is null");
tileEntityTransformer(tileEntityId, (tag, world, chunk) -> transformerFunction.apply(tag, world));
}
/**
* Register a callback function to transform a tile entity before it's loaded.
* Callback returns a {@link BlockInfo} which the tile entity will be transformed into.</br>
* Note that the callback will be called before the world is fully loaded, and world.getBlock will throw an error.
* Use chunk.getBlock(x & 15, y, z & 15) instead.
* Callback returns a {@link BlockInfo} which the tile entity will be transformed into.
* If you return a null value instead of a block info, Postea will assume that you don't want to replace the block
* and/or tile entity and look for any other compatible handlers that may be able to do a replacement.<br>
* <br>
* Note that the callback will be called before the world is fully loaded, and {@code world.getBlock} will throw an
* error. If you need to identify a block, consider using
* {@link BlockReplacementManager#registerIDResolver(String, Consumer)}
* and {@link BlockAccessCompat#getBlockIDAtTE(NBTTagCompound, Chunk)} instead. If you need to identify the meta
* value, consider using {@code Chunk.getBlockMetadata(x & 15, y, z & 15)} or the other functions inside of
* {@link BlockReplacementManager}.<br>
* <br>
* {@code BlockReplacementManager.registerIDResolver} registers a simple callback that will be executed just after
* the game resolves all numeric ID conflicts when loading a save. The provided value is the numeric ID of the
* block linked provided string ID or -1 if the block has <em>never</em> been registered while the currently
* loading world.<br>
* <br>
* {@code BlockAccessCompat.getBlockIDAtTE} lets you fetch the ID of the block where the current tile entity is
* located. Since forge doesn't actually replace the id of missing blocks for performance reasons, opting to treat
* them like air instead; You can use the value given to the resolver you registered using
* {@code BlockReplacementManager.registerIDResolver} to identify the block directly.<br>
* <br>
* Using these 2 methods skips the need to register dummy blocks or items to identify blocks when migrating things,
* which will improve the overall performance and compatibility of your migrations.
*
* @param tileEntityId ID of the tile entity to be transformed
* @param transformerFunction Callback function to transforms the tile entity
*/
public static void tileEntityTransformer(String tileEntityId,
TriFunction<NBTTagCompound, World, Chunk, BlockInfo> transformerFunction) {
tileEntityToNormalBlockTransformer.put(tileEntityId, transformerFunction);
if (transformerFunction == null) throw new NullPointerException("transformer is null");
if (tileEntityId == null) throw new NullPointerException("transformer is null");
TransformerRegistry.addTileEntityTransformer(tileEntityId, transformerFunction);
}
public static Collection<TriFunction<NBTTagCompound, World, Chunk, BlockInfo>> getTileEntityToNormalBlockTransformerFunction(
String tileEntityId) {
return tileEntityToNormalBlockTransformer.get(tileEntityId);
/**
* Creates a bare-bones tile entity tag for the provided ID that clones all the positional information of the
* provided tile entity NBT Tag. The passed NBT tag is never modified.
*
* @param tag The tag of the tile entity at the current position.
* @param newId The ID of the new tile entity.
* @return A bare-bones tile entity tag for the provided id at ths same position as the passed tile entity tag.
*/
public static NBTTagCompound createTETagAtSamePosition(String newId, NBTTagCompound tag) {
if (tag == null) throw new NullPointerException("tag was null");
if (newId == null) throw new NullPointerException("tag was null");
return PosteaUtilities.cleanseNBT(newId, tag);
}
}
@@ -1,5 +1,13 @@
package com.gtnewhorizons.postea.api;
/**
* A simple function interface for functions with 3 parameters that must return a value.
*
* @param <T> The first parameter type.
* @param <U> The second parameter type.
* @param <O> The third parameter type.
* @param <R> The return type.
*/
@FunctionalInterface
public interface TriFunction<T, U, O, R> {
@@ -46,6 +46,7 @@ public final class PosteaLoadingPlugin implements IEarlyMixinLoader, IFMLLoading
mixins.add("MixinItemStack");
mixins.add("MixinChunk");
mixins.add("MixinAnvilChunkLoader");
mixins.add("MixinNBTTagList");
return mixins;
}
}
@@ -0,0 +1,49 @@
package com.gtnewhorizons.postea.mixins;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
public final class PosteaPreInitLoadingPlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String mixinPackage) {
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return true;
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
}
@Override
public List<String> getMixins() {
List<String> mixins = new ArrayList<>();
mixins.add("MixinGameData");
return mixins;
}
@Override
public void preApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {
}
@Override
public void postApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {
}
}
@@ -4,7 +4,6 @@ import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -12,7 +11,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.gtnewhorizons.postea.mixins.interfaces.IChunkMixin;
import com.gtnewhorizons.postea.utility.ChunkFixerUtility;
import com.llamalad7.mixinextras.sugar.Local;
@@ -23,53 +21,17 @@ public abstract class MixinAnvilChunkLoader {
@Inject(method = "readChunkFromNBT", at = @At("RETURN"), require = 1)
private void postea$chunkHook(CallbackInfoReturnable<Chunk> cir, @Local Chunk chunk,
@Local(argsOnly = true) World world, @Local(ordinal = 0, argsOnly = true) NBTTagCompound tag) {
IChunkMixin chunkMixin = (IChunkMixin) chunk;
chunkMixin.Postea$setPosteaCode(-1);
if (tag.hasKey("POSTEA")) {
chunkMixin.Postea$setPosteaCode(tag.getLong("POSTEA"));
}
// This checks if the chunk has been run with the current POSTEA_UPDATE_CODE and skips it if so.
if (chunkMixin.Postea$getPosteaCode() == ChunkFixerUtility.POSTEA_UPDATE_CODE) return;
for (ExtendedBlockStorage ebs : chunk.getBlockStorageArray()) {
if (ebs == null) {
continue;
}
ChunkFixerUtility.transformNormalBlocks(chunk, ebs, world);
}
}
@Inject(
method = "loadEntities",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/nbt/NBTTagCompound;getTagList(Ljava/lang/String;I)Lnet/minecraft/nbt/NBTTagList;",
ordinal = 1),
require = 1)
private void postea$transformTileEntities(CallbackInfo ci, @Local(ordinal = 0) NBTTagCompound tag,
@Local World world, @Local Chunk chunk) {
IChunkMixin chunkMixin = (IChunkMixin) chunk;
// This checks if the chunk has been run with the current POSTEA_UPDATE_CODE and skips it if so.
if (chunkMixin.Postea$getPosteaCode() == ChunkFixerUtility.POSTEA_UPDATE_CODE) return;
ChunkFixerUtility.transformTileEntities(tag, chunk, world);
ChunkFixerUtility.onChunkRead(chunk, world, tag);
}
@Inject(method = "loadChunk", at = @At("RETURN"))
private void postea$updatePosteaChunkCode(World world, int i, int j, CallbackInfoReturnable<Chunk> cir) {
Chunk chunk = cir.getReturnValue();
if (chunk instanceof IChunkMixin iChunkMixin) {
if (iChunkMixin.Postea$getPosteaCode() != ChunkFixerUtility.POSTEA_UPDATE_CODE) {
iChunkMixin.Postea$setPosteaCode(ChunkFixerUtility.POSTEA_UPDATE_CODE);
chunk.setChunkModified();
}
}
ChunkFixerUtility.onChunkLoaded(cir.getReturnValue());
}
@Inject(method = "writeChunkToNBT", at = @At("HEAD"))
private void postea$writePosteaChunkCode(Chunk chunk, World world, NBTTagCompound nbtTagCompound, CallbackInfo ci) {
if (chunk instanceof IChunkMixin iChunkMixin) {
nbtTagCompound.setLong("POSTEA", iChunkMixin.Postea$getPosteaCode());
}
ChunkFixerUtility.onChunkWrite(chunk, nbtTagCompound);
}
}
@@ -0,0 +1,28 @@
package com.gtnewhorizons.postea.mixins.early;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import com.gtnewhorizons.postea.mixins.interfaces.IMixinNBTTagList;
@Mixin(NBTTagList.class)
public class MixinNBTTagList implements IMixinNBTTagList {
@Shadow
@Final
private List tagList = new ArrayList();
@Override
@Unique
public void Postea$replaceCompoundTagAt(int index, NBTTagCompound tag) {
tagList.set(index, tag);
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.postea.mixins.interfaces;
import net.minecraft.nbt.NBTTagCompound;
public interface IMixinNBTTagList {
void Postea$replaceCompoundTagAt(int index, NBTTagCompound tag);
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.postea.mixins.preinit;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.gtnewhorizons.postea.utility.IDRegistry;
import cpw.mods.fml.common.registry.GameData;
@Mixin(value = GameData.class, remap = false)
public abstract class MixinGameData {
@Inject(
method = "injectWorldIDMap(Ljava/util/Map;Ljava/util/Set;Ljava/util/Map;Ljava/util/Map;Ljava/util/Set;Ljava/util/Set;ZZ)Ljava/util/List;",
at = @At("HEAD"),
require = 1)
@SuppressWarnings("unused")
private static void Postea$injectWorldIDMap(Map<String, Integer> dataList, Set<Integer> blockedIds,
Map<String, String> blockAliases, Map<String, String> itemAliases, Set<String> blockSubstitutions,
Set<String> itemSubstitutions, boolean injectFrozenData, boolean isLocalWorld,
CallbackInfoReturnable<List<String>> cir) {
IDRegistry.registerWorldSpecificIDs(dataList);
}
}
@@ -4,12 +4,35 @@ import net.minecraft.world.World;
public final class BlockConversionInfo {
/**
* The namespaced ID of the block being converted.
*/
public final String blockName;
/**
* The numeric ID of the block currently being replaced, or of the block you want it replaced with.
*/
public int blockID;
/**
* The meta value of the block currently being replaced, or value you want it replaced with.
*/
public int metadata;
/**
* The x coordinate of the block being replaced.
*/
public final int x;
/**
* The y coordinate of the block being replaced.
*/
public final int y;
/**
* The z coordinate of the block being replaced.
*/
public final int z;
/**
* The world containing the block being replaced. It won't be loaded fully when a block transformer is executed, so
* any call to something like world.getBlock() will throw an error.
*/
public final World world;
public BlockConversionInfo(String blockName, int blockID, int metadata, int x, int y, int z, World world) {
@@ -11,12 +11,29 @@ public final class BlockInfo {
public final int metadata;
public final Function<NBTTagCompound, NBTTagCompound> tileTransformer;
/**
* Used to notify postea that you wish to update a tile entity, and the block situated at that same location.
*
* @param block The block that should replace the block currently stored at the tile entity's location.
* @param metadata The meta value of the block being placed where the tile entity used to exist.
* @param tileTransformer A transformer function for the NBT data of the tile entity. (you can just edit the one
* passed to the transformer instead, it's executed right after you return this block info,
* just make sure to provide a pass though function otherwise we won't know you want to keep
* the tile entity alive.)
*/
public BlockInfo(Block block, int metadata, Function<NBTTagCompound, NBTTagCompound> tileTransformer) {
this.block = block;
this.metadata = metadata;
this.tileTransformer = tileTransformer;
}
/**
* Used to replace a tile entity with a block that does not need any tile entity data. Any existing data will be
* erased when returning a block info created with this constructor.
*
* @param block The block that should replace the block currently stored at the tile entity's location.
* @param metadata The meta value of the block being placed where the tile entity used to exist.
*/
public BlockInfo(Block block, int metadata) {
this.block = block;
this.metadata = metadata;
@@ -1,6 +1,5 @@
package com.gtnewhorizons.postea.utility;
import static com.gtnewhorizons.postea.utility.PosteaUtilities.getModListHash;
import static com.gtnewhorizons.postea.utility.TransformerRegistry.getBlockReplacement;
import java.util.ArrayList;
@@ -13,21 +12,72 @@ import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import com.gtnewhorizons.postea.api.TileEntityReplacementManager;
import com.gtnewhorizons.postea.api.TriFunction;
import com.gtnewhorizons.postea.compat.Compat;
import com.gtnewhorizons.postea.compat.SubChunkAccess;
import akka.japi.Pair;
import com.gtnewhorizons.postea.mixins.interfaces.IChunkMixin;
import com.gtnewhorizons.postea.mixins.interfaces.IMixinNBTTagList;
public class ChunkFixerUtility {
// This will not change between runs, unless a mod is updated or added.
public static final int POSTEA_UPDATE_CODE = getModListHash();
public static final int POSTEA_UPDATE_CODE = PosteaUtilities.getModListHash();
public static boolean hasChunkBeenUpdated(long chunkHash) {
// We are in a dev env.
if (POSTEA_UPDATE_CODE == -1) return false;
return chunkHash == POSTEA_UPDATE_CODE;
}
private static final int AIR_ID = 0;
public static void transformNormalBlocks(Chunk chunk, ExtendedBlockStorage ebs, World world) {
public static void onChunkRead(Chunk chunk, World world, NBTTagCompound tag) {
IChunkMixin chunkMixin = (IChunkMixin) chunk;
chunkMixin.Postea$setPosteaCode(-1);
if (tag.hasKey("POSTEA", 4)) {
chunkMixin.Postea$setPosteaCode(tag.getLong("POSTEA"));
}
// This checks if the chunk has been run with the current POSTEA_UPDATE_CODE and skips it if so.
if (hasChunkBeenUpdated(chunkMixin.Postea$getPosteaCode())) return;
List<ConversionInfo> conversionInfoList = adjustTileEntities(tag.getTagList("TileEntities", 10), world, chunk);
for (ExtendedBlockStorage ebs : chunk.getBlockStorageArray()) {
if (ebs == null) continue;
int sectionY = ebs.getYLocation();
List<ConversionInfo> filteredList = conversionInfoList.stream()
.filter(info -> info.y >= sectionY && info.y < (sectionY + 16))
.collect(Collectors.toList());
for (ConversionInfo info : filteredList) {
int localX = info.x & 15;
int localY = info.y & 15;
int localZ = info.z & 15;
ebs.func_150818_a(localX, localY, localZ, info.blockInfo.block);
ebs.setExtBlockMetadata(localX, localY, localZ, info.blockInfo.metadata);
}
transformNormalBlocksInSubChunk(chunk, ebs, world);
}
chunkMixin.Postea$setPosteaCode(ChunkFixerUtility.POSTEA_UPDATE_CODE);
}
public static void onChunkLoaded(Chunk chunk) {
if (chunk instanceof IChunkMixin iChunkMixin) {
if (!hasChunkBeenUpdated(iChunkMixin.Postea$getPosteaCode())) {
iChunkMixin.Postea$setPosteaCode(ChunkFixerUtility.POSTEA_UPDATE_CODE);
chunk.setChunkModified();
}
}
}
public static void onChunkWrite(Chunk chunk, NBTTagCompound tag) {
if (chunk instanceof IChunkMixin iChunkMixin) {
tag.setLong("POSTEA", iChunkMixin.Postea$getPosteaCode());
}
}
public static void transformNormalBlocksInSubChunk(Chunk chunk, ExtendedBlockStorage ebs, World world) {
int chunkXPos = chunk.xPosition * 16;
int chunkZPos = chunk.zPosition * 16;
@@ -62,54 +112,15 @@ public class ChunkFixerUtility {
}
}
public static void transformTileEntities(NBTTagCompound levelCompoundTag, Chunk chunk, World world) {
Pair<List<ConversionInfo>, NBTTagList> output = adjustTileEntities(
levelCompoundTag.getTagList("TileEntities", 10),
world,
chunk);
List<ConversionInfo> conversionInfoList = output.first();
NBTTagList tileEntities = output.second();
if (tileEntities.tagCount() > 0) {
levelCompoundTag.setTag("TileEntities", tileEntities);
}
for (ExtendedBlockStorage ebs : chunk.getBlockStorageArray()) {
if (ebs == null) continue;
processSection(ebs, conversionInfoList);
}
}
private static void processSection(ExtendedBlockStorage ebs, List<ConversionInfo> conversionInfoList) {
int sectionY = ebs.getYLocation();
List<ConversionInfo> filteredList = conversionInfoList.stream()
.filter(info -> info.y >= sectionY && info.y < (sectionY + 16))
.collect(Collectors.toList());
for (ConversionInfo info : filteredList) {
int localX = info.x & 15;
int localY = info.y & 15;
int localZ = info.z & 15;
ebs.func_150818_a(localX, localY, localZ, info.blockInfo.block);
ebs.setExtBlockMetadata(localX, localY, localZ, info.blockInfo.metadata);
}
}
private static Pair<List<ConversionInfo>, NBTTagList> adjustTileEntities(NBTTagList tileEntities, World world,
Chunk chunk) {
private static List<ConversionInfo> adjustTileEntities(NBTTagList tileEntities, World world, Chunk chunk) {
List<ConversionInfo> conversionInfo = new ArrayList<>();
NBTTagList tileEntitiesCopy = new NBTTagList();
for (int i = 0; i < tileEntities.tagCount(); i++) {
NBTTagCompound tileEntity = tileEntities.getCompoundTagAt(i);
String tileEntityId = tileEntity.getString("id");
// Check if we have a transformer registered for this tile entity ID
boolean found = false;
for (TriFunction<NBTTagCompound, World, Chunk, BlockInfo> transformationFunction : TileEntityReplacementManager
for (TriFunction<NBTTagCompound, World, Chunk, BlockInfo> transformationFunction : TransformerRegistry
.getTileEntityToNormalBlockTransformerFunction(tileEntityId)) {
int x = tileEntity.getInteger("x");
@@ -119,21 +130,18 @@ public class ChunkFixerUtility {
BlockInfo blockInfo = transformationFunction.apply(tileEntity, world, chunk);
if (blockInfo == null) continue;
if (blockInfo.tileTransformer != null) {
tileEntitiesCopy.appendTag(blockInfo.tileTransformer.apply(tileEntity));
} // Otherwise they are removed, therefore not appended.
if (blockInfo.tileTransformer == null) {
tileEntities.removeTag(i--);
} else {
((IMixinNBTTagList) tileEntities)
.Postea$replaceCompoundTagAt(i, blockInfo.tileTransformer.apply(tileEntity));
}
conversionInfo.add(new ConversionInfo(x, y, z, blockInfo));
found = true;
break;
}
if (!found) {
// Do nothing.
tileEntitiesCopy.appendTag(tileEntity);
}
}
return new Pair<>(conversionInfo, tileEntitiesCopy);
return conversionInfo;
}
private static class ConversionInfo {
@@ -0,0 +1,133 @@
package com.gtnewhorizons.postea.utility;
import java.util.Map;
import java.util.function.Consumer;
import com.google.common.collect.LinkedHashMultimap;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.LoaderState;
import cpw.mods.fml.common.registry.GameData;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
/**
* A simple utility to resolve the IDs of any namepsaced ID that has ever been registered to an active world's id list.
*/
public abstract class IDRegistry {
// Part of a bi-map for lookups between block namespaced ids and numeric ids
private static final Object2IntOpenHashMap<String> BLOCK_NAME_TO_ID = new Object2IntOpenHashMap<>();
// Part of a bi-map for lookups between item namespaced ids and numeric ids
private static final Object2IntOpenHashMap<String> ITEM_NAME_TO_ID = new Object2IntOpenHashMap<>();
private static final LinkedHashMultimap<String, Consumer<Integer>> BLOCK_ID_RESOLVERS = LinkedHashMultimap.create();
private static final LinkedHashMultimap<String, Consumer<Integer>> ITEM_ID_RESOLVERS = LinkedHashMultimap.create();
/**
* Triggers all the callbacks registered by other mods that wish to identify a block that may no longer exist.
* These callbacks effectively remove the need for dummies, and should help improve performance at runtime.
*/
public static void onMappingUpdated() {
// this event is fired on server startup and shutdown. On shutdown, we just need to clear the mappings to save
// some space memory we can just ignore adding stuff safely.
if (!Loader.instance()
.isInState(LoaderState.SERVER_ABOUT_TO_START)) return;
// we should be fine to execute those in parallel I think?
BLOCK_ID_RESOLVERS.asMap()
.entrySet()
.parallelStream()
.forEach(kv -> {
int id = getItemId(kv.getKey());
for (Consumer<Integer> resolver : kv.getValue()) {
resolver.accept(id);
}
});
ITEM_ID_RESOLVERS.asMap()
.entrySet()
.parallelStream()
.forEach(kv -> {
int id = getItemId(kv.getKey());
for (Consumer<Integer> resolver : kv.getValue()) {
resolver.accept(id);
}
});
}
/**
* Called by {@link com.gtnewhorizons.postea.mixins.preinit.MixinGameData}, this function is used to register all
* the previously registered namespaced to numeric id mappings that have ever been seen into a world.<br>
* <br>
* This function executes before the game starts remapping it's own internal IDs, so we can be sure that any
* existing will remain while the world is loaded.
*/
public static void registerWorldSpecificIDs(Map<String, Integer> map) {
BLOCK_NAME_TO_ID.clear();
ITEM_NAME_TO_ID.clear();
for (Map.Entry<String, Integer> kv : map.entrySet()) {
String name = kv.getKey()
.substring(1);
int id = kv.getValue();
if (kv.getKey()
.charAt(0) == '\u0001') {
BLOCK_NAME_TO_ID.put(name, id);
} else {
ITEM_NAME_TO_ID.put(name, id);
}
}
}
/**
* Fetches the block numeric ID associated with the namespaced ID.
*
* @param name the namespaced id of the block.
* @return The ID of the block, or -1 if the namespaced ID has never been registered while the current world was
* loaded.
*/
public static int getBlockId(String name) {
int id = BLOCK_NAME_TO_ID.getOrDefault(name, -1);
if (id < 0) {
id = GameData.getBlockRegistry()
.getId(name);
if (id >= 0) BLOCK_NAME_TO_ID.put(name, id);
}
return id;
}
/**
* Fetches the item numeric ID associated with the namespaced ID.
*
* @param name the namespaced id of the block.
* @return The ID of the block, or -1 if the namespaced ID has never been registered while the current world was
* loaded.
*/
public static int getItemId(String name) {
int id = ITEM_NAME_TO_ID.getOrDefault(name, -1);
if (id < 0) {
id = GameData.getItemRegistry()
.getId(name);
if (id >= 0) ITEM_NAME_TO_ID.put(name, id);
}
return id;
}
/**
* Registers a callback used by mods to identify the numeric ID of a namespaced ID when a world is loaded.
*
* @param name The namespaced ID to resolve.
* @param resolver The callback to call just after forge adjusts the ID mappings to prevent collisions.
*/
public static void registerBlockIDResolver(String name, Consumer<Integer> resolver) {
BLOCK_ID_RESOLVERS.put(name, resolver);
}
/**
* Registers a callback used by mods to identify the numeric ID of a namespaced ID when a world is loaded.
*
* @param name The namespaced ID to resolve.
* @param resolver The callback to call just after forge adjusts the ID mappings to prevent collisions.
*/
public static void registerItemIDResolver(String name, Consumer<Integer> resolver) {
ITEM_ID_RESOLVERS.put(name, resolver);
}
}
@@ -1,116 +1,40 @@
package com.gtnewhorizons.postea.utility;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import com.gtnewhorizons.postea.Postea;
import com.gtnewhorizons.postea.compat.Compat;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
/**
* A simple utility used to allow mods to register simple missing mapping migrations without having to set up a whole
* system themselves.
*/
public abstract class MissingMappingHandler {
enum MappingType {
/** Tells the mapping logic to register an Item. */
Item,
/** Tells the mapping logic to register a Block (and ItemBlock). */
Block
}
static class DummyMapping {
/** The type of thing that should be registered for this dummy */
public MappingType type;
/** The id under which the dummy should be registered */
public String registerAs;
/** The full id of the dummy, including mod prefix */
public String id;
public DummyMapping(MappingType type, String registerAs, String id) {
this.type = type;
this.registerAs = registerAs;
this.id = id;
}
}
// because we're using reflection to quickly identify our stuff at runtime.
public static class DummyBlock extends Block {
public DummyBlock() {
super(Material.ground);
}
}
public static class DummyItem extends Item {
}
public static class DummyItemBlock extends ItemBlock {
public DummyItemBlock(Block p_i45328_1_) {
super(p_i45328_1_);
}
}
private static final Set<String> MISSING_IDS_TO_IGNORE = new HashSet<>();
private static final HashMap<String, Item> MISSING_ITEM_MAPPINGS = new HashMap<>();
private static final HashMap<String, Block> MISSING_BLOCK_MAPPINGS = new HashMap<>();
private static final HashMap<String, DummyMapping> DUMMY_TARGET_TYPES = new HashMap<>();
private static final HashMap<String, String> DUMMY_ID_TO_ORIGINAL_ID_LOOKUP = new HashMap<>();
private static final HashMap<String, String> ORIGINAL_ID_TO_DUMMY_ID_LOOKUP = new HashMap<>();
private static final IntOpenHashSet DUMMY_IDS = new IntOpenHashSet();
private static final String DUMMY_ID_PREFIX_START = Postea.MODID + ":";
private static final String DUMMY_ID_PREFIX_END = "d.";
private static final String DUMMY_ID_PREFIX = DUMMY_ID_PREFIX_START + DUMMY_ID_PREFIX_END;
public static void onLoadCompleted() {
for (Map.Entry<String, DummyMapping> kv : DUMMY_TARGET_TYPES.entrySet()) {
DummyMapping mapping = kv.getValue();
String originalId = kv.getKey();
// split the original id into parts to query for the original's existance.
String[] splitId = splitItemId(originalId);
if (splitId == null) continue;
// ensure nothing else is registered under that id.
if (GameRegistry.findBlock(splitId[0], splitId[1]) != null) continue;
if (GameRegistry.findItem(splitId[0], splitId[1]) != null) continue;
// remap if not found
if (mapping.type == MappingType.Block) {
// check if the block was since registered.
Block dummy = new DummyBlock();
GameRegistry.registerBlock(dummy, DummyItemBlock.class, mapping.registerAs);
// register both for when we're remapping from ourselves to ourselves because something wasn't loaded
// and transformed.
MISSING_BLOCK_MAPPINGS.put(originalId, dummy);
Compat.hideItemFromNEI(Item.getItemFromBlock(dummy));
} else {
Item dummy = new DummyItem();
GameRegistry.registerItem(dummy, mapping.registerAs);
MISSING_ITEM_MAPPINGS.put(originalId, dummy);
Compat.hideItemFromNEI(dummy);
}
DUMMY_ID_TO_ORIGINAL_ID_LOOKUP.put(mapping.id, originalId);
ORIGINAL_ID_TO_DUMMY_ID_LOOKUP.put(originalId, mapping.id);
}
// no longer needed free what ever used to be in there
DUMMY_TARGET_TYPES.clear();
}
/**
* A set of String IDs that can safely be ignored.
*/
private static final Set<String> MISSING_IDS_TO_IGNORE = new ObjectOpenHashSet<>();
/**
* A set of string IDs and items they should map to.
*/
private static final Map<String, Item> MISSING_ITEM_MAPPINGS = new Object2ObjectOpenHashMap<>();
/**
* A set of string IDs and items they should map to.
*/
private static final Map<String, Block> MISSING_BLOCK_MAPPINGS = new Object2ObjectOpenHashMap<>();
// region FML Life Cycle handlers
public static void onMissingMapping(FMLMissingMappingsEvent event) {
for (FMLMissingMappingsEvent.MissingMapping mapping : event.getAll()) {
// ignore things that can be ignored safely, and our own dummy items for when a handler is removed.
if (MISSING_IDS_TO_IGNORE.contains(mapping.name) || mapping.name.startsWith(DUMMY_ID_PREFIX)) {
if (MISSING_IDS_TO_IGNORE.contains(mapping.name)) {
mapping.ignore();
continue;
}
@@ -135,70 +59,35 @@ public abstract class MissingMappingHandler {
}
}
}
// endregion FML Life Cycle handlers
private static void onMappingAdded(String originalId, MappingType mappingType) {
// split name into parts
String[] splitId = splitItemId(originalId);
if (splitId == null) return;
// else register a fake item
final String registerAs = DUMMY_ID_PREFIX_END + splitId[0] + "." + splitId[1];
final String dummyFullId = DUMMY_ID_PREFIX_START + registerAs;
if (mappingType == MappingType.Item) {
DUMMY_TARGET_TYPES.putIfAbsent(originalId, new DummyMapping(mappingType, registerAs, dummyFullId));
} else {
DummyMapping existing = DUMMY_TARGET_TYPES.getOrDefault(originalId, null);
if (existing != null) {
existing.type = mappingType;
} else {
DUMMY_TARGET_TYPES.put(originalId, new DummyMapping(mappingType, registerAs, dummyFullId));
}
}
}
public static void addSimpleReplacement(String originalId, Item item) {
/**
* Adds a direct mapping for a missing item mapping.
*
* @param originalId The namespaced id of the missing item.
* @param item The item it should be replaced with.
*/
public static void addItemMapping(String originalId, Item item) {
MISSING_ITEM_MAPPINGS.put(originalId, item);
}
public static void addSimpleReplacement(String originalId, Block block) {
/**
* Adds a direct mapping for a missing block mapping.
*
* @param originalId The namespaced id of the missing block.
* @param block The block it should be replaced with.
*/
public static void addBlockMapping(String originalId, Block block) {
MISSING_BLOCK_MAPPINGS.put(originalId, block);
}
/**
* Adds a notice that FML should silence any warning when loading a world telling the player that an ID
* is now missing.
*
* @param originalId The namespace ID to ignore.
*/
public static void addIgnore(String originalId) {
MISSING_IDS_TO_IGNORE.add(originalId);
}
public static void createDummyBlockIfNeeded(String originalId) {
DummyMapping existing = DUMMY_TARGET_TYPES.get(originalId);
// item mappings should be over-written
if (existing != null && existing.type == MappingType.Block) return;
onMappingAdded(originalId, MappingType.Block);
}
public static void createDummyItemIfNeeded(String originalId) {
DummyMapping existing = DUMMY_TARGET_TYPES.get(originalId);
if (existing != null) return;
onMappingAdded(originalId, MappingType.Item);
}
public static String getOriginalIDIfIDIsDummyID(String originalId) {
return DUMMY_ID_TO_ORIGINAL_ID_LOOKUP.getOrDefault(originalId, originalId);
}
public static String getDummyIdIfMissing(String originalId) {
return ORIGINAL_ID_TO_DUMMY_ID_LOOKUP.getOrDefault(originalId, originalId);
}
private static @Nullable String[] splitItemId(String itemId) {
int pos = itemId.indexOf(':');
if (pos < 1) return null;
return new String[] { itemId.substring(0, pos), itemId.substring(pos + 1) };
}
public static boolean isDummyItem(Item item) {
return item instanceof DummyItem || item instanceof DummyItemBlock;
}
public static boolean isDummyBlock(Block block) {
return block instanceof DummyBlock;
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.postea.utility;
import java.util.ArrayList;
import java.util.Collections;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
@@ -30,6 +31,9 @@ public abstract class PosteaUtilities {
}
public static int getModListHash() {
// when in a dev env allow mutations to happen every time a chunk is loaded for testing purposes.
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
if (deobfEnv != null && (boolean) deobfEnv) return -1;
// We iterate over every mod, get its version and put it into a giant list.
// This is done so we can see if our mods have actually changed, if so, we
@@ -10,55 +10,97 @@ import net.minecraftforge.oredict.OreDictionary;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
public class SimpleTransformationMap<T> {
/**
* A simple mapping registry for quick access to simple transformations.<br>
* <br>
* It's essentially a 2 layer map.
* The first layer contains the namespaced ID of the thing that needs to be replaced.
* The second layer contains mappings for each meta/damage value of the namespaced ID.<br>
* <br>
* When registering something to the second/meta layer, Using {@link OreDictionary#WILDCARD_VALUE}
* will register a fallback for all meta/damage value of the namespaced ID.<br>
* <br>
* Here is an example of the typical structure of this map
*
* <pre>
* {@code
* ┌─ mod:name_one ─┬─ 1 -> {Stone, 0} : Transforms all mod:name_one with meta 1 into stone with meta 0
* │ └─ wildcard -> {Plank, Wildcard} : Transforms all other mod:name_one to planks of the same meta.
* │
* ├─ mod:name_two ─── 2 -> {Clay, 0} : transforms all mod:name_two with meta 2 to clay with meta 0
* │
* └─ mod:name_three ─┬─ 0 -> {Air, 0} : transforms all mod:name_three with meta 0 to air with meta 0
* ├─ 1 -> {Sand, 1} : transforms all mod:name_three with meta 1 to sand with meta 1
* └─ 5 -> {SandStone, Wildcard} : transforms all mod:name_three to sandstone with the same meta.
* }
* </pre>
*
* @param <T> Either {@link net.minecraft.block.Block} or {@link net.minecraft.item.Item}
*/
class SimpleTransformationMap<T> {
private final Map<String, Map<Integer, T>> registry = new Object2ObjectOpenHashMap<>();
public static class Value<T> {
/**
* The target block or item for the simple transformation
*/
public final T target;
/**
* The target meta/damage value for the transformation.
* If the value is {@link OreDictionary#WILDCARD_VALUE}, It maintains the old meta/damage value.
*/
public final short targetMeta;
/**
* The numeric id of the target block, updated during the FMLMappingUpdate event
*/
public int targetRuntimeId;
public Value(T target, short targetMeta) {
this.target = target;
this.targetMeta = targetMeta;
this.targetRuntimeId = -1;
}
}
private final Map<String, Map<Integer, Value<T>>> registry = new Object2ObjectOpenHashMap<>();
/**
* Puts a mapping in this registry.
*
* @param originalId The original id of the item/block to replace.
* @param originalMeta The metadata of the item/block to replace. OreDictionary.WILDCARD_VALUE can be used as a
* wildcard.
* @param originalId The namespaced ID of the item or block to replace.
* @param originalMeta The meta or damage value of the item or block to replace.
* {@link OreDictionary#WILDCARD_VALUE} can be used as a wildcard/fallback value.
* When querying, the value will be replaced, which will
* be used when ever no other specific meta match is found.
*/
public void put(String originalId, int originalMeta, T data) {
public void put(String originalId, int originalMeta, T target, short targetMeta) {
if (originalMeta == -1) originalMeta = OreDictionary.WILDCARD_VALUE;
this.registry.computeIfAbsent(originalId, k -> new Int2ObjectOpenHashMap<>())
.put(originalMeta, data);
.put(originalMeta, new Value<>(target, targetMeta));
}
/**
* Gets the mapping from the registry.
* Used to query sub-maps at runtime since we capture the relevant sub-map into the transformation handler that is
* registered for simple transformations. This allows us to not re-query the first layer of this map while
* performing simple transformations, which should hopefully increase performance.
*
* @param id The id of the item/block to replace.
* @param meta The meta of the item/block to replace.
* @return The new id and meta for the item/block if found.
* @param subMap The sub-map to query.
* @param meta The meta/damage value of the block/item being transformed.
* @return The mapping if found, else null.
* @param <T> The type of the sub-map,
*/
public @Nullable T get(String id, int meta) {
Map<Integer, T> metaMap = this.registry.get(id);
if (metaMap == null) return null;
T mapping = metaMap.get(meta);
public static <T> @Nullable Value<T> getFromSubMap(Map<Integer, Value<T>> subMap, int meta) {
Value<T> mapping = subMap.get(meta);
if (mapping != null) return mapping;
return metaMap.get(OreDictionary.WILDCARD_VALUE);
}
public static <T> @Nullable T getFromSubmap(Map<Integer, T> metaMap, int meta) {
T mapping = metaMap.get(meta);
if (mapping != null) return mapping;
return metaMap.get(OreDictionary.WILDCARD_VALUE);
return subMap.get(OreDictionary.WILDCARD_VALUE);
}
/**
* @return the set of all registered item ids.
* Used to update runtime IDs during the MappingUpdate event.
*
* @return The set of all registered item ids
*/
public Set<String> keySet() {
return this.registry.keySet();
}
/**
* @return the set of all registered item ids.
*/
public Set<Map.Entry<String, Map<Integer, T>>> entrySet() {
public Set<Map.Entry<String, Map<Integer, Value<T>>>> entrySet() {
return this.registry.entrySet();
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.postea.utility;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
@@ -8,12 +10,8 @@ import net.minecraft.item.ItemBlock;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.commons.lang3.tuple.Pair;
import com.gtnewhorizons.postea.api.IDExtenderCompat;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
/**
* A registry for simple item and block transformations. A common use case is mod removal, replacements or deprecations.
* When replacing one mod with another in GTNH, we have to migrate most if not all the mod's items and this often means
@@ -30,49 +28,47 @@ public class SimpleTransformationRegistry {
/**
* Stores simple block transformations (eg: if old id is # with meta # then turn into item with id # with meta #.)
*/
public static final SimpleTransformationMap<Pair<Block, Short>> SIMPLE_BLOCK_TRANSFORMATION_MAP = new SimpleTransformationMap<>();
public static final Object2IntOpenHashMap<Block> RUNTIME_BLOCK_ID_LOOKUP = new Object2IntOpenHashMap<>();
private static final SimpleTransformationMap<Block> SIMPLE_BLOCK_TRANSFORMATION_MAP = new SimpleTransformationMap<>();
/**
* Stores simple item transformations (eg: if old id is # with meta # then turn into item with id # with meta #.)
*/
public static final SimpleTransformationMap<Pair<Item, Short>> SIMPLE_ITEM_TRANSFORMATION_MAP = new SimpleTransformationMap<>();
public static final Object2IntOpenHashMap<Item> RUNTIME_ITEM_ID_LOOKUP = new Object2IntOpenHashMap<>();
private static final SimpleTransformationMap<Item> SIMPLE_ITEM_TRANSFORMATION_MAP = new SimpleTransformationMap<>();
// region FML Life Cycle handlers
/**
* Registers 1 handler per targeted ID to minimize the number of handlers. The meta-map is captured to reduce
* the execution time of the handlers.
* the execution time of the handlers. This is done at the load completed to hopefully always register these
* handlers last, allowing all other user made transformers to execute first.
*/
public static void onLoadCompleted() {
// add handlers for simple block remaps
for (Map.Entry<String, Map<Integer, Pair<Block, Short>>> kv : SIMPLE_BLOCK_TRANSFORMATION_MAP.entrySet()) {
Map<Integer, Pair<Block, Short>> subMap = kv.getValue();
TransformerRegistry.addBlockTransformer(kv.getKey(), info -> simpleBlockTransformer(info, subMap));
for (Map.Entry<String, Map<Integer, SimpleTransformationMap.Value<Block>>> kv : SIMPLE_BLOCK_TRANSFORMATION_MAP
.entrySet()) {
TransformerRegistry.addBlockTransformer(kv.getKey(), new SimpleBlockTransformationHandler(kv.getValue()));
}
// add handlers for simple item remaps
for (Map.Entry<String, Map<Integer, Pair<Item, Short>>> kv : SIMPLE_ITEM_TRANSFORMATION_MAP.entrySet()) {
Map<Integer, Pair<Item, Short>> subMap = kv.getValue();
TransformerRegistry
.addStackTransformer(kv.getKey(), (originalId, tag) -> simpleItemStackTransformer(tag, subMap));
for (Map.Entry<String, Map<Integer, SimpleTransformationMap.Value<Item>>> kv : SIMPLE_ITEM_TRANSFORMATION_MAP
.entrySet()) {
TransformerRegistry.addStackTransformer(kv.getKey(), new SimpleItemTransformationHandler(kv.getValue()));
}
}
/**
* Pre-caches updated block and item IDs for any simple transformation to speed-up the handlers.
* Pre-caches updated block and item numeric IDs for any simple transformation to speed up the handlers.
*/
public static void onIdMappingsChanged() {
RUNTIME_ITEM_ID_LOOKUP.clear();
RUNTIME_BLOCK_ID_LOOKUP.clear();
for (Map.Entry<String, Map<Integer, Pair<Block, Short>>> kv : SIMPLE_BLOCK_TRANSFORMATION_MAP.entrySet()) {
for (Pair<Block, Short> mapping : kv.getValue()
for (Map.Entry<String, Map<Integer, SimpleTransformationMap.Value<Block>>> kv : SIMPLE_BLOCK_TRANSFORMATION_MAP
.entrySet()) {
for (SimpleTransformationMap.Value<Block> mapping : kv.getValue()
.values()) {
RUNTIME_BLOCK_ID_LOOKUP.computeIfAbsent(mapping.getKey(), Block::getIdFromBlock);
mapping.targetRuntimeId = Block.getIdFromBlock(mapping.target);
}
}
for (Map.Entry<String, Map<Integer, Pair<Item, Short>>> kv : SIMPLE_ITEM_TRANSFORMATION_MAP.entrySet()) {
for (Pair<Item, Short> mapping : kv.getValue()
for (Map.Entry<String, Map<Integer, SimpleTransformationMap.Value<Item>>> kv : SIMPLE_ITEM_TRANSFORMATION_MAP
.entrySet()) {
for (SimpleTransformationMap.Value<Item> mapping : kv.getValue()
.values()) {
RUNTIME_ITEM_ID_LOOKUP.computeIfAbsent(mapping.getKey(), Item::getIdFromItem);
mapping.targetRuntimeId = Item.getIdFromItem(mapping.target);
}
}
}
@@ -104,11 +100,11 @@ public class SimpleTransformationRegistry {
public static void addSimpleTransformer(String originalId, int originalMeta, Block newBlock, int newMeta,
boolean skipStackRemap) {
if (newMeta == -1) newMeta = OreDictionary.WILDCARD_VALUE;
SIMPLE_BLOCK_TRANSFORMATION_MAP.put(originalId, originalMeta, Pair.of(newBlock, (short) newMeta));
SIMPLE_BLOCK_TRANSFORMATION_MAP.put(originalId, originalMeta, newBlock, (short) newMeta);
if (!skipStackRemap) {
Item item = Item.getItemFromBlock(newBlock);
if (item != null) {
SIMPLE_ITEM_TRANSFORMATION_MAP.put(originalId, originalMeta, Pair.of(item, (short) newMeta));
SIMPLE_ITEM_TRANSFORMATION_MAP.put(originalId, originalMeta, item, (short) newMeta);
}
}
}
@@ -118,14 +114,25 @@ public class SimpleTransformationRegistry {
*
* @implNote This implementation has constant-time runtime.
*/
private static boolean simpleBlockTransformer(BlockConversionInfo info, Map<Integer, Pair<Block, Short>> metaMap) {
Pair<Block, Short> mapping = SimpleTransformationMap.getFromSubmap(metaMap, info.metadata);
if (mapping == null) return false;
info.blockID = RUNTIME_BLOCK_ID_LOOKUP.computeIfAbsent(mapping.getKey(), Block::getIdFromBlock);
if (mapping.getValue() != OreDictionary.WILDCARD_VALUE) {
info.metadata = mapping.getValue();
private static class SimpleBlockTransformationHandler implements Function<BlockConversionInfo, Boolean> {
private final Map<Integer, SimpleTransformationMap.Value<Block>> metaMap;
SimpleBlockTransformationHandler(Map<Integer, SimpleTransformationMap.Value<Block>> metaMap) {
this.metaMap = metaMap;
}
@Override
public Boolean apply(BlockConversionInfo info) {
SimpleTransformationMap.Value<Block> mapping = SimpleTransformationMap
.getFromSubMap(metaMap, info.metadata);
if (mapping == null || mapping.targetRuntimeId <= -1) return false;
info.blockID = mapping.targetRuntimeId;
if (mapping.targetMeta != OreDictionary.WILDCARD_VALUE) {
info.metadata = mapping.targetMeta;
}
return true;
}
return true;
}
// endregion block transformations
@@ -159,9 +166,9 @@ public class SimpleTransformationRegistry {
boolean skipBlockRemap) {
if (newMeta == -1) newMeta = OreDictionary.WILDCARD_VALUE;
if (!skipBlockRemap && newItem instanceof ItemBlock ib) {
SIMPLE_BLOCK_TRANSFORMATION_MAP.put(originalId, originalMeta, Pair.of(ib.field_150939_a, (short) newMeta));
SIMPLE_BLOCK_TRANSFORMATION_MAP.put(originalId, originalMeta, ib.field_150939_a, (short) newMeta);
}
SIMPLE_ITEM_TRANSFORMATION_MAP.put(originalId, originalMeta, Pair.of(newItem, (short) newMeta));
SIMPLE_ITEM_TRANSFORMATION_MAP.put(originalId, originalMeta, newItem, (short) newMeta);
}
/**
@@ -169,16 +176,25 @@ public class SimpleTransformationRegistry {
*
* @implNote This implementation has constant-time runtime.
*/
private static boolean simpleItemStackTransformer(NBTTagCompound tag, Map<Integer, Pair<Item, Short>> metaMap) {
short meta = tag.getShort("Damage");
Pair<Item, Short> mapping = SimpleTransformationMap.getFromSubmap(metaMap, meta);
if (mapping == null) return false;
IDExtenderCompat
.setItemStackID(tag, RUNTIME_ITEM_ID_LOOKUP.computeIfAbsent(mapping.getKey(), Item::getIdFromItem));
if (mapping.getValue() != OreDictionary.WILDCARD_VALUE) {
tag.setShort("Damage", mapping.getValue());
private static class SimpleItemTransformationHandler implements BiFunction<String, NBTTagCompound, Boolean> {
private final Map<Integer, SimpleTransformationMap.Value<Item>> metaMap;
SimpleItemTransformationHandler(Map<Integer, SimpleTransformationMap.Value<Item>> metaMap) {
this.metaMap = metaMap;
}
@Override
public Boolean apply(String originalId, NBTTagCompound tag) {
short meta = tag.getShort("Damage");
SimpleTransformationMap.Value<Item> mapping = SimpleTransformationMap.getFromSubMap(metaMap, meta);
if (mapping == null || mapping.targetRuntimeId <= -1) return false;
IDExtenderCompat.setItemStackID(tag, mapping.targetRuntimeId);
if (mapping.targetMeta != OreDictionary.WILDCARD_VALUE) {
tag.setShort("Damage", mapping.targetMeta);
}
return true;
}
return true;
}
// endregion item transformations
}
@@ -1,23 +1,26 @@
package com.gtnewhorizons.postea.utility;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.LinkedListMultimap;
import com.gtnewhorizons.postea.api.IDExtenderCompat;
import com.gtnewhorizons.postea.api.TriFunction;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.LoaderState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
/**
* A registry for all transformer functions
@@ -28,7 +31,8 @@ public class TransformerRegistry {
.create();
public static final LinkedListMultimap<String, Function<BlockConversionInfo, Boolean>> BLOCK_REPLACEMENT_MAP = LinkedListMultimap
.create();
private static final LinkedListMultimap<String, TriFunction<NBTTagCompound, World, Chunk, BlockInfo>> TILE_ENTITY_REPLACEMENT_MAP = LinkedListMultimap
.create();
/**
* Map of itemID -> originalName+transformationHandler.
* Pivoting on the id is faster than pivoting on the strings so this should help with perf a bit.
@@ -41,11 +45,40 @@ public class TransformerRegistry {
* Generated during the mapping update event whilst entering/loading a world.
*/
private static final Int2ObjectOpenHashMap<Pair<String, List<Function<BlockConversionInfo, Boolean>>>> RUNTIME_BLOCK_REPLACEMENT_MAP = new Int2ObjectOpenHashMap<>();
// region FML Life Cycle handlers
/**
* List of ids used by dummy items and blocks generated by Postea.
* Generated during the mapping update event whilst entering/loading a world.
* When FML loads a world, it remaps certain IDs if the IDs it registered during the load period do not match the
* IDs that were previously registered when the world was last loaded.
* <br>
* We use this opportunity to fetch the "world specific" IDs of all the blocks and items that have a
* transformation registered to them so we can pivot using the numeric IDs when we're doing replacements.
* <br>
* This is because pivoting on the numeric ids is significantly faster than pivoting on string or object hashes.
*/
public static final IntOpenHashSet DUMMY_IDS = new IntOpenHashSet();
public static void onIdMappingsChanged() {
RUNTIME_BLOCK_REPLACEMENT_MAP.clear();
RUNTIME_ITEM_REPLACEMENT_MAP.clear();
// this event is fired on server startup and shutdown. On shutdown, we just need to clear the mappings to save
// some space memory we can just ignore adding stuff safely.
if (!Loader.instance()
.isInState(LoaderState.SERVER_ABOUT_TO_START)) return;
// create a lookup for source block ids.
for (String key : BLOCK_REPLACEMENT_MAP.keys()) {
int id = IDRegistry.getBlockId(key);
if (id >= 0) {
RUNTIME_BLOCK_REPLACEMENT_MAP.put(id, Pair.of(key, BLOCK_REPLACEMENT_MAP.get(key)));
}
}
// create a lookup for source block ids.
for (String key : ITEM_REPLACEMENT_MAP.keys()) {
int id = IDRegistry.getItemId(key);
if (id >= 0) {
RUNTIME_ITEM_REPLACEMENT_MAP.put(id, Pair.of(key, ITEM_REPLACEMENT_MAP.get(key)));
}
}
}
// endregion FML Life Cycle handlers
/**
* Registers a block transformation handler function for the given id.
@@ -60,8 +93,8 @@ public class TransformerRegistry {
* @param originalId The namespaced id of the block to transform.
* @param transformer The transformation handler that will execute a transformation on the block.
*/
public static void addBlockTransformer(String originalId, Function<BlockConversionInfo, Boolean> transformer) {
MissingMappingHandler.createDummyBlockIfNeeded(originalId);
public static void addBlockTransformer(@Nonnull String originalId,
@Nonnull Function<BlockConversionInfo, Boolean> transformer) {
BLOCK_REPLACEMENT_MAP.put(originalId, transformer);
}
@@ -78,50 +111,30 @@ public class TransformerRegistry {
* @param originalId The namespaced id of the block to transform.
* @param transformer The transformation handler that will execute a transformation on the block.
*/
public static void addStackTransformer(String originalId, BiFunction<String, NBTTagCompound, Boolean> transformer) {
MissingMappingHandler.createDummyItemIfNeeded(originalId);
public static void addStackTransformer(@Nonnull String originalId,
@Nonnull BiFunction<String, NBTTagCompound, Boolean> transformer) {
ITEM_REPLACEMENT_MAP.put(originalId, transformer);
}
// region FML Life Cycle handlers
public static void onIdMappingsChanged() {
DUMMY_IDS.clear();
RUNTIME_BLOCK_REPLACEMENT_MAP.clear();
RUNTIME_ITEM_REPLACEMENT_MAP.clear();
// create a lookup for source block ids.
for (String key : BLOCK_REPLACEMENT_MAP.keys()) {
String name = MissingMappingHandler.getDummyIdIfMissing(key);
if (Block.blockRegistry.getObject(name) instanceof Block block) {
int id = Block.getIdFromBlock(block);
RUNTIME_BLOCK_REPLACEMENT_MAP.put(id, Pair.of(key, BLOCK_REPLACEMENT_MAP.get(key)));
// not using .equals() on purpose, I'm looking for instance, not content equality
if (key != name) DUMMY_IDS.add(id);
}
}
// create a lookup for source block ids.
for (String key : ITEM_REPLACEMENT_MAP.keys()) {
String name = MissingMappingHandler.getDummyIdIfMissing(key);
if (Item.itemRegistry.getObject(name) instanceof Item item) {
int id = Item.getIdFromItem(item);
RUNTIME_ITEM_REPLACEMENT_MAP.put(id, Pair.of(key, ITEM_REPLACEMENT_MAP.get(key)));
// not using .equals() on purpose, I'm looking for instance, not content equality
if (key != name) DUMMY_IDS.add(id);
}
}
/**
* Registers an item transformation handler function for the given id.
*
* @implNote It doesn't matter if the id doesn't belong to an existing item, a dummy item will be created when
* Postea detects that the id isn't associated to an item during the FMLLoadCompleted event. Dependent
* mods don't need to worry about this as transformation handlers are given the original id associated
* with the nbt they are provided when the handler is executed. When Postea runs a transformation on a
* dummy item and no handler returns true, Postea will automatically invalidate the stack by removing
* its id and idExt fields.
*
* @param originalId The namespaced id of the block to transform.
* @param transformer The transformation handler that will execute a transformation on the block.
*/
public static void addTileEntityTransformer(@Nonnull String originalId,
@Nonnull TriFunction<NBTTagCompound, World, Chunk, BlockInfo> transformer) {
TILE_ENTITY_REPLACEMENT_MAP.put(originalId, transformer);
}
public static void onServerStopping() {
RUNTIME_BLOCK_REPLACEMENT_MAP.clear();
RUNTIME_ITEM_REPLACEMENT_MAP.clear();
DUMMY_IDS.clear();
}
// endregion FML Life Cycle handlers
// region transformation handlers
public static boolean isDummyId(int id) {
return TransformerRegistry.DUMMY_IDS.contains(id);
}
public static @Nullable BlockConversionInfo getBlockReplacement(int blockId, byte metadata, World world, int x,
int y, int z) {
Pair<String, List<Function<BlockConversionInfo, Boolean>>> data = RUNTIME_BLOCK_REPLACEMENT_MAP.get(blockId);
@@ -137,19 +150,9 @@ public class TransformerRegistry {
world);
for (Function<BlockConversionInfo, Boolean> transformer : data.getValue()) {
if (transformer.apply(blockConversionInfo)) {
// Not sure if we want to keep this around since this would affect perf a bit, good for debugging
// though.
// if (DUMMY_IDS.contains(result.blockID)) FMLLog.warning("returned a success on a non-transformed
// block!");
return blockConversionInfo;
}
}
// if we're transforming a dummy block from a missing mapping and it wasn't handled, delete the block.
if (TransformerRegistry.isDummyId(blockId)) {
blockConversionInfo.blockID = 0;
blockConversionInfo.metadata = 0;
return blockConversionInfo;
}
return null;
}
@@ -160,24 +163,20 @@ public class TransformerRegistry {
int id = IDExtenderCompat.getItemStackID(tag);
Pair<String, List<BiFunction<String, NBTTagCompound, Boolean>>> data = RUNTIME_ITEM_REPLACEMENT_MAP.get(id);
// abort early if handler not found
if (data == null || data.getValue()
.isEmpty()) return;
// apply handlers
for (BiFunction<String, NBTTagCompound, Boolean> transformer : data.getValue()) {
if (transformer.apply(data.getKey(), tag)) {
// Not sure if we want to keep this around since this would affect perf a bit, good for debugging
// though.
// if (DUMMY_IDS.contains(IDExtenderCompat.getItemStackID(tag))) FMLLog.warning("returned a success on a
// non-transformed block!");
return;
if (data != null && !data.getValue()
.isEmpty()) {
// apply handlers
for (BiFunction<String, NBTTagCompound, Boolean> transformer : data.getValue()) {
if (transformer.apply(data.getKey(), tag)) {
return;
}
}
}
// if no handler overwrote anything on this stack, and it's a dummy, delete the stack.
if (TransformerRegistry.isDummyId(id)) {
tag.removeTag("id");
tag.removeTag("idExt");
}
}
public static Collection<TriFunction<NBTTagCompound, World, Chunk, BlockInfo>> getTileEntityToNormalBlockTransformerFunction(
String tileEntityId) {
return TILE_ENTITY_REPLACEMENT_MAP.get(tileEntityId);
}
// endregion transformation handlers
}
+2 -1
View File
@@ -8,7 +8,8 @@
"mixins": [
"MixinAnvilChunkLoader",
"MixinChunk",
"MixinItemStack"
"MixinItemStack",
"MixinNBTTagList"
],
"client": [],
"server": []
+3 -1
View File
@@ -1,8 +1,10 @@
{
"required": true,
"minVersion": "0.8.5-GTNH",
"package": "com.gtnewhorizons.postea.mixins.preinit",
"plugin": "com.gtnewhorizons.postea.mixins.PosteaPreInitLoadingPlugin",
"refmap": "mixins.postea.refmap.json",
"target": "@env(DEFAULT)",
"target": "@env(PREINIT)",
"compatibilityLevel": "JAVA_8",
"mixins": [],
"client": [],