-
Notifications
You must be signed in to change notification settings - Fork 23
Creating copycats
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.
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.
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?
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.
-
Create your
Wrapped___Blockclass that inherits from the target vanilla Block class. e.g.class WrappedTrapdoorBlock extends TrapDoorBlock -
Create your
Copycat___Blockclass that inherits fromWaterloggedCopycatWrappedBlock -
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,
createBlockStateDefinitionandcopyStateyourself to set up the block states. - Read through the vanilla block class and all its parent classes. For each block event it uses (except
useandgetStateForPlacement, which is handled automatically), override the corresponding event in your copycat block class and redirect the call to the wrapped instance.
- Create a public static field to store your
-
Implement the copycat-specific methods:
isIgnoredConnectivitySide,canConnectTexturesToward,canFaceBeOccluded,shouldFaceAlwaysRender.isIgnoredConnectivitySidecontrols whether an adjacent block can appear connected to your copycat on the specified block face.canConnectTexturesTowardcontrols whether your copycat can appear connected to an adjacent block as a whole.
You are on your own here. This is just a basic list of what's required, but you may need more.
-
Create your
Copycat___Blockclass that inherits fromWaterloggedCopycatBlock -
In your
Copycat___Blockclass:- Implement the constructor,
createBlockStateDefinition,getStateForPlacement,getShape,rotate, andmirror - If your block consists of multiple items being put in the same block space, also
implement
canBeReplaced,onSneakWrenched(so that you can dismantle individual parts on sneak-wrenching),getRequiredItems(for Schematicannon) - I highly recommend you implement block drops with loot tables instead of overriding
getDrops, so that it remains compatible with datapacks. An example can be found here: https://github.com/copycats-plus/copycats/blob/134edde1b2371208db37716ae12b12723a6a81ff/src/main/java/com/copycatsplus/copycats/CCBlocks.java#L187-L209
- Implement the constructor,
-
Implement the copycat-specific methods:
isIgnoredConnectivitySide,canConnectTexturesToward,canFaceBeOccluded,shouldFaceAlwaysRender.isIgnoredConnectivitySidecontrols whether an adjacent block can appear connected to your copycat on the specified block face.canConnectTexturesTowardcontrols whether your copycat can appear connected to an adjacent block as a whole.
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 |
|---|---|
![]() |
Copy Position to vec3()Copy Size to aabb()
|
![]() |
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.
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.
Just add it to the list.
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
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 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 a recipe using the copycat method, and then run datagen.
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.
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.

