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.
Copycat___Block
Implementing 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.
-
Create your
Wrapped___Block
class that inherits from the target vanilla Block class. e.g.class WrappedTrapdoorBlock extends TrapDoorBlock
-
Create your
Copycat___Block
class 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,
createBlockStateDefinition
andcopyState
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
andgetStateForPlacement
, 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
.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.
-
Create your
Copycat___Block
class that inherits fromWaterloggedCopycatBlock
-
In your
Copycat___Block
class:- 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
.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.
Copycat___Model
Implementing 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.
CCBlocks
Register the block in 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.
CCCreativeTabs
Register the item in 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
tooltips.json
Add tooltips in 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
.
CCStandardRecipes
Add crafting recipes in Add a recipe using the copycat
method, and then run datagen.
getRequiredItems
Implement 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.
CCBlockEntityTypes
Register the copycat in 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.