mirror of
https://github.com/GTNewHorizons/Postea.git
synced 2026-10-25 20:32:22 +00:00
Add support for multiple mods remapping the same item/block/tile entity. (#18)
This commit is contained in:
1 parent
2641e10174
commit
c59ec9bb36
36 files changed
+2904
-310
No files matched your search
@@ -1,94 +1,516 @@
|
||||
# TileEntityReplacementManager API
|
||||
|
||||
This API provides a suite of tools for transforming blocks and items in the game, allowing developers to replace or modify game elements based on specific conditions.
|
||||
This library provides a suite of tools for transforming any existing tile entities, blocks and items the world at
|
||||
runtime. Allowing developers to replace, migrate or modify game elements based on specific conditions.
|
||||
|
||||
## Features
|
||||
|
||||
1. **Tile Entity Transformation**: Replace or modify tile entities.
|
||||
2. **Block Replacement**: Replace normal blocks with another normal block.
|
||||
3. **ItemStack Replacement**: Modify or replace items.
|
||||
1. **Tile Entity Transformers**: Replace or modify tile entities.
|
||||
2. **Block Transformers**: Replace normal blocks with another normal block.
|
||||
3. **ItemStack Transformers**: Replace or Modify items.
|
||||
4. **Simple Replacement API**: Efficiently replace or remap large amounts of blocks and items at scale without incurring any noticeable performance impact.
|
||||
5. **Missing Mapping Replacement API**: A set of API endpoints to register actions to be taken when FML detects a missing mapping for a given ID.
|
||||
6. **Numeric ID Identification for Removed Content**: A way to identify the ID of any content that has ceased to exist.
|
||||
|
||||
## Examples
|
||||
|
||||
### 1. Tile Entity Transformation
|
||||
### 1. Tile Entity to Regular Block Transformation
|
||||
|
||||
#### a. We provide the registered TE ID "GT_TileEntity_Ores" and a lambda function that takes in the NBTTagCompound for any matching tile entity in world and a World and returns a BlockInfo. This will replace all TE's with the ID "GT_TileEntity_Ores" with the block specified in the BlockInfo. The secondary parameter determines the damage value of the block. Returning null instead of a BlockInfo will leave the existing tile and block totally unchanged.
|
||||
The `TileEntityReplacementManager` class can be used to register tile entity transformers for any tile entities with
|
||||
the given ID. These transformers usually perform one of 3 types of interactions: Block replacements, NBT transformations
|
||||
and full-on replacements.
|
||||
|
||||
When doing a block replacement, you simply just return a `BlockInfo` without specifying an NBT transformer (the third
|
||||
argument of the constructor). This will replace the existing block and erase the presence of the tile entity being
|
||||
transformed.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Tile entity transformers are always applied before any block transformers.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Tile entity and block transformers are only ran once per chunk until a player's mod list changes. This behaviour is
|
||||
> disabled if you are in a dev environment; Allowing you to test your transformers simply by leaving and rejoining your
|
||||
> test world.
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Tile entity and block transformers are executed as the chunk data is being read and so the world being passed isn't
|
||||
> fully loaded. **If you try to perform any kind of block access using the world parameter, forge will throw an error.**
|
||||
>
|
||||
> **If you need to perform any kind of block access on the block located at the same place as the tile entity being
|
||||
> transformed use the `BlockAccessCompat`**. These endpoints will fetch the ID and meta value of that block without
|
||||
> causing any errors. You can then compare it to a value obtained from a callback registered using
|
||||
> `BlockReplacementManager.registerIDResolver` to match the block in question.
|
||||
>
|
||||
> If you try to use the chunk, to get a value, you may get an incorrect value if the block that was previously located
|
||||
> at the location of the tile entity no longer exists. The `BlockAccessCompat` endpoints are able to overcome this
|
||||
> issue and will allow you to identify block ids linked to content that no longer exists without the use of a
|
||||
> FMLMissingMapping remap.
|
||||
|
||||
Code example:
|
||||
```java
|
||||
TileEntityReplacementManager.tileEntityTransformer("GT_TileEntity_Ores", (tag, world) -> {
|
||||
if (world.provider.dimensionId == -1) {
|
||||
return new BlockInfo(Blocks.wool, 1);
|
||||
}
|
||||
if (world.provider.dimensionId == 1) {
|
||||
return new BlockInfo(Blocks.wool, 2);
|
||||
}
|
||||
return new BlockInfo(Blocks.wool, 3);
|
||||
});
|
||||
```
|
||||
|
||||
#### b. We do the same here, but now we convert a Furnace TE to a chest TE. We also need to convert the underlying block to a chest. In addtion to this we can provide a transformer that can edit the NBTTagCompound of the TE. This is useful for converting NBT data from one format to another. Here we simply edit the NBT to place stone in the middle slot.
|
||||
|
||||
```java
|
||||
TileEntityReplacementManager.tileEntityTransformer("Furnace", (tag, world, chunk) -> {
|
||||
return new BlockInfo(Blocks.chest, 0, Postea::chestTransformer);
|
||||
});
|
||||
|
||||
private static NBTTagCompound chestTransformer(NBTTagCompound oldTag) {
|
||||
|
||||
NBTTagCompound newTag = cleanseNBT("Chest", oldTag);
|
||||
|
||||
NBTTagList tagList = new NBTTagList();
|
||||
|
||||
NBTTagCompound stoneAtSlot13 = new NBTTagCompound();
|
||||
stoneAtSlot13.setByte("Count", (byte) 1);
|
||||
stoneAtSlot13.setByte("Slot", (byte) 13);
|
||||
stoneAtSlot13.setShort("Damage", (short) 0);
|
||||
stoneAtSlot13.setShort("id", (short) Block.getIdFromBlock(Blocks.stone));
|
||||
|
||||
tagList.appendTag(stoneAtSlot13);
|
||||
|
||||
newTag.setTag("Items", tagList);
|
||||
|
||||
return newTag;
|
||||
public abstract class TEToBlockExample {
|
||||
public static void postLoad() {
|
||||
// In this example, we look for any tile entity with the ID of `GT_TileEntity_Ores` and
|
||||
// replace it with are regular block with no tile entities. This is specified by returning
|
||||
// a `BlockInfo` that has no nbt transformation function.
|
||||
TileEntityReplacementManager.tileEntityTransformer("GT_TileEntity_Ores", (tag, world, chunk) -> {
|
||||
// yes, it's in great shame that we admit that Gregtech ores used to be tile entities.
|
||||
if (world.provider.dimensionId == -1) {
|
||||
// nether gets orange wool
|
||||
return new BlockInfo(Blocks.wool, 1);
|
||||
}
|
||||
if (world.provider.dimensionId == 1) {
|
||||
// the end gets magenta wool
|
||||
return new BlockInfo(Blocks.wool, 2);
|
||||
}
|
||||
// all other dimensions get light blue wool
|
||||
return new BlockInfo(Blocks.wool, 3);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
Remember that unlike items, tile entities manually store every tag of their nbt data individually, meaning that any additional tags will be discarded and cannot be used to store additional data unless it is already a part of the new tile entity.
|
||||
### 2. Tile Entity NBT Transformations
|
||||
|
||||
### 2. Normal Block Transformation
|
||||
If you need to edit an existing tile entity without replacing it, simply perform the modification as usual inside the
|
||||
main transformer function and return a `null` value instead of a `BlockInfo`. This will tell Postea that you haven't
|
||||
done any replacements and that any other handler targeting this tile entity type should also be able to perform their
|
||||
tasks.
|
||||
|
||||
#### a. The following example will replace all grass blocks with even x-coordinates with orange wool blocks.
|
||||
> [!NOTE]
|
||||
>
|
||||
> Technically you can also return a `BlockInfo` with the same block and meta value, and a pass though function for the
|
||||
> NBT transformer, _**but this is considered bad practice for pure NBT transformations**_.
|
||||
>
|
||||
> You should also note that when multiple tile entity transformers are registered to a single tile entity ID, they will
|
||||
> always be executed in order of their registrations.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> It's technically more efficient to use Postea to do data migrations since your TE's own code won't have to do extra
|
||||
> checks every time it reads its own NBT data.
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> Remember that unlike items, tile entities manually store every tag of their NBT data individually, meaning that any
|
||||
> additional tags will be discarded if the tile entity doesn't read it when it reads it's reloads it's state by
|
||||
> reading it's stored NBT data.
|
||||
|
||||
Code example:
|
||||
```java
|
||||
public abstract class TEModifyExample {
|
||||
|
||||
private static int reedsId = -1;
|
||||
private static int diamondId = -1;
|
||||
private static int stoneId = -1;
|
||||
|
||||
private static int record11Id = -1;
|
||||
|
||||
public static void postLoad() {
|
||||
// pre caching ids like this will lead to faster transformers
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:reeds", i -> reedsId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:diamond", i -> diamondId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:stone", i -> stoneId = i);
|
||||
TileEntityReplacementManager.tileEntityTransformer("Chest", (tag, world, chunk) -> {
|
||||
// This transformer replaces all sugar cane stacks inside a chest with
|
||||
// stone, unless it's in the 13th slot at which point it's replaced with
|
||||
// diamonds. All transformations are done without affecting the stack size.
|
||||
NBTTagList slots = tag.getTagList("Items", 10);
|
||||
for (int i = 0; i < slots.tagCount(); i++) {
|
||||
NBTTagCompound slot = slots.getCompoundTagAt(i);
|
||||
// only target the removed item
|
||||
if (IDExtenderCompat.getItemStackID(slot) != reedsId) continue;
|
||||
if (slot.getByte("Slot") == 13) {
|
||||
// replace it with diamonds if it's in slot 13
|
||||
IDExtenderCompat.setItemStackID(slot, diamondId);
|
||||
}
|
||||
else {
|
||||
// else replace it with stone.
|
||||
IDExtenderCompat.setItemStackID(slot, stoneId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// this tile entity transformer also executes because the other transformer returned null
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:record_11", i -> record11Id = i);
|
||||
byte targetSLot = (byte) 12;
|
||||
TileEntityReplacementManager.tileEntityTransformer("Chest", (tag, world, chunk) -> {
|
||||
// This transformer looks for the 12th slot of the chest and replaces
|
||||
// or sets it's content to a new stack of record 11 with a stack size of 1.
|
||||
NBTTagList slots = tag.getTagList("Items", 10);
|
||||
NBTTagCompound toEdit = null;
|
||||
for (int i = 0; i < slots.tagCount(); i++) {
|
||||
NBTTagCompound slot = slots.getCompoundTagAt(i);
|
||||
if (slot.getByte("Slot") != targetSLot) continue;
|
||||
toEdit = slot;
|
||||
break;
|
||||
}
|
||||
if (toEdit == null) {
|
||||
toEdit = new NBTTagCompound();
|
||||
toEdit.setByte("Slot", (byte) targetSLot);
|
||||
slots.appendTag(toEdit);
|
||||
}
|
||||
toEdit.setByte("Count", (byte) 1);
|
||||
toEdit.setShort("Damage", (short) 0);
|
||||
IDExtenderCompat.setItemStackID(toEdit, stoneId);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Full Tile Entity Replacements
|
||||
|
||||
If you need to fully replace a tile entity, your tile entity transformer should return a `BlockInfo` that includes an
|
||||
NBT transformation callback.
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> When you're doing a full replacement, changing the NBT parameter outside the NBT transformation callback is considered
|
||||
> to be bad practice. It's only acceptable, (and even reccomended to do so) if you're performing a pure NBT
|
||||
> transformation like in the last example.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> If your NBT transformer returns `null`, the previous tile entity data will simply be removed. Then what ever block you
|
||||
> specified can now simply create its own tile entity from scratch when it's loaded.
|
||||
|
||||
Code example:
|
||||
```java
|
||||
public abstract class TEFullReplacementExample {
|
||||
|
||||
public static int litFurnaceId = -1;
|
||||
public static int unlitFurnaceId = -1;
|
||||
public static int fireChargeId = -1;
|
||||
|
||||
public static void postLoad() {
|
||||
BlockReplacementManager.registerIDResolver("minecraft:lit_furnace", i -> litFurnaceId = i);
|
||||
BlockReplacementManager.registerIDResolver("minecraft:furnace", i -> unlitFurnaceId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:fire_charge", i -> fireChargeId = i);
|
||||
TileEntityReplacementManager.tileEntityTransformer("Furnace", TEFullReplacementExample::transformFurnace);
|
||||
}
|
||||
|
||||
/**
|
||||
* This transformer transforms a furnace into a chest, and adds a fire charge in the 4th slot of the chest if
|
||||
* the block where the tile entity once stood was a lit furnace. If it's not either a lit or unlit furnace,
|
||||
* the block is simply transformed into air.
|
||||
*
|
||||
* @param tag The NBT data of the tile entity being transformed.
|
||||
* @param world The world in which the tile entity is located.
|
||||
* @param chunk The chunk in which the tile entity is located.
|
||||
* @return Null if we didn't update the block, else a BlockInfo with the transformation data you wish to
|
||||
* execute.
|
||||
*/
|
||||
private static BlockInfo transformFurnace(NBTTagCompound tag, World world, Chunk chunk) {
|
||||
int[] blockIdAndMeta = BlockAccessCompat.getBlockIDAndMetaAtTE(tag, chunk);
|
||||
int blockId = blockIdAndMeta[0];
|
||||
int meta = blockIdAndMeta[1];
|
||||
// we return a value this time because we are actually replacing the block and tile entity.
|
||||
// we copy the meta value since that preserves the orientation for these blocks.
|
||||
return new BlockInfo(Blocks.chest, meta, oldTag -> transformFurnaceNBT(oldTag, blockId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the content of the furnace to the chest and adds a fire charge in the 4th slot if the furnace was lit.
|
||||
*
|
||||
* @param oldTag The current NBT data ofthe tile entity being transformed.
|
||||
* @return null if we want the tile entity to be erased.
|
||||
*/
|
||||
private static NBTTagCompound transformFurnaceNBT(NBTTagCompound oldTag, int blockId) {
|
||||
// if the block wasn't a furnace, clear the current tile entity and let the chest spawn a new one.
|
||||
if (blockId != litFurnaceId && blockId != unlitFurnaceId) return null;
|
||||
// use this function to create a new tile entity tag with the same positional data as the old one.
|
||||
NBTTagCompound newTag = TileEntityReplacementManager.createTETagAtSamePosition("Chest", oldTag);
|
||||
NBTTagList newSlots = (NBTTagList) oldTag.getTagList("Items", 10)
|
||||
.copy();
|
||||
// if the block used to be a lit furnace add a fire charge in the 4th slot.
|
||||
if (blockId == litFurnaceId) {
|
||||
NBTTagCompound fireChargeStack = new NBTTagCompound();
|
||||
IDExtenderCompat.setItemStackID(fireChargeStack, fireChargeId);
|
||||
fireChargeStack.setByte("Slot", (byte) 4);
|
||||
fireChargeStack.setByte("Count", (byte) 1);
|
||||
fireChargeStack.setShort("Damage", (short) 0);
|
||||
newSlots.appendTag(fireChargeStack);
|
||||
}
|
||||
newTag.setTag("Items", newSlots);
|
||||
return newTag;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Simple Block and Item Replacements
|
||||
|
||||
If you need to transform an existing block or item into something else, Postea now features a set of API endpoints that
|
||||
let you register "Simple Transformations".
|
||||
|
||||
When applicable, we recommend using these endpoints as they will generally result in a faster execution time than if you
|
||||
were to implement your own handler. Another advantage is that multiple, completely distinct, mods can now target the
|
||||
same item ID for a simple transformation with zero impact to compatibility.
|
||||
|
||||
Simple Transformers can be used to perform one of 4 types of transformations:
|
||||
1. Some block or item with any meta or damage value -> some block or item, preserving the meta or damage value.
|
||||
- Behaves similarly to an FMLMissingMapping remap, but is more effective in the long term as it doesn't cause an ID
|
||||
aliasing.
|
||||
2. Some block or item with any meta or damage value -> some block or item with a specific meta or damage value.
|
||||
3. Some block or item with a specific meda or damage value -> some block or item, preserving the meta or damage value.
|
||||
4. Some block or item with a specific meta or damage value -> some block or item, with a specific meta or damage value.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> These simple transformers can be used to target blocks and items that no longer exist in the world due to a content
|
||||
> or mod removal. Postea can identify the numeric ID of any content that was once registered to a world's ID map. Even
|
||||
> if postea wasn't installed when those mods were installed or removed.
|
||||
>
|
||||
> This also applies to the `BlockReplacementManager.registerIDResolver` and `ItemStackReplacementManager.registerIDResolver`
|
||||
> functions. The callbacks registered by these function will return with a value of -1 which is impossible to get from
|
||||
> the `BlockAccessCompat` functions that return block IDs, (they return 0 (air) when encountering something which isn't
|
||||
> in the known ID map as a failsafe.)
|
||||
>
|
||||
> This means that technically it's better to use simple transformation to migrate removed content as it removes the
|
||||
> need for forge to create any kind of ID aliases which usually need to be resolved at runtime.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> `OreDictionary.WILDCARD_VALUE` has a meaning when registering simple transformations:
|
||||
>
|
||||
> When used as the **source** meta or damage value; Postea will use the transformation being specified as a **fallback**
|
||||
> if nothing has specified a meta or damage transformation for the block or item's current meta or damage value.
|
||||
>
|
||||
> When used as the **target** meta or damage value; Postea will **preserve the existing value** instead of over-writing it.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> When registering block or item conversions, Postea will automatically detect if the given item or block has a block or
|
||||
> item counterpart (respectively) and register an appropriate simple transformation. You can disable this behaviour by
|
||||
> adding a `true` argument to end of all simple transformation endpoints.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Simple transformations are usually executed after all other complex transformers.
|
||||
|
||||
Code example
|
||||
```java
|
||||
public abstract class SimpleTransformationExample {
|
||||
public static void postLoad() {
|
||||
// item/block -> item/block, preserving the Meta/Damage value.
|
||||
//
|
||||
// remap all block and items of colored stone blocks with wool of the same color
|
||||
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:colorStoneBrick", Blocks.wool);
|
||||
|
||||
// Item/Block with meta -> Item/Block, meta preserved
|
||||
//
|
||||
// angel ring:3 to disk 11
|
||||
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:angelRing", 3, new ItemStack(Items.record_11));
|
||||
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:angelRing", 4, new ItemStack(Items.record_13));
|
||||
|
||||
// Item/Block with meta -> Item/Block with specific meta
|
||||
//
|
||||
// remap all compressed cobble to oak planks
|
||||
//
|
||||
// When using a stack as a target the passed stack size is ignored,
|
||||
// only the contained item and meta value are kept.
|
||||
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 0, new ItemStack(Blocks.planks, 0, 0));
|
||||
|
||||
// block only with meta -> block with specific meta value.
|
||||
//
|
||||
// remap blocks of double compressed cobble to cobble.
|
||||
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 1, Blocks.cobblestone, 0, true);
|
||||
// item only with damage -> item only with specific damage value.
|
||||
//
|
||||
// remap stacks of double compressed cobble to oak saplings.
|
||||
ItemStackReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 1,new ItemStack(Blocks.sapling, 1, 0), true);
|
||||
|
||||
// Block/Item with any Meta/Damage -> Block/Item with specific Meta/Damage value.
|
||||
//
|
||||
// Using OreDictionary.WILDCARD_VALUE will tell postea to use this mapping as a fallback when
|
||||
// there is no transformer targeting the specific meta/damage value of the block/item.
|
||||
//
|
||||
// remap all other instances of compressed blocks to endstone
|
||||
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", OreDictionary.WILDCARD_VALUE, Blocks.end_stone, 0);
|
||||
|
||||
// Block/Item with any Meta/Damage -> Block/Item, preserving the Meta/Damage value.
|
||||
//
|
||||
// When passing OreDictionary.WILDCARD_VALUE as the target Meta/Damage value, Postea will
|
||||
// preserve any existing Meta/Damage value. This is also the default value when you don't
|
||||
// specify a target meta value.
|
||||
//
|
||||
// remap all other instances of compressed blocks to endstone
|
||||
BlockReplacementManager.addSimpleReplacement(
|
||||
"ExtraUtilities:cobblestone_compressed",
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
Blocks.end_stone,
|
||||
OreDictionary.WILDCARD_VALUE);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 5. Complex Block Transformers
|
||||
|
||||
If you need some custom logic in order to perform a block conversion, you can register a custom transformer using
|
||||
`BlockReplacementManager.addTransformationHandler`.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Returning `true` tells Postea to stop looking for any other handlers for the current block, and to replace the block
|
||||
> being transformed using the `blockID` and `metadata` currently stored in the `BlockConversionInfo` parameter.
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Just like for Tile Entity transformers, trying to perform block access using the world provided by the
|
||||
> BlockTransformationInfo parameter will result in an error as the transformers are executed before the world's content
|
||||
> are properly initialized.
|
||||
|
||||
```java
|
||||
BiFunction<BlockConversionInfo, World, BlockConversionInfo> blockTransformer = (blockConversionInfoOld, world) -> {
|
||||
public abstract class ComplexBlockTransformer {
|
||||
|
||||
BlockConversionInfo blockConversionInfoNew = new BlockConversionInfo();
|
||||
private static int woolId = -1;
|
||||
|
||||
if ((blockConversionInfoOld.x % 2 == 0)) {
|
||||
blockConversionInfoNew.blockID = Block.getIdFromBlock(Blocks.wool);
|
||||
blockConversionInfoNew.metadata = 1;
|
||||
} else {
|
||||
blockConversionInfoNew.blockID = blockConversionInfoOld.blockID;
|
||||
blockConversionInfoNew.metadata = blockConversionInfoOld.metadata;
|
||||
}
|
||||
public static void postLoad() {
|
||||
// This transformer replaces all grass blocks with even x-coordinates with orange wool.
|
||||
BlockReplacementManager.registerIDResolver("minecraft:wool", i -> woolId = i);
|
||||
BlockReplacementManager.addTransformationHandler("minecraft:grass", ComplexBlockTransformer::transformGrass);
|
||||
}
|
||||
|
||||
return blockConversionInfoNew;
|
||||
};
|
||||
private static boolean transformGrass(BlockConversionInfo info) {
|
||||
// only affect blocks that are on even x coordinates
|
||||
if (info.x % 2 != 0) return false;
|
||||
// transform all these blocks into orange wool.
|
||||
info.blockID = woolId;
|
||||
info.metadata = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockReplacementManager.addBlockReplacement("minecraft:grass", blockTransformer);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3. ItemStack Transformation
|
||||
### 6. Stack Transformation
|
||||
|
||||
#### a. The following example will replace all iron ingots with gold ingots.
|
||||
If you need some custom logic in order to perform an item conversion, you can register a custom transformer using
|
||||
`ItemStackReplacementManager.addTransformationHandler`.
|
||||
|
||||
Code example:
|
||||
```java
|
||||
Function<NBTTagCompound, NBTTagCompound> itemTransformer = (tag) -> {
|
||||
// Use the utility method for compatibility with NEID/EndlessIDs
|
||||
IDExtenderCompat.setItemStackID(tag, Item.getIdFromItem(Items.gold_ingot));
|
||||
return tag;
|
||||
};
|
||||
public abstract class ComplexItemTransformer {
|
||||
|
||||
ItemStackReplacementManager.addItemReplacement("minecraft:iron_ingot", itemTransformer);
|
||||
private static int writableBookId = -1;
|
||||
private static int woolId = -1;
|
||||
|
||||
private static void postLoad() {
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:writable_book", i -> writableBookId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:wool", i -> woolId = i);
|
||||
ItemStackReplacementManager.addTransformationHandler("minecraft:written_book", ComplexItemTransformer::unsignBook);
|
||||
}
|
||||
|
||||
/**
|
||||
* This transformer transforms a written book into either an unsigned book or orange wool depending on it's title.
|
||||
*
|
||||
* @param name The name of the item being transformed, even if that item doesn't exist anymore.
|
||||
* @param nbt The NBT data of the stack item being transformed.
|
||||
* @return True if we've actually transformed the item.
|
||||
*/
|
||||
private static boolean unsignBook(String name, NBTTagCompound nbt) {
|
||||
// check if the book has a title.
|
||||
if (!nbt.hasKey("tag",10)) return false;
|
||||
NBTTagCompound tag = nbt.getCompoundTag("tag");
|
||||
if (!tag.hasKey("title", 8)) return false;
|
||||
String title = tag.getString("title");
|
||||
if (title.equalsIgnoreCase("unsign me")) {
|
||||
// unsign the book
|
||||
IDExtenderCompat.setItemStackID(nbt, writableBookId);
|
||||
nbt.setShort("Damage", (short) 0);
|
||||
nbt.setByte("Count", (byte) 1);
|
||||
// clear the signed book nbt
|
||||
tag.removeTag("title");
|
||||
tag.removeTag("author");
|
||||
return true;
|
||||
}
|
||||
else if (title.equalsIgnoreCase("orange wool")) {
|
||||
// Transform it into orange wool.
|
||||
IDExtenderCompat.setItemStackID(nbt, woolId);
|
||||
nbt.setShort("Damage", (short) 1);
|
||||
nbt.setByte("Count", (byte) 1);
|
||||
// removes the NBT tag from the stack.
|
||||
nbt.removeTag("tag");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Remapping Missing Mappings using FML
|
||||
|
||||
Postea provides a few API endpoints to quickly register re-mappings for any given Item or Block that has been removed.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> It's generally more advisable to use the simple transformers instead as they will create less overhead in the
|
||||
> long term.
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> If you try to remap something to a block or item that already has ever existed at a prior date inside a world,
|
||||
> (eg: remapping to a block of dirt or stick, or something from your own mod that has existed for a while) FML will
|
||||
> throw an error when it loads the world and tell you that your world is corrupted.
|
||||
>
|
||||
> Simple Transformers are generally safer in that regard and do not care if a thing has ever existed in a world before.
|
||||
> Therefore we generally recommend using them instead of FML re-mappings.
|
||||
|
||||
Code example:
|
||||
```java
|
||||
public abstract class FMLReMappingExample {
|
||||
|
||||
// You have to make your own block class since the block constructor is protected.
|
||||
public static class MyBlock extends Block {
|
||||
protected MyBlock() {
|
||||
super(Material.ground);
|
||||
}
|
||||
}
|
||||
|
||||
public static void postLoad() {
|
||||
// this only works when the thing you're replacing with has never been registered to a world's
|
||||
// ID map. If you try to run this with a block that already exists (eg good old dirt), FML
|
||||
// error out and you that your world is corrupted beyond repair, and advise you to undo what
|
||||
// ever you just did.
|
||||
//
|
||||
// Postea Simple Transformers don't have that issue and are more lag friendly in the long term.
|
||||
// Consider using them when ever possible, instead of FML mappings.
|
||||
Item item = new Item();
|
||||
GameRegistry.registerItem(item, "my_item");
|
||||
ItemStackReplacementManager.replaceMissingMapping("IC2:itemFertilizer", item);
|
||||
|
||||
// This also remaps the stacks of IC2:blockCrop
|
||||
Block block = new MyBlock();
|
||||
GameRegistry.registerBlock(block, "my_block");
|
||||
BlockReplacementManager.replaceMissingMapping("IC2:blockCrop", block);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Ignoring Missing Mappings from FML
|
||||
|
||||
Postea provides a few API endpoints that let you tell FML to hide any missing mapping warnings regarding that ID when
|
||||
loading a world.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> You should only register an ignore mapping when you know that your transformers fully replace what ever used to be
|
||||
> associated with the given ID.
|
||||
|
||||
Code example:
|
||||
```java
|
||||
public abstract class FMLIgnoreMissingMappingExample {
|
||||
public static void postLoad() {
|
||||
// It doesn't really matter which one you use, they both do the same thing under the hood.
|
||||
ItemStackReplacementManager.ignoreMissingMapping("IC2:itemFertilizer");
|
||||
BlockReplacementManager.ignoreMissingMapping("IC2:blockCrop");
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -4,4 +4,5 @@ dependencies {
|
||||
compileOnly('com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev')
|
||||
compileOnly('com.falsepattern:chunkapi-mc1.7.10:0.6.0:dev')
|
||||
compileOnly('com.falsepattern:endlessids-mc1.7.10:1.6.0:dev')
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughItems:2.8.48-GTNH:dev") { transitive = false }
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package com.gtnewhorizons.postea;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
import com.gtnewhorizons.postea.api.BlockAccessCompat;
|
||||
import com.gtnewhorizons.postea.api.BlockReplacementManager;
|
||||
import com.gtnewhorizons.postea.api.IDExtenderCompat;
|
||||
import com.gtnewhorizons.postea.api.ItemStackReplacementManager;
|
||||
import com.gtnewhorizons.postea.api.TileEntityReplacementManager;
|
||||
import com.gtnewhorizons.postea.utility.BlockConversionInfo;
|
||||
import com.gtnewhorizons.postea.utility.BlockInfo;
|
||||
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
|
||||
abstract class ExampleMigrators {
|
||||
|
||||
static void postLoad() {
|
||||
TEToBlockExample.postLoad();
|
||||
TEModifyExample.postLoad();
|
||||
TEFullReplacementExample.postLoad();
|
||||
SimpleTransformationExample.postLoad();
|
||||
ComplexBlockTransformer.postLoad();
|
||||
ComplexItemTransformer.postLoad();
|
||||
FMLReMappingExample.postLoad();
|
||||
}
|
||||
|
||||
public static abstract class TEToBlockExample {
|
||||
|
||||
public static void postLoad() {
|
||||
// it's with great shame that we do admit that gregtech ores used to be tile entities.
|
||||
TileEntityReplacementManager.tileEntityTransformer("GT_TileEntity_Ores", (tag, world, chunk) -> {
|
||||
if (world.provider.dimensionId == -1) {
|
||||
// nether gets orange wool
|
||||
return new BlockInfo(Blocks.wool, 1);
|
||||
}
|
||||
if (world.provider.dimensionId == 1) {
|
||||
// the end gets magenta wool
|
||||
return new BlockInfo(Blocks.wool, 2);
|
||||
}
|
||||
// all other dimensions get light blue wool
|
||||
return new BlockInfo(Blocks.wool, 3);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class TEModifyExample {
|
||||
|
||||
private static int reedsId = -1;
|
||||
private static int diamondId = -1;
|
||||
private static int stoneId = -1;
|
||||
private static int record11Id = -1;
|
||||
|
||||
public static void postLoad() {
|
||||
// pre caching ids like this will lead to faster transformers
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:reeds", i -> reedsId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:diamond", i -> diamondId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:stone", i -> stoneId = i);
|
||||
TileEntityReplacementManager.tileEntityTransformer("Chest", (tag, world, chunk) -> {
|
||||
// This transformer replaces all sugar cane stacks inside a chest with
|
||||
// stone, unless it's in the 13th slot at which point it's replaced with
|
||||
// diamonds. All transformations are done without affecting the stack size.
|
||||
NBTTagList slots = tag.getTagList("Items", 10);
|
||||
for (int i = 0; i < slots.tagCount(); i++) {
|
||||
NBTTagCompound slot = slots.getCompoundTagAt(i);
|
||||
// only target the removed item
|
||||
if (IDExtenderCompat.getItemStackID(slot) != reedsId) continue;
|
||||
if (slot.getByte("Slot") == 13) {
|
||||
// replace it with diamonds if it's in slot 13
|
||||
IDExtenderCompat.setItemStackID(slot, diamondId);
|
||||
} else {
|
||||
// else replace it with stone.
|
||||
IDExtenderCompat.setItemStackID(slot, stoneId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// this tile entity transformer also executes because the other transformer returned null
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:record_11", i -> record11Id = i);
|
||||
byte targetSLot = (byte) 12;
|
||||
TileEntityReplacementManager.tileEntityTransformer("Chest", (tag, world, chunk) -> {
|
||||
// This transformer looks for the 12th slot of the chest and replaces
|
||||
// or sets it's content to a new stack of record 11 with a stack size of 1.
|
||||
NBTTagList slots = tag.getTagList("Items", 10);
|
||||
NBTTagCompound toEdit = null;
|
||||
for (int i = 0; i < slots.tagCount(); i++) {
|
||||
NBTTagCompound slot = slots.getCompoundTagAt(i);
|
||||
if (slot.getByte("Slot") != targetSLot) continue;
|
||||
toEdit = slot;
|
||||
break;
|
||||
}
|
||||
if (toEdit == null) {
|
||||
toEdit = new NBTTagCompound();
|
||||
toEdit.setByte("Slot", targetSLot);
|
||||
slots.appendTag(toEdit);
|
||||
}
|
||||
toEdit.setByte("Count", (byte) 1);
|
||||
toEdit.setShort("Damage", (short) 0);
|
||||
IDExtenderCompat.setItemStackID(toEdit, record11Id);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class TEFullReplacementExample {
|
||||
|
||||
public static int litFurnaceId = -1;
|
||||
public static int unlitFurnaceId = -1;
|
||||
public static int fireChargeId = -1;
|
||||
|
||||
public static void postLoad() {
|
||||
BlockReplacementManager.registerIDResolver("minecraft:lit_furnace", i -> litFurnaceId = i);
|
||||
BlockReplacementManager.registerIDResolver("minecraft:furnace", i -> unlitFurnaceId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:fire_charge", i -> fireChargeId = i);
|
||||
TileEntityReplacementManager.tileEntityTransformer("Furnace", TEFullReplacementExample::transformFurnace);
|
||||
}
|
||||
|
||||
/**
|
||||
* This transformer transforms a furnace into a chest, and adds a fire charge in the 4th slot of the chest if
|
||||
* the block where the tile entity once stood was a lit furnace. If it's not either a lit or unlit furnace,
|
||||
* the block is simply transformed into air.
|
||||
*
|
||||
* @param tag The NBT data of the tile entity being transformed.
|
||||
* @param world The world in which the tile entity is located.
|
||||
* @param chunk The chunk in which the tile entity is located.
|
||||
* @return Null if we didn't update the block, else a BlockInfo with the transformation data you wish to
|
||||
* execute.
|
||||
*/
|
||||
private static BlockInfo transformFurnace(NBTTagCompound tag, World world, Chunk chunk) {
|
||||
int[] blockIdAndMeta = BlockAccessCompat.getBlockIDAndMetaAtTE(tag, chunk);
|
||||
int blockId = blockIdAndMeta[0];
|
||||
int meta = blockIdAndMeta[1];
|
||||
// we return a value this time because we are actually replacing the block and tile entity.
|
||||
// we copy the meta value since that preserves the orientation for these blocks.
|
||||
return new BlockInfo(Blocks.chest, meta, oldTag -> transformFurnaceNBT(oldTag, blockId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the content of the furnace to the chest and adds a fire charge in the 4th slot if the furnace was lit.
|
||||
*
|
||||
* @param oldTag The current NBT data ofthe tile entity being transformed.
|
||||
* @return null if we want the tile entity to be erased.
|
||||
*/
|
||||
private static NBTTagCompound transformFurnaceNBT(NBTTagCompound oldTag, int blockId) {
|
||||
// if the block wasn't a furnace, clear the current tile entity and let the chest spawn a new one.
|
||||
if (blockId != litFurnaceId && blockId != unlitFurnaceId) {
|
||||
return null;
|
||||
}
|
||||
// use this function to create a new tile entity tag with the same positional data as the old one.
|
||||
NBTTagCompound newTag = TileEntityReplacementManager.createTETagAtSamePosition("Chest", oldTag);
|
||||
NBTTagList newSlots = (NBTTagList) oldTag.getTagList("Items", 10)
|
||||
.copy();
|
||||
// if the block used to be a lit furnace add a fire charge in the 4th slot.
|
||||
if (blockId == litFurnaceId) {
|
||||
NBTTagCompound fireChargeStack = new NBTTagCompound();
|
||||
IDExtenderCompat.setItemStackID(fireChargeStack, fireChargeId);
|
||||
fireChargeStack.setByte("Slot", (byte) 4);
|
||||
fireChargeStack.setByte("Count", (byte) 1);
|
||||
fireChargeStack.setShort("Damage", (short) 0);
|
||||
newSlots.appendTag(fireChargeStack);
|
||||
}
|
||||
newTag.setTag("Items", newSlots);
|
||||
return newTag;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class SimpleTransformationExample {
|
||||
|
||||
public static void postLoad() {
|
||||
// item/block -> item/block, preserving the Meta/Damage value.
|
||||
//
|
||||
// remap all block and items of colored stone blocks with wool of the same color
|
||||
BlockReplacementManager.addSimpleReplacement("ExtraUtilities:colorStoneBrick", Blocks.wool);
|
||||
|
||||
// Item/Block with meta -> Item/Block, meta preserved
|
||||
//
|
||||
// angel ring:3 to disk 11
|
||||
ItemStackReplacementManager
|
||||
.addSimpleReplacement("ExtraUtilities:angelRing", 3, new ItemStack(Items.record_11));
|
||||
ItemStackReplacementManager
|
||||
.addSimpleReplacement("ExtraUtilities:angelRing", 4, new ItemStack(Items.record_13));
|
||||
|
||||
// Item/Block with meta -> Item/Block with specific meta
|
||||
//
|
||||
// remap all compressed cobble to oak planks
|
||||
//
|
||||
// When using a stack as a target the passed stack size is ignored, only the contained item and meta value
|
||||
// are kept.
|
||||
ItemStackReplacementManager
|
||||
.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 0, new ItemStack(Blocks.planks, 0, 0));
|
||||
|
||||
// block only with meta -> block with specific meta value.
|
||||
//
|
||||
// remap blocks of double compressed cobble to cobble.
|
||||
BlockReplacementManager
|
||||
.addSimpleReplacement("ExtraUtilities:cobblestone_compressed", 1, Blocks.cobblestone, 0, true);
|
||||
// item only with damage -> item only with specific damage value.
|
||||
//
|
||||
// remap stacks of double compressed cobble to oak saplings.
|
||||
ItemStackReplacementManager.addSimpleReplacement(
|
||||
"ExtraUtilities:cobblestone_compressed",
|
||||
1,
|
||||
new ItemStack(Blocks.sapling, 1, 0),
|
||||
true);
|
||||
|
||||
// Block/Item with any Meta/Damage -> Block/Item with specific Meta/Damage value.
|
||||
//
|
||||
// Using OreDictionary.WILDCARD_VALUE will tell postea to use this mapping as a fallback when there is no
|
||||
// transformer targeting the specific meta/damage value of the block/item.
|
||||
//
|
||||
// remap all other instances of compressed blocks to endstone
|
||||
BlockReplacementManager.addSimpleReplacement(
|
||||
"ExtraUtilities:cobblestone_compressed",
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
Blocks.end_stone,
|
||||
0);
|
||||
|
||||
// Block/Item with any Meta/Damage -> Block/Item, preserving the Meta/Damage value.
|
||||
//
|
||||
// When passing OreDictionary.WILDCARD_VALUE as the target Meta/Damage value, Postea will preserve any
|
||||
// existing
|
||||
// Meta/Damage value. This is also the default value when you don't specify a target meta value.
|
||||
//
|
||||
// remap all other instances of compressed blocks to endstone
|
||||
BlockReplacementManager.addSimpleReplacement(
|
||||
"ExtraUtilities:cobblestone_compressed",
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
Blocks.end_stone,
|
||||
OreDictionary.WILDCARD_VALUE);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class ComplexBlockTransformer {
|
||||
|
||||
private static int woolId = -1;
|
||||
|
||||
public static void postLoad() {
|
||||
// This transformer replaces all grass blocks with even x-coordinates with orange wool.
|
||||
BlockReplacementManager.registerIDResolver("minecraft:wool", i -> woolId = i);
|
||||
BlockReplacementManager
|
||||
.addTransformationHandler("minecraft:grass", ComplexBlockTransformer::transformGrass);
|
||||
}
|
||||
|
||||
private static boolean transformGrass(BlockConversionInfo info) {
|
||||
// only affect blocks that are on even x coordinates
|
||||
if (info.x % 2 != 0) return false;
|
||||
// transform all these blocks into orange wool.
|
||||
info.blockID = woolId;
|
||||
info.metadata = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class ComplexItemTransformer {
|
||||
|
||||
private static int writableBookId = -1;
|
||||
private static int woolId = -1;
|
||||
|
||||
private static void postLoad() {
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:writable_book", i -> writableBookId = i);
|
||||
ItemStackReplacementManager.registerIDResolver("minecraft:wool", i -> woolId = i);
|
||||
ItemStackReplacementManager
|
||||
.addTransformationHandler("minecraft:written_book", ComplexItemTransformer::unsignBook);
|
||||
}
|
||||
|
||||
/**
|
||||
* This transformer transforms a written book into either an unsigned book or orange wool depending on it's
|
||||
* title.
|
||||
*
|
||||
* @param name The name of the item being transformed, even if that item doesn't exist anymore.
|
||||
* @param nbt The NBT data of the stack item being transformed.
|
||||
* @return True if we've actually transformed the item.
|
||||
*/
|
||||
private static boolean unsignBook(String name, NBTTagCompound nbt) {
|
||||
// check if the book has a title.
|
||||
if (!nbt.hasKey("tag", 10)) return false;
|
||||
NBTTagCompound tag = nbt.getCompoundTag("tag");
|
||||
if (!tag.hasKey("title", 8)) return false;
|
||||
String title = tag.getString("title");
|
||||
if (title.equalsIgnoreCase("unsign me")) {
|
||||
// unsign the book
|
||||
IDExtenderCompat.setItemStackID(nbt, writableBookId);
|
||||
nbt.setShort("Damage", (short) 0);
|
||||
nbt.setByte("Count", (byte) 1);
|
||||
// clear the signed book nbt
|
||||
tag.removeTag("title");
|
||||
tag.removeTag("author");
|
||||
return true;
|
||||
} else if (title.equalsIgnoreCase("orange wool")) {
|
||||
// Transform it into orange wool.
|
||||
IDExtenderCompat.setItemStackID(nbt, woolId);
|
||||
nbt.setShort("Damage", (short) 1);
|
||||
nbt.setByte("Count", (byte) 1);
|
||||
// removes the NBT tag from the stack.
|
||||
nbt.removeTag("tag");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class FMLReMappingExample {
|
||||
|
||||
// You have to make your own block class since the block constructor is protected.
|
||||
public static class MyBlock extends Block {
|
||||
|
||||
protected MyBlock() {
|
||||
super(Material.ground);
|
||||
}
|
||||
}
|
||||
|
||||
public static void postLoad() {
|
||||
// this only works when the thing you're replacing with has never been registered to a world's
|
||||
// id map. If you try to run this with a block that already exists (eg good old dirt), FML error out and
|
||||
// you that your world is corrupted beyond repair, and advise you to undo what ever you just did.
|
||||
//
|
||||
// Postea Simple Transformers don't have that issue and are more lag friendly in the long term. Consider
|
||||
// using them when ever possible, instead of FML mappings.
|
||||
Item item = new Item();
|
||||
GameRegistry.registerItem(item, "my_item");
|
||||
ItemStackReplacementManager.replaceMissingMapping("IC2:itemFertilizer", item);
|
||||
|
||||
Block block = new MyBlock();
|
||||
GameRegistry.registerBlock(block, "my_block");
|
||||
BlockReplacementManager.replaceMissingMapping("IC2:blockCrop", block);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class FMLIgnoreMissingMappingExample {
|
||||
|
||||
public static void postLoad() {
|
||||
// It doesn't really matter which one you use, they both do the same thing under the hood.
|
||||
ItemStackReplacementManager.ignoreMissingMapping("IC2:itemFertilizer");
|
||||
BlockReplacementManager.ignoreMissingMapping("IC2:blockCrop");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
package com.gtnewhorizons.postea;
|
||||
|
||||
import static com.gtnewhorizons.postea.api.BlockReplacementManager.blockReplacementMap;
|
||||
import static com.gtnewhorizons.postea.api.BlockReplacementManager.posteaMarkedIDs;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
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;
|
||||
|
||||
import cpw.mods.fml.common.Mod;
|
||||
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.FMLPostInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
|
||||
|
||||
@Mod(
|
||||
modid = Postea.MODID,
|
||||
@@ -26,20 +31,30 @@ public class Postea {
|
||||
public void preInit(FMLPreInitializationEvent event) {}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void onIdMappingsChanged(FMLModIdMappingEvent event) {
|
||||
posteaMarkedIDs.clear();
|
||||
for (String name : blockReplacementMap.keySet()) {
|
||||
Block block = Block.getBlockFromName(name);
|
||||
if (block != null) {
|
||||
int id = Block.getIdFromBlock(block);
|
||||
posteaMarkedIDs.add(id);
|
||||
}
|
||||
}
|
||||
public void postsLoad(FMLPostInitializationEvent event) {
|
||||
// TestUIEMigrator.postLoad();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void onServerStopping(FMLServerStoppingEvent event) {
|
||||
posteaMarkedIDs.clear();
|
||||
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
|
||||
public void onMissingMapping(FMLMissingMappingsEvent event) {
|
||||
MissingMappingHandler.onMissingMapping(event);
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void onLoadCompleted(FMLLoadCompleteEvent event) {
|
||||
SimpleTransformationRegistry.onLoadCompleted();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,41 +1,395 @@
|
||||
package com.gtnewhorizons.postea.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
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;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
public class BlockReplacementManager {
|
||||
|
||||
public static final Map<String, BiFunction<BlockConversionInfo, World, BlockConversionInfo>> blockReplacementMap = new HashMap<>();
|
||||
public static final IntOpenHashSet posteaMarkedIDs = new IntOpenHashSet();
|
||||
// region custom transformer registration
|
||||
|
||||
/**
|
||||
* Adds a custom transformer for a given id.
|
||||
*
|
||||
* @apiNote When the transformer function passed to this function returns a null, value Postea will assume that
|
||||
* the transformer has failed to identify and run the handler on any other handler function registered
|
||||
* to the given ID.
|
||||
* <br>
|
||||
* If you need to perform a simple transformations consider using the following methods instead:
|
||||
* <ul>
|
||||
* <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 #addSimpleReplacement(String, int, Block, int)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @implNote For performance reasons, Postea assumes that the BlockConversionInfo that was passed to the handler
|
||||
* hasn't been modified if your handler return false. This isn't enforced though since returning false
|
||||
* after doing some modifications has a couple niche uses. eg: having separate handlers for different
|
||||
* migrations of a single thing that can be sunset at different times down the line.
|
||||
*
|
||||
* @param originalId The id of the block to transform.
|
||||
* @param transformer The transformer to apply.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addBlockReplacement(String blockNameIn,
|
||||
BiFunction<BlockConversionInfo, World, BlockConversionInfo> transformer) {
|
||||
blockReplacementMap.put(blockNameIn, transformer);
|
||||
public static void addTransformationHandler(String originalId, Function<BlockConversionInfo, Boolean> transformer) {
|
||||
if (originalId == null) throw new IllegalArgumentException("original id is null");
|
||||
if (transformer == null) throw new IllegalArgumentException("transformer is null");
|
||||
TransformerRegistry.addBlockTransformer(originalId, transformer);
|
||||
}
|
||||
|
||||
public static BlockConversionInfo getBlockReplacement(BlockConversionInfo blockConversionInfo, World world) {
|
||||
// endregion custom transformer registration
|
||||
|
||||
BiFunction<BlockConversionInfo, World, BlockConversionInfo> transformer = blockReplacementMap
|
||||
.getOrDefault(blockConversionInfo.blockName, null);
|
||||
// region id resolving
|
||||
|
||||
if (transformer == null) {
|
||||
return null;
|
||||
} else {
|
||||
return transformer.apply(blockConversionInfo, world);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
// We need this to save reprocessing blocks.
|
||||
public static boolean blockNotConvertible(int blockID) {
|
||||
return !posteaMarkedIDs.contains(blockID);
|
||||
// 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.<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 #addSimpleReplacement(String, Block)} with both meta parameters set to
|
||||
* OreDictionary.WILDCARD_VALUE.</u></b>
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param block The block to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void replaceMissingMapping(String originalId, Block block) {
|
||||
if (originalId == null) throw new IllegalArgumentException("original id is null");
|
||||
if (block == null) throw new IllegalArgumentException("block is null");
|
||||
MissingMappingHandler.addBlockMapping(originalId, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses any missing mapping warning for the given id.
|
||||
*
|
||||
* @param originalId The id of the block or item to suppress the warning for.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void ignoreMissingMapping(@Nonnull String originalId) {
|
||||
MissingMappingHandler.addIgnore(originalId);
|
||||
}
|
||||
|
||||
// endregion missing mapping replacements
|
||||
|
||||
// region simple replacements
|
||||
|
||||
// block -> block
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block to another block, whilst maintaining its
|
||||
* meta value. This also adds a matching item stack replacement.
|
||||
*
|
||||
* @apiNote Use this if you are replacing a block with something that was already registered to a world's id
|
||||
* mappings,
|
||||
* (e.g.: Transforming a removed block into dirt)
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param block The block to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block) {
|
||||
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, OreDictionary.WILDCARD_VALUE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block to another block, whilst maintaining its
|
||||
* meta value.
|
||||
*
|
||||
* @apiNote Use this if you are replacing an item with something that was already registered to a world's id
|
||||
* mappings,
|
||||
* (e.g.: Transforming a removed block into dirt)
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param block The block to remap to.
|
||||
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block, boolean skipStackRemap) {
|
||||
addSimpleReplacement(
|
||||
originalId,
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
block,
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
skipStackRemap);
|
||||
}
|
||||
|
||||
// block+meta -> block
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block and damage/meta value to another block,
|
||||
* whilst maintaining its meta value. This also adds a matching item stack replacement.
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param originalMeta The meta of the block to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param block The block to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block) {
|
||||
addSimpleReplacement(originalId, originalMeta, block, OreDictionary.WILDCARD_VALUE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block and damage/meta value to another block,,
|
||||
* whilst maintaining its meta value.
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param originalMeta The meta of the block to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param block The block to remap to.
|
||||
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block,
|
||||
boolean skipStackRemap) {
|
||||
addSimpleReplacement(originalId, originalMeta, block, OreDictionary.WILDCARD_VALUE, skipStackRemap);
|
||||
}
|
||||
|
||||
// block -> block+meta
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block to another block with a given damage/meta
|
||||
* value. This also adds a matching item stack replacement.
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param block The block to remap to.
|
||||
* @param newMeta The meta of the block to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block, int newMeta) {
|
||||
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, newMeta, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block to another block with a given damage/meta
|
||||
* value.
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param block The block to remap to.
|
||||
* @param newMeta The meta of the block to remap to. OreDictionary.WILDCARD_VALUE maintains the existing
|
||||
* value.
|
||||
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Block block, int newMeta,
|
||||
boolean skipStackRemap) {
|
||||
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, block, newMeta, skipStackRemap);
|
||||
}
|
||||
|
||||
// block+meta -> block+meta
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block and damage/meta value to another block
|
||||
* with a given damage/meta value. This also adds a matching item stack replacement.
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param originalMeta The meta of the block to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param block The block to remap to.
|
||||
* @param newMeta The meta of the block to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Block block,
|
||||
int newMeta) {
|
||||
addSimpleReplacement(originalId, originalMeta, block, newMeta, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks and block instances of a given block and damage/meta value to another block
|
||||
* with a given damage/meta value.
|
||||
*
|
||||
* @param originalId The id of the block to remap.
|
||||
* @param originalMeta The meta of the block to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param block The block to remap to.
|
||||
* @param newMeta The meta of the block to remap to. OreDictionary.WILDCARD_VALUE maintains the existing
|
||||
* value.
|
||||
* @param skipStackRemap Set to true to skip auto-adding an item stack remapper.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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,24 +1,376 @@
|
||||
package com.gtnewhorizons.postea.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
public class ItemStackReplacementManager {
|
||||
import com.gtnewhorizons.postea.utility.IDRegistry;
|
||||
import com.gtnewhorizons.postea.utility.MissingMappingHandler;
|
||||
import com.gtnewhorizons.postea.utility.SimpleTransformationRegistry;
|
||||
import com.gtnewhorizons.postea.utility.TransformerRegistry;
|
||||
|
||||
private static final Map<String, Function<NBTTagCompound, NBTTagCompound>> replacementMap = new HashMap<>();
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @apiNote When the transformer function passed to this function returns a null, value Postea will assume that
|
||||
* the transformer has failed to identify and run the handler on any other handler function registered
|
||||
* to the given ID.
|
||||
* <p>
|
||||
* If you need to perform a simple transformations consider using the following methods instead:
|
||||
* <ul>
|
||||
* <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
|
||||
* modified if you return false. This isn't enforced though since returning false after doing some
|
||||
* modifications has a couple niche uses. eg: having separate handlers for different migrations of a
|
||||
* single thing that can be sunset at different times down the line.
|
||||
*
|
||||
* @param originalId The id of the item to transform.
|
||||
* @param transformer The transformer to apply.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addItemReplacement(String itemName, Function<NBTTagCompound, NBTTagCompound> transformer) {
|
||||
replacementMap.put(itemName, transformer);
|
||||
public static void addTransformationHandler(String originalId,
|
||||
BiFunction<String, NBTTagCompound, Boolean> transformer) {
|
||||
if (originalId == null) throw new IllegalArgumentException("original id is null");
|
||||
if (transformer == null) throw new IllegalArgumentException("transformer is null");
|
||||
TransformerRegistry.addStackTransformer(originalId, transformer);
|
||||
}
|
||||
|
||||
// endregion custom transformer registration
|
||||
|
||||
// region id resolving
|
||||
|
||||
/**
|
||||
* 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 Function<NBTTagCompound, NBTTagCompound> getItemReplacement(String itemNameInternal) {
|
||||
return replacementMap.get(itemNameInternal);
|
||||
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 #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.
|
||||
* @param item The item to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void replaceMissingMapping(String originalId, Item item) {
|
||||
if (originalId == null) throw new IllegalArgumentException("original id is null");
|
||||
if (item == null) throw new IllegalArgumentException("item is null");
|
||||
MissingMappingHandler.addItemMapping(originalId, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppresses any missing mapping warning for the given id.
|
||||
*
|
||||
* @param originalId The id of the block or item to suppress the warning for.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void ignoreMissingMapping(@Nonnull String originalId) {
|
||||
MissingMappingHandler.addIgnore(originalId);
|
||||
}
|
||||
|
||||
// endregion missing mapping replacements
|
||||
|
||||
// region simple replacements
|
||||
|
||||
// item -> item
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap stacks of a given item to another item, whilst maintaining its damage/meta value. This
|
||||
* also adds a matching block replacement if this item happens to be an ItemBlock.
|
||||
*
|
||||
* @apiNote Use this if you are replacing an item with something that was already registered to a world's id
|
||||
* mappings,
|
||||
* (e.g.: Transforming a removed item into a stick)
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param item The item to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item) {
|
||||
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, OreDictionary.WILDCARD_VALUE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap stacks of a given item to another item, whilst maintaining its damage/meta value.
|
||||
*
|
||||
* @apiNote Use this if you are replacing an item with something that was already registered to a world's id
|
||||
* mappings,
|
||||
* (e.g.: Transforming a removed item into a stick)
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param item The item to remap to.
|
||||
* @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 addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item, boolean skipBlockRemap) {
|
||||
addSimpleReplacement(
|
||||
originalId,
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
item,
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
skipBlockRemap);
|
||||
}
|
||||
|
||||
// item -> item+meta
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap stacks of a given item to another item with a given damage/meta value. This also adds a
|
||||
* matching block replacement if this item happens to be an ItemBlock.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param item The item to remap to.
|
||||
* @param newMeta The meta of the item to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item, int newMeta) {
|
||||
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, newMeta, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap stacks of a given item to another item with a given damage/meta value.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param item The item to remap to.
|
||||
* @param newMeta The meta of the item to remap to. OreDictionary.WILDCARD_VALUE maintains the existing
|
||||
* value.
|
||||
* @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 addSimpleReplacement(@Nonnull String originalId, @Nonnull Item item, int newMeta,
|
||||
boolean skipBlockRemap) {
|
||||
addSimpleReplacement(originalId, OreDictionary.WILDCARD_VALUE, item, newMeta, skipBlockRemap);
|
||||
}
|
||||
|
||||
// item -> stack
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap stacks of a given item to another item with a given damage/meta value. This also adds a
|
||||
* matching block replacement if this item happens to be an ItemBlock.
|
||||
* <br>
|
||||
* DOES NOT COPY NBT FROM STACK.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param stack The item to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, @Nonnull ItemStack stack) {
|
||||
addSimpleReplacement(
|
||||
originalId,
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
stack.getItem(),
|
||||
Items.feather.getDamage(stack),
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap stacks of a given item to another item with a given damage/meta value.
|
||||
* <br>
|
||||
* DOES NOT COPY NBT FROM STACK.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param stack A stack of the item to replace to.
|
||||
* @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 addSimpleReplacement(@Nonnull String originalId, @Nonnull ItemStack stack,
|
||||
boolean skipBlockRemap) {
|
||||
addSimpleReplacement(
|
||||
originalId,
|
||||
OreDictionary.WILDCARD_VALUE,
|
||||
stack.getItem(),
|
||||
Items.feather.getDamage(stack),
|
||||
skipBlockRemap);
|
||||
}
|
||||
|
||||
// item+meta -> item
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks of a given item and damage/meta value to another item, whilst maintaining its
|
||||
* damage/meta value. This also adds a matching block replacement if this item happens to be an ItemBlock.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param originalMeta The meta of the item to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param item The item to remap to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item) {
|
||||
addSimpleReplacement(originalId, originalMeta, item, OreDictionary.WILDCARD_VALUE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks of a given item and damage/meta value to another item, whilst maintaining its
|
||||
* damage/meta value.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param originalMeta The meta of the item to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param item The item to remap to.
|
||||
* @param skipBlockRemap Set to true to skip auto-adding a item remapper if the .
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item,
|
||||
boolean skipBlockRemap) {
|
||||
addSimpleReplacement(originalId, originalMeta, item, OreDictionary.WILDCARD_VALUE, skipBlockRemap);
|
||||
}
|
||||
|
||||
// item+meta -> stack
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks of a given item and damage/meta value to another item with a
|
||||
* given damage/meta value. This also adds a matching block replacement if this item happens to be an ItemBlock.
|
||||
* <br>
|
||||
* DOES NOT COPY NBT FROM STACK.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param originalMeta The meta of the item to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param stack A stack of the item to replace to.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull ItemStack stack) {
|
||||
addSimpleReplacement(originalId, originalMeta, stack, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks of a given item and damage/meta value to another item with a
|
||||
* given damage/meta value.
|
||||
* <br>
|
||||
* DOES NOT COPY NBT FROM STACK.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param originalMeta The meta of the item to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param stack A stack of the item to replace to.
|
||||
* @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 addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull ItemStack stack,
|
||||
boolean skipBlockRemap) {
|
||||
addSimpleReplacement(originalId, originalMeta, stack.getItem(), Items.feather.getDamage(stack), skipBlockRemap);
|
||||
}
|
||||
|
||||
// item+meta -> item+meta
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks of a given item and damage/meta value to another item with a
|
||||
* given damage/meta value. This also adds a matching block replacement if this item happens to be an ItemBlock.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param originalMeta The meta of the item to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param item The item to remap to.
|
||||
* @param newMeta The meta of the item to remap to. OreDictionary.WILDCARD_VALUE maintains the existing value.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void addSimpleReplacement(@Nonnull String originalId, int originalMeta, @Nonnull Item item,
|
||||
int newMeta) {
|
||||
addSimpleReplacement(originalId, originalMeta, item, newMeta, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping to remap all stacks of a given item and damage/meta value to another item with a
|
||||
* given damage/meta value.
|
||||
*
|
||||
* @implNote All transformations registered using this handler are executed in constant time regardless of how many
|
||||
* transformations are registered to a single id.
|
||||
*
|
||||
* @param originalId The id of the item to remap.
|
||||
* @param originalMeta The meta of the item to remap. OreDictionary.WILDCARD_VALUE acts as a wildcard.
|
||||
* @param item The item to remap to.
|
||||
* @param newMeta The meta of the item to remap to. OreDictionary.WILDCARD_VALUE maintains the existing
|
||||
* value.
|
||||
* @param skipBlockRemap Set to true to skip auto-adding a item remapper if the .
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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,45 +1,70 @@
|
||||
package com.gtnewhorizons.postea.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
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.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 HashMap<String, TriFunction<NBTTagCompound, World, Chunk, BlockInfo>> tileEntityToNormalBlockTransformer = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @deprecated Superseded by {@link #tileEntityTransformer(String, TriFunction)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void tileEntityTransformer(String tileEntityId,
|
||||
BiFunction<NBTTagCompound, World, BlockInfo> transformerFunction) {
|
||||
tileEntityToNormalBlockTransformer
|
||||
.put(tileEntityId, (tag, world, chunk) -> transformerFunction.apply(tag, world));
|
||||
}
|
||||
public abstract class TileEntityReplacementManager {
|
||||
|
||||
/**
|
||||
* 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 TriFunction<NBTTagCompound, World, Chunk, BlockInfo> getTileEntityToNormalBlockTransformerFunction(
|
||||
String tileEntityId) {
|
||||
return tileEntityToNormalBlockTransformer.getOrDefault(tileEntityId, null);
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
package com.gtnewhorizons.postea.compat;
|
||||
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.world.chunk.NibbleArray;
|
||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
import com.falsepattern.endlessids.mixin.helpers.SubChunkBlockHook;
|
||||
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
|
||||
|
||||
import codechicken.nei.api.API;
|
||||
|
||||
public class Compat {
|
||||
|
||||
private static Boolean chunkapi = null;
|
||||
private static Boolean endlessids = null;
|
||||
private static Boolean neid = null;
|
||||
private static Boolean nei = null;
|
||||
|
||||
public static boolean chunkapiPresent() {
|
||||
if (chunkapi == null) {
|
||||
@@ -47,6 +53,17 @@ public class Compat {
|
||||
return neid;
|
||||
}
|
||||
|
||||
public static boolean neiPresent() {
|
||||
if (nei == null) {
|
||||
boolean present = false;
|
||||
try {
|
||||
present = Launch.classLoader.getClassBytes("codechicken.nei.api.API") != null;
|
||||
} catch (Throwable ignored) {}
|
||||
nei = present;
|
||||
}
|
||||
return nei;
|
||||
}
|
||||
|
||||
public static SubChunkAccess getSubChunkAccess(ExtendedBlockStorage subChunk) {
|
||||
if (endlessidsPresent()) {
|
||||
return EndlessIDSCompat.getSubChunkAccess(subChunk);
|
||||
@@ -162,6 +179,18 @@ public class Compat {
|
||||
}
|
||||
}
|
||||
|
||||
public static void hideItemFromNEI(Item item) {
|
||||
if (!neiPresent()) return;
|
||||
NEICompat.hideItem(item);
|
||||
}
|
||||
|
||||
private static class NEICompat {
|
||||
|
||||
public static void hideItem(Item item) {
|
||||
API.hideItem(new ItemStack(item, 1, OreDictionary.WILDCARD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
private static int toIndex(int x, int y, int z) {
|
||||
return y << 8 | z << 4 | x;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.gtnewhorizons.postea.mixins.early;
|
||||
|
||||
import static com.gtnewhorizons.postea.utility.ItemFixerUtility.fixItemStack;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
@@ -10,6 +8,8 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.gtnewhorizons.postea.utility.TransformerRegistry;
|
||||
|
||||
@Mixin(ItemStack.class)
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class MixinItemStack {
|
||||
@@ -17,7 +17,7 @@ public abstract class MixinItemStack {
|
||||
@Inject(method = "readFromNBT(Lnet/minecraft/nbt/NBTTagCompound;)V", at = @At("HEAD"))
|
||||
@SuppressWarnings("unused")
|
||||
private void onReadFromNBTReturn(NBTTagCompound tag, CallbackInfo ci) {
|
||||
fixItemStack(tag);
|
||||
TransformerRegistry.transformItem(tag);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,47 @@
|
||||
package com.gtnewhorizons.postea.utility;
|
||||
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public final class BlockConversionInfo {
|
||||
|
||||
public int x;
|
||||
public int y;
|
||||
public int z;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public World world;
|
||||
public NBTTagCompound tileEntityNBT;
|
||||
/**
|
||||
* 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 String blockName;
|
||||
public BlockConversionInfo(String blockName, int blockID, int metadata, int x, int y, int z, World world) {
|
||||
this.blockName = blockName;
|
||||
this.blockID = blockID;
|
||||
this.metadata = metadata;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.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,38 +1,83 @@
|
||||
package com.gtnewhorizons.postea.utility;
|
||||
|
||||
import static com.gtnewhorizons.postea.api.BlockReplacementManager.blockNotConvertible;
|
||||
import static com.gtnewhorizons.postea.utility.PosteaUtilities.getModListHash;
|
||||
import static com.gtnewhorizons.postea.utility.TransformerRegistry.getBlockReplacement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
|
||||
|
||||
import com.gtnewhorizons.postea.api.BlockReplacementManager;
|
||||
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 cpw.mods.fml.common.registry.GameRegistry;
|
||||
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;
|
||||
private static final HashMap<Block, String> loadedBlocks = new HashMap<>();
|
||||
|
||||
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;
|
||||
@@ -49,29 +94,14 @@ public class ChunkFixerUtility {
|
||||
|
||||
// Skip air.
|
||||
if (blockId == AIR_ID) continue;
|
||||
// If this block has no registered Postea conversion, skip it.
|
||||
if (blockNotConvertible(blockId)) continue;
|
||||
|
||||
// Cache block names to improve performance, as findUniqueIdentifierFor is expensive.
|
||||
Block block = Block.getBlockById(blockId);
|
||||
String blockName = loadedBlocks.computeIfAbsent(
|
||||
block,
|
||||
b -> GameRegistry.findUniqueIdentifierFor(b)
|
||||
.toString());
|
||||
|
||||
BlockConversionInfo blockConversionInfo = new BlockConversionInfo();
|
||||
blockConversionInfo.blockName = blockName;
|
||||
blockConversionInfo.blockID = blockId;
|
||||
blockConversionInfo.metadata = (byte) metadata; // Updated
|
||||
|
||||
blockConversionInfo.world = world;
|
||||
|
||||
blockConversionInfo.x = x + chunkXPos + 1;
|
||||
blockConversionInfo.y = y + sectionY;
|
||||
blockConversionInfo.z = z + chunkZPos + 1;
|
||||
|
||||
BlockConversionInfo output = BlockReplacementManager
|
||||
.getBlockReplacement(blockConversionInfo, world);
|
||||
BlockConversionInfo output = getBlockReplacement(
|
||||
blockId,
|
||||
(byte) metadata,
|
||||
world,
|
||||
x + chunkXPos + 1,
|
||||
y + sectionY,
|
||||
z + chunkZPos + 1);
|
||||
|
||||
if (output != null) {
|
||||
access.setBlockId(x, y, z, output.blockID);
|
||||
@@ -82,79 +112,37 @@ 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
|
||||
TriFunction<NBTTagCompound, World, Chunk, BlockInfo> transformationFunction = TileEntityReplacementManager
|
||||
.getTileEntityToNormalBlockTransformerFunction(tileEntityId);
|
||||
for (TriFunction<NBTTagCompound, World, Chunk, BlockInfo> transformationFunction : TransformerRegistry
|
||||
.getTileEntityToNormalBlockTransformerFunction(tileEntityId)) {
|
||||
|
||||
if (transformationFunction != null) {
|
||||
int x = tileEntity.getInteger("x");
|
||||
int y = tileEntity.getInteger("y");
|
||||
int z = tileEntity.getInteger("z");
|
||||
|
||||
BlockInfo blockInfo = transformationFunction.apply(tileEntity, world, chunk);
|
||||
if (blockInfo == null) {
|
||||
// Do nothing.
|
||||
tileEntitiesCopy.appendTag(tileEntity);
|
||||
continue;
|
||||
if (blockInfo == null) continue;
|
||||
|
||||
NBTTagCompound newTag;
|
||||
if (blockInfo.tileTransformer != null
|
||||
&& (newTag = blockInfo.tileTransformer.apply(tileEntity)) != null) {
|
||||
((IMixinNBTTagList) tileEntities).Postea$replaceCompoundTagAt(i, newTag);
|
||||
} else {
|
||||
tileEntities.removeTag(i--);
|
||||
}
|
||||
|
||||
if (blockInfo.tileTransformer != null) {
|
||||
tileEntitiesCopy.appendTag(blockInfo.tileTransformer.apply(tileEntity));
|
||||
} // Otherwise they are removed, therefore not appended.
|
||||
|
||||
conversionInfo.add(new ConversionInfo(x, y, z, blockInfo));
|
||||
} else {
|
||||
// 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,34 +0,0 @@
|
||||
package com.gtnewhorizons.postea.utility;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import com.gtnewhorizons.postea.api.IDExtenderCompat;
|
||||
import com.gtnewhorizons.postea.api.ItemStackReplacementManager;
|
||||
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
|
||||
public abstract class ItemFixerUtility {
|
||||
|
||||
public static void fixItemStack(NBTTagCompound tag) {
|
||||
|
||||
if (tag.hasNoTags()) return;
|
||||
|
||||
if (tag.hasKey("id")) {
|
||||
int id = IDExtenderCompat.getItemStackID(tag);
|
||||
Item item = Item.getItemById(id);
|
||||
if (item == null) return;
|
||||
String itemNameInternal = GameRegistry.findUniqueIdentifierFor(item).modId + ":"
|
||||
+ GameRegistry.findUniqueIdentifierFor(item).name;
|
||||
|
||||
Function<NBTTagCompound, NBTTagCompound> transformer = ItemStackReplacementManager
|
||||
.getItemReplacement(itemNameInternal);
|
||||
|
||||
if (transformer != null) {
|
||||
transformer.apply(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.gtnewhorizons.postea.utility;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.Item;
|
||||
|
||||
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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.ignore();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mapping.type == GameRegistry.Type.BLOCK) {
|
||||
Block remappedBlock = MISSING_BLOCK_MAPPINGS.get(mapping.name);
|
||||
if (remappedBlock != null) {
|
||||
mapping.remap(remappedBlock);
|
||||
continue;
|
||||
}
|
||||
} else if (mapping.type == GameRegistry.Type.ITEM) {
|
||||
Block remappedBlock = MISSING_BLOCK_MAPPINGS.get(mapping.name);
|
||||
if (remappedBlock != null) {
|
||||
mapping.remap(Item.getItemFromBlock(remappedBlock));
|
||||
continue;
|
||||
}
|
||||
Item remappedItem = MISSING_ITEM_MAPPINGS.get(mapping.name);
|
||||
if (remappedItem != null) {
|
||||
mapping.remap(MISSING_ITEM_MAPPINGS.get(mapping.name));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion FML Life Cycle handlers
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gtnewhorizons.postea.utility;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
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 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 target, short targetMeta) {
|
||||
if (originalMeta == -1) originalMeta = OreDictionary.WILDCARD_VALUE;
|
||||
this.registry.computeIfAbsent(originalId, k -> new Int2ObjectOpenHashMap<>())
|
||||
.put(originalMeta, new Value<>(target, targetMeta));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 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 subMap.get(OreDictionary.WILDCARD_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to update runtime IDs during the MappingUpdate event.
|
||||
*
|
||||
* @return The set of all registered item ids
|
||||
*/
|
||||
public Set<Map.Entry<String, Map<Integer, Value<T>>>> entrySet() {
|
||||
return this.registry.entrySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
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;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraftforge.oredict.OreDictionary;
|
||||
|
||||
import com.gtnewhorizons.postea.api.IDExtenderCompat;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* having to migrate almost every block and item from the original mod.
|
||||
* <hr>
|
||||
* If a mod uses metas to differentiate items (eg: GT5u) that will end up being migrated to separate items, this would
|
||||
* usually result in potentially multiple handlers per item if they are destined to be split into separate mods,
|
||||
* which increases the execution time linearly. The simple transformations uses a better algorithm for these
|
||||
* which is executed in constant time regardless of how many transformations have been assigned to a given id. (mostly
|
||||
* at the cost of higher memory usage due to caching)
|
||||
*/
|
||||
public class SimpleTransformationRegistry {
|
||||
|
||||
/**
|
||||
* Stores simple block transformations (eg: if old id is # with meta # then turn into item with id # with meta #.)
|
||||
*/
|
||||
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 #.)
|
||||
*/
|
||||
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. 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, 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, SimpleTransformationMap.Value<Item>>> kv : SIMPLE_ITEM_TRANSFORMATION_MAP
|
||||
.entrySet()) {
|
||||
TransformerRegistry.addStackTransformer(kv.getKey(), new SimpleItemTransformationHandler(kv.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-caches updated block and item numeric IDs for any simple transformation to speed up the handlers.
|
||||
*/
|
||||
public static void onIdMappingsChanged() {
|
||||
for (Map.Entry<String, Map<Integer, SimpleTransformationMap.Value<Block>>> kv : SIMPLE_BLOCK_TRANSFORMATION_MAP
|
||||
.entrySet()) {
|
||||
for (SimpleTransformationMap.Value<Block> mapping : kv.getValue()
|
||||
.values()) {
|
||||
mapping.targetRuntimeId = Block.getIdFromBlock(mapping.target);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Map<Integer, SimpleTransformationMap.Value<Item>>> kv : SIMPLE_ITEM_TRANSFORMATION_MAP
|
||||
.entrySet()) {
|
||||
for (SimpleTransformationMap.Value<Item> mapping : kv.getValue()
|
||||
.values()) {
|
||||
mapping.targetRuntimeId = Item.getIdFromItem(mapping.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion FML Life Cycle handlers
|
||||
|
||||
// region block transformations
|
||||
/**
|
||||
* Registers a simple Block transformation for a simple 1:1 transformation:
|
||||
* <ul>
|
||||
* <li>block -> block (with same meta)</li>
|
||||
* <li>block -> block with specific meta</li>
|
||||
* <li>block with meta -> block (with same meta)</li>
|
||||
* <li>block with meta -> block with specific meta</li>
|
||||
* </ul>
|
||||
*
|
||||
* @implNote With these simple transformers, it doesn't matter how many transformations are registered to a single
|
||||
* id, only a single handler will be registered per id during the FMLLoadCompleted event. Replacements
|
||||
* are by using hash-table lookups so the execution of the transformation should be constant-time
|
||||
* regardless of how many handlers were registered to a single id.
|
||||
*
|
||||
* @param originalId The original namespaced id of the block to replace.
|
||||
* @param originalMeta The meta value of the block to replace. OreDictionary.WILDCARD_VALUE acts as a wildcard
|
||||
* and fallback if there isn't simple transformation registered for a given meta.
|
||||
* @param newBlock What block to replace with.
|
||||
* @param newMeta The meta value of the transformed block. OreDictionary.WILDCARD_VALUE preserves the
|
||||
* existing value.
|
||||
* @param skipStackRemap Set to true to skip registering an identical simple item transformation.
|
||||
*/
|
||||
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, newBlock, (short) newMeta);
|
||||
if (!skipStackRemap) {
|
||||
Item item = Item.getItemFromBlock(newBlock);
|
||||
if (item != null) {
|
||||
SIMPLE_ITEM_TRANSFORMATION_MAP.put(originalId, originalMeta, item, (short) newMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler for the simple block transformation.
|
||||
*
|
||||
* @implNote This implementation has constant-time runtime.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
// endregion block transformations
|
||||
|
||||
// region item transformations
|
||||
/**
|
||||
* Registers a simple Block transformation for a simple 1:1 transformation:
|
||||
* <ul>
|
||||
* <li>item -> item (with same meta)</li>
|
||||
* <li>item -> item with specific meta</li>
|
||||
* <li>item with meta/damage -> item (with same meta/damage)</li>
|
||||
* <li>item with meta/damage -> item with specific meta/damage</li>
|
||||
* </ul>
|
||||
*
|
||||
* @implNote With these simple transformers, it doesn't matter how many transformations are registered to a single
|
||||
* id, only a single handler will be registered per id during the FMLLoadCompleted event. Replacements
|
||||
* are by using hash-table lookups so the execution of the transformation should be constant-time
|
||||
* regardless of how many handlers were registered to a single id.
|
||||
*
|
||||
* @param originalId The original namespaced id of the item to replace.
|
||||
* @param originalMeta The meta/damage value of the item to replace. OreDictionary.WILDCARD_VALUE acts as a
|
||||
* wildcard
|
||||
* and fallback if there isn't simple transformation registered for a given meta.
|
||||
* @param newItem What item to replace with.
|
||||
* @param newMeta The meta/damage value of the transformed item. OreDictionary.WILDCARD_VALUE preserves the
|
||||
* existing
|
||||
* value.
|
||||
* @param skipBlockRemap Set to true to skip registering an identical block transformation if the item is an
|
||||
* ItemBlock.
|
||||
*/
|
||||
public static void addSimpleTransformer(String originalId, int originalMeta, Item newItem, int newMeta,
|
||||
boolean skipBlockRemap) {
|
||||
if (newMeta == -1) newMeta = OreDictionary.WILDCARD_VALUE;
|
||||
if (!skipBlockRemap && newItem instanceof ItemBlock ib) {
|
||||
SIMPLE_BLOCK_TRANSFORMATION_MAP.put(originalId, originalMeta, ib.field_150939_a, (short) newMeta);
|
||||
}
|
||||
SIMPLE_ITEM_TRANSFORMATION_MAP.put(originalId, originalMeta, newItem, (short) newMeta);
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler for the simple item transformation.
|
||||
*
|
||||
* @implNote This implementation has constant-time runtime.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
// endregion item transformations
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* A registry for all transformer functions
|
||||
*/
|
||||
public class TransformerRegistry {
|
||||
|
||||
private static final LinkedListMultimap<String, BiFunction<String, NBTTagCompound, Boolean>> ITEM_REPLACEMENT_MAP = LinkedListMultimap
|
||||
.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.
|
||||
* Generated during the mapping update event whilst entering/loading a world.
|
||||
*/
|
||||
private static final Int2ObjectOpenHashMap<Pair<String, List<BiFunction<String, NBTTagCompound, Boolean>>>> RUNTIME_ITEM_REPLACEMENT_MAP = new Int2ObjectOpenHashMap<>();
|
||||
/**
|
||||
* Map of blockIO -> originalName+transformationHandler.
|
||||
* Pivoting on the id is faster than pivoting on the strings so this should help with perf a bit.
|
||||
* 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
|
||||
/**
|
||||
* 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 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.
|
||||
*
|
||||
* @implNote It doesn't matter if the id doesn't belong to an existing block, a dummy block will be created when
|
||||
* Postea detects that the id isn't associated to a block during the FMLLoadCompleted event. Dependent
|
||||
* mods don't need to worry about this as the block name in the BlockConversionInfo is automatically
|
||||
* updated to the original id when attempting to transform a dummy block. When Postea runs a
|
||||
* transformation on a dummy block and no handler return true, Postea will automatically convert it to
|
||||
* air.
|
||||
*
|
||||
* @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(@Nonnull String originalId,
|
||||
@Nonnull Function<BlockConversionInfo, Boolean> transformer) {
|
||||
BLOCK_REPLACEMENT_MAP.put(originalId, transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 addStackTransformer(@Nonnull String originalId,
|
||||
@Nonnull BiFunction<String, NBTTagCompound, Boolean> transformer) {
|
||||
ITEM_REPLACEMENT_MAP.put(originalId, transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// region transformation handlers
|
||||
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);
|
||||
if (data == null) return null;
|
||||
BlockConversionInfo blockConversionInfo = new BlockConversionInfo(
|
||||
// transparently maps the id to the original id if it's a dummy id
|
||||
data.getKey(),
|
||||
blockId,
|
||||
metadata,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
world);
|
||||
for (Function<BlockConversionInfo, Boolean> transformer : data.getValue()) {
|
||||
if (transformer.apply(blockConversionInfo)) {
|
||||
return blockConversionInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void transformItem(NBTTagCompound tag) {
|
||||
// abort early if tag is bad.
|
||||
if (tag.hasNoTags() || !tag.hasKey("id")) return;
|
||||
// get handler
|
||||
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()) {
|
||||
// apply handlers
|
||||
for (BiFunction<String, NBTTagCompound, Boolean> transformer : data.getValue()) {
|
||||
if (transformer.apply(data.getKey(), tag)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<TriFunction<NBTTagCompound, World, Chunk, BlockInfo>> getTileEntityToNormalBlockTransformerFunction(
|
||||
String tileEntityId) {
|
||||
return TILE_ENTITY_REPLACEMENT_MAP.get(tileEntityId);
|
||||
}
|
||||
// endregion transformation handlers
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
"mixins": [
|
||||
"MixinAnvilChunkLoader",
|
||||
"MixinChunk",
|
||||
"MixinItemStack"
|
||||
"MixinItemStack",
|
||||
"MixinNBTTagList"
|
||||
],
|
||||
"client": [],
|
||||
"server": []
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
Reference in new issue
Block a user