Creating copycats - copycats-plus/copycats GitHub Wiki

Obsolete as of Copycats+ v2.0.0

This guide details my process of adding copycats to Create: Copycats+, and also serves as a checklist for myself since I always forget something in this complex process.

Creating the item model in Blockbench

This model is only used as the inventory icon of the copycat, but it is still highly recommended that you model it according to how the textures are actually cut in the dynamic copycat model. This will come in handy later when coding the dynamic model.

If your model has a horizontal facing, model it in the facing=south state.

If your model can be flipped vertically, model it in the half=bottom state.

Implementing Copycat___Block

This is the most difficult part of the whole process, but there are ways to cheat this if what you are implementing already has a vanilla counterpart. If your copycat is halfway between vanilla and custom, consider the following to decide which route you should take:

  • Should other mods treat your block the same way as the vanilla variants?
  • Are the vanilla variants free of special quirks that don't fit your copycat?
  • Can the extra features of your block be implemented by inheriting the vanilla block class and only overriding a few methods?

With Vanilla Counterpart (Trapdoor, Stairs, ...)

The whole idea here is to store a reference to an instance of the vanilla Block class, and forward all block events to that instance for processing. This works because Block classes are singletons that are only loosely related to the block that they are registered with. As long as your copycat has the same set of block state properties, the vanilla class can process your block just fine.

  1. Create your Wrapped___Block class that inherits from the target vanilla Block class. e.g. class WrappedTrapdoorBlock extends TrapDoorBlock

  2. Create your Copycat___Block class that inherits from WaterloggedCopycatWrappedBlock

  3. In your Copycat___Block:

    • Create a public static field to store your Wrapped___Block. We will put something in this field during registration.
    • Implement the required public Block getWrappedBlock() by returning the static field.
    • Implement the constructor, createBlockStateDefinition and copyState yourself to set up the block states.
    • Read through the vanilla block class and all its parent classes. For each block event it uses (except use and getStateForPlacement, which is handled automatically), override the corresponding event in your copycat block class and redirect the call to the wrapped instance.
  4. Implement the copycat-specific methods: isIgnoredConnectivitySide, canConnectTexturesToward, canFaceBeOccluded, shouldFaceAlwaysRender.

    isIgnoredConnectivitySide controls whether an adjacent block can appear connected to your copycat on the specified block face.

    canConnectTexturesToward controls whether your copycat can appear connected to an adjacent block as a whole.

Without Vanilla Counterpart (Bytes, Boards, ...)

You are on your own here. This is just a basic list of what's required, but you may need more.

  1. Create your Copycat___Block class that inherits from WaterloggedCopycatBlock

  2. In your Copycat___Block class:

  3. Implement the copycat-specific methods: isIgnoredConnectivitySide, canConnectTexturesToward, canFaceBeOccluded, shouldFaceAlwaysRender.

    isIgnoredConnectivitySide controls whether an adjacent block can appear connected to your copycat on the specified block face.

    canConnectTexturesToward controls whether your copycat can appear connected to an adjacent block as a whole.

Implementing Copycat___Model

Create's code is really confusing in this part, but the ISimpleCopycatModel interface provides some helper methods to greatly simplify this process. Copy this boilerplate code to get started (remember to rename the class):

import com.copycatsplus.copycats.content.copycat.SimpleCopycatModel;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.world.level.block.state.BlockState;

/* Remember to add this line! Your IDE won't add this automatically when you specify cullfaces later */
import static com.copycatsplus.copycats.content.copycat.ISimpleCopycatModel.MutableCullFace.*;

public class CopycatBlockModel extends SimpleCopycatModel {

    public CopycatBlockModel(BakedModel originalModel) {
        super(originalModel);
    }

    @Override
    protected void emitCopycatQuads(BlockState state, CopycatRenderContext context, BlockState material) {
        // model logic here
    }
}

Now to actually code the model, you need to understand the ISimpleCopycatModel#assemblePiece method. This method cuts a cuboid shape from the block model of the applied material and pastes it in your copycat model. The parameters are as follows:

assemblePiece(
     // just copy this
     context,

     // horizontally rotate the whole cut-and-paste operation by this number of degrees
     // - only multiples of 90 are supported
     // - this allows you to only model the facing=south state and automatically support all horizontal facings
     // - convert a Direction to degrees by using `(int) direction.toYRot()`
     rotation,

     // whether to vertically flip the whole cut-and-paste operation
     // - this allows you to only model the half=bottom state and automatically support half=top as well
     flipY,

     // in voxel space, the position to place the copied cuboid in your copycat model
     // - coordinates are changed automatically according to rotation and flipY
     vec3(0,0,0),

     // in voxel space, the size and position of the cuboid to be cut from the source block model
     // - move() can be omitted if the cuboid is positioned at 0, 0, 0
     // - all coordinates are changed automatically according to rotation and flipY
     aabb(16,4,16).move(0,0,0),

     // faces of this cuboid that should not render in you copycat model
     // - use `|` (bitwise OR) to include multiple faces
     // - the cullfaces are changed automatically according to rotation and flipY
     cull(UP|SOUTH)
);

Now to actually implement your model, simply open your item model in Blockbench, then for each element in Blockbench, add one call to assemblePiece in your code.

Image Explanation
image Copy Position to vec3()Copy Size to aabb()
image For each face you have disabled here, add that face to cull()

When you've copied everything, adjust the .move() parts to shift the textures to the correct positions, and then add additional logic to handle different block states.

Register the block in CCBlocks

Just copy an existing registration. There's too much boilerplate code to explain here.

If your copycat consists of multiple parts in the same block space, define a loot table here. An example can be found in https://github.com/copycats-plus/copycats/blob/134edde1b2371208db37716ae12b12723a6a81ff/src/main/java/com/copycatsplus/copycats/CCBlocks.java#L187-L209

If you copycat contains a Wrapped___Block, you need to also register the wrapped block here.

Register the item in CCCreativeTabs

Just add it to the list.

Add mixins for compatibility

This step is only required if the copycat has a vanilla counterpart (such as stairs and trapdoors).

Due to how the *CopycatBlock is implemented, vanilla is not aware that, for example, our copycat trapdoor is an actual trapdoor. This is because our copycat inherits from CopycatBlock, not TrapDoorBlock, causing instanceof checks to fail.

To fix this, do a global search for instanceof TrapDoorBlock and patch all applicable locations with mixins so that the instanceof check is executed against a WrappedTrapdoorBlock instance instead of the outer CopycatTrapdoorBlock instance.

The ICopycatWithWrappedBlock interface provides an unwrap function to help with this. It returns the inner Wrapped*Block if it exists, and returns the original CopycatBlock if it doesnt.

You can find examples of these mixins here: https://github.com/copycats-plus/copycats/blob/main/src/main/java/com/copycatsplus/copycats/mixin/copycat/trapdoor/RedStoneWireBlockMixin.java

Edge cases

Many of these instanceof checks are located in the vanilla pathfinding algorithm. Extra care needs to be taken when patching those methods, because they are a prime target for optimization by Lithium/Radium. To avoid crashes due to mixin collisions, set your mixin to be optional, increase the priority of your mixin, and write another one that is specific to Lithium/Radium.

A example can be found here: https://github.com/copycats-plus/copycats/blob/main/src/main/java/com/copycatsplus/copycats/mixin/copycat/fencegate/WalkNodeEvaluatorMixin.java

Add tooltips in tooltips.json

Add it in src/main/resources/lang/default/tooltips.json, and then run datagen to merge it to src/generated/resources/lang/en_us.json.

Add crafting recipes in CCStandardRecipes

Add a recipe using the copycat method, and then run datagen.

Implement getRequiredItems

Only required if your copycat combines multiple items in one block.

ISpecialBlockItemRequirement#getRequiredItems specifies the material requirement for a given block state, mainly so that the schematicannon will request for the correct amount rather than the default single item.

Register the copycat in CCBlockEntityTypes

This does not seem to be essential to the copycat's function, but to follow Create's implementation, it's better to do this regardless.