-
Notifications
You must be signed in to change notification settings - Fork 2
Entity Registration
IMDLib's EntityRegistrarHandler
and EntityTypeContainer
system handles the following:
- Creation and registration of
EntityType
s - Registration of natural spawning
- Registration of spawn costs
- Registration of spawn placement locations
- Creation and registration of spawn eggs
- Registration of entity attributes
- Creation of spawning configuration
- Custom configuration fields
- Creation and registration of entity heads
- Entity texture variants
- Entity containers (buckets, bottles, etc)
This is all provided through an extremely efficient and clean builder interface.
Please be sure to review Required Entity Interfaces to ensure function of your entities following use of this system.
Uses official maps and Architectury
public static final EntityRegistrarHandler H = IMDLib.entityHandler(MOD_ID);
public static final EntityTypeContainer<ExampleEntity> EXAMPLE = H.add(ExampleEntity.class, ExampleEntity::new, "example", () -> Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, 10D), b -> b
.spawn(MobCategory.WATER_CREATURE, 10, 1, 5)
.spawnCost(1D, 10D)
.waterPlacement()
.egg(0x000000, 0xffffff)
.size(0.5F, 0.5F)
.biomesOverworld(BiomeTypes.RIVER)
.variants("red", "green", "blue")
.head().mapToNames().setModel(ExampleEntityHeadModel::new).itemGroup(ExampleMod.ITEM_GROUP).done()
.config((holder, builder) -> {
holder.put("example_boolean", Boolean.class, builder.define("example_boolean", "Example Comment", false));
}, holder -> {
// Alternatively, use EntityTypeContainer::getCustomConfiguration().getBoolean("example_boolean") directly wherever the field is used and remove this load method.
ExampleEntity.example_boolean = holder.getBoolean("example_boolean");
})
.clientConfig((holder, builder) -> {
holder.put("example_boolean_client", Boolean.class, builder.define("example_boolean_client", "Example Comment", false));
}, holder -> {
ExampleEntity.example_boolean_client = holder.getBoolean("example_boolean_client");
});
/* Called by the mod class at construction. On Forge, use H.subscribe(modBus) instead (see Usage below) */
public static void init() {
H.init();
}
- Better Animals Plus (Architectury)
- Better Animals Plus (Forge)
- Whisperwoods (Architectury)
- Whisperwoods (Forge)
IMDLib uses exclusively a deferred-registry like system. All entities should be put into public static final
fields, preferably in a class along the lines of ModEntities
.
The entity interface is provided via IMDLib::entityHandler(String modid)
.
public static final EntityRegistrarHandler H = IMDLib.entityHandler(MOD_ID);
You can then register a basic entity using H.add
. Please note some functions in this example depend on your mappings. I will be using official mappings.
The handler MUST be initialized with the mod bus. This varies based on if you are using Forge or Architectury.
On Architectury, call init()
on the handler when the mod is constructed.
On Forge, the handler must be passed the mod bus at mod construction via the subscribe()
method.
This can be done as such:
H.subscribe(FMLJavaModLoadingContext.get().getModEventBus());
This is the minimum required code to register an entity and its attributes.
public static final EntityTypeContainer<ExampleEntity> EXAMPLE = H.add(ExampleEntity.class, ExampleEntity::new, "example", () -> Mob.createMobAttributes(), b -> b);
Additional methods will be documented below. All methods are added to the lambda parameter (b -> b.exampleMethod()
)
Spawns are added via the spawn
method.
Parameters: MobCategory spawnType, int spawnWeight, int minSpawnGroup, int maxSpawnGroup
b -> b.spawn(MobCategory.CREATURE, 10, 1, 5)
This is required if spawn
is set, otherwise it will not be applied in any biomes.
Spawn Biomes are provided by a variety of methods, supporting BiomeDictionary
/BiomeTypes
.
On Architectury, IMDLib includes a wrapper for Forge's BiomeDictionary
called BiomeTypes
. This example will use BiomeTypes
, but it can be safely replaced with BiomeDictionary.Type
on Forge.
This will add the spawn to all biomes within the type.
b -> b.biomes(BiomeTypes.FOREST, BiomeTypes.HOT)
This will filter to only biomes with the OVERWORLD type.
b -> b.biomesOverworld(BiomeTypes.FOREST, BiomeTypes.HOT)
This provides a consistent interface to delay the generation of biome lists to when the biomes are loaded, because Minecraft loads biomes when the world is loaded. This is the best user-facing way to dynamically provide biomes. The supplier called after biomes have loaded.
b -> b.biomes(() -> BiomeTypes.getBiomes(BiomeTypes.FOREST))
This will construct a BiomeListBuilder
and pass it to a lambda for use. See BiomeListBuilder
for more details.
b -> b.biomes(c -> c.withTypes(BiomeTypes.FOREST).withoutTypes(BiomeTypes.COLD).extra(Biomes.DESERT).onlyOverworld())
Spawn placement is provided by multiple methods.
Generally, spawn placement provides a predicate which returns true if the entity should spawn there or not. The placement type and height map type determine where the spawning engine will attempt to place your entity.
This is the full method providing full control over spawn placement.
Parameters: SpawnPlacements.Type type, Heightmap.Types heightMap, SpawnPlacements.SpawnPredicate<T> predicate
b -> b.placement(SpawnPlacements.Type.NO_RESTRICTIONS, Heightmap.Types.MOTION_BLOCKING, null)
A null predicate will always return true. The above configuration will attempt to place the entity anywhere there is not a solid block (including mid-air).
This is a convenience method that wraps placement
. It provides only a predicate, and passes SpawnPlacements.Type.ON_GROUND, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES
. This value is used by most vanilla ground creatures.
b -> b.defaultPlacement(ExampleEntity::placement)
This is a convenience method that wraps placement
. It passes SpawnPlacements.Type.IN_WATER, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES
. It can either accept a custom predicate or use a default.
The default predicate checks the following:
- Y position is greater than 45
- Y position less than the sea level minus one
- The block fluid state is of the
WATER
type.
b -> b.waterPlacement()
OR
b -> b.waterPlacement(ExampleEntity::placement)
This will register spawn costs.
Parameters: double costPer, double biomeMaxCost
b -> b.spawnCosts(1D, 10D)
This will create and register a spawn egg.
Parameters: int solidColor, int spotColor
b -> b.egg(0x000000, 0xffffff)
This will set the hitbox size of the entity. If not specified, it defaults to 1 by 1.
Parameters: float width, float height
b -> b.size(1.5F, 3F)
This will enable despawning by default on the entity. This is recommended for WATER MobCategory due to how Minecraft's water spawning system works.
b -> b.despawn()
Provides an interface to add custom configuration fields under the entity's section. There are four overloads.
config(CustomConfigurationInit)
: Initialization only (load left blank)
config(CustomConfigurationInit, CustomConfigurationLoad)
: Init & load methods
clientConfig(CustomConfigurationInit)
: Client-side initialization only (load left blank)
clientConfig(CustomConfigurationInit, CustomConfigurationLoad)
: Client-side init & load methods
CustomConfigurationInit
is a consumer that provides CustomConfigurationHolder
and a ConfigBuilder
on Architectury (ForgeConfigSpec.Builder
on Forge)
CustomConfigurationLoad
is a consumer that provides the same CustomConfigurationHolder
from initialization.
Typical usage is to pass built configuration suppliers to the CustomConfigurationHolder
via holder.put
.
The implementation of holder.put
varies based on modding API. Architectury uses the following:
b -> b.config((holder, builder) -> {
holder.put("example_boolean", Boolean.class, builder.define("example_boolean", "Example Comment", false));
}, holder -> {
// Alternatively, use EntityTypeContainer::getCustomConfiguration().getBoolean("example_boolean") directly wherever the field is used and remove this load method.
ExampleEntity.example_boolean = holder.getBoolean("example_boolean");
})
Forge:
b -> b.config((holder, builder) -> {
holder.put(builder.comment("Example Comment").worldRestart().define("example_boolean"));
}, holder -> {
// Alternatively, use EntityTypeContainer.getCustomConfiguration().getBoolean("example_boolean") directly wherever the field is used and remove this load method.
ExampleEntity.example_boolean = holder.getBoolean("example_boolean");
})
The same applies for clientConfig
, however the holder is in EntityTypeContainer::getCustomConfigurationClient()
.
This provides a builtin way for entities to include texture variants.
This is a quick and dirty way to provide a list of variant instances to use. The default implementation provides EntityVariant
s, which accepts a variant name and variant texture path, but this method allows for custom implementations with non-single-string constructors to be provided.
b -> b.variants(new EntityVariant("variant_1", "tex_1"), new EntityVariant("variant_2", "tex_2"))
This is the most common method to use, it will create EntityVariant
s with the name provided and the texture pointing to modid:textures/entity/name.png
. By default they will all have heads enabled if the entity is supplied a head configuration.
This will create integer named variants from 1
to the parameter. It is essentially a wrapper for the list of variant names that loops up to the provided parameter.
b -> b.variants(5)
This will create variants "1", "2", "3", "4", "5"
, in the same fashion as the list of variant names method.
This is shorthand allowing for an IVariant
implementation constructor that accepts a single string to be passed arguments for construction. It will call the function on each supplied string to create the variant list.
Parameters: Function<String, IVariant> constructor, String... variantArguments
b -> b.variants(EntityVariant::new, "1", "2")
First, head()
must be called, then HeadType builder parameters are provided, then done()
is called to return to a normal EntityTypeContainer builder.
b -> b.head().mapToNames().setModel(() -> ExampleEntityHeadModel::new).itemGroup(ExampleMod.ITEM_GROUP).done()
A second method that accepts a String will allow the item id of the head to be changed. By default, it is the entity's name with head
appended.
The HeadType builder provides an interface to map variants to models and textures for the head, as well as provide other miscellaneous information.
When it is finished being used, call done()
to return to the entity builder.
It is required that a head has the following set on its builder:
- ID Mapping
- Model
- Item Group
This specified what creative tab / item group to place the head into.
b -> b.head().itemGroup(ExampleMod.ITEM_GROUP).done()
The head IDs are dynamically mapped to the entity's variants via three methods.
This is used for entities that have no variants.
b -> b.singleton("1", "tex")
The above creates a head with the ID: examplehead_1
(given that the entity is named example
, and head()
was not passed a custom name)
The head's texture (when placed) will be from modid:textures/entity/tex.png
This is the most straightforward, the head IDs are mapped directly to the variant names. Any IVariant
that returns true for hasHead()
will be given a head matching its name.
b -> b.head().mapToNames().done()
If an entity has named variants, but you want the head IDs mapped to numbers, it will assign an index to each variant by its registration order and map the item IDs to each of those instead.
b -> b.head().mapToNumbers().done()
This allows any ID mapping scheme to be created. It accepts a function returning an ID given an IVariant
.
b -> b.head().mapToCustom(variant -> variant.getName()).done()
This is the model used for rendering the head. It is passed via double supplier to avoid classloading issues on the dedicated server.
b -> b.head().setModel(() -> ExampleEntityHeadModel::new).done()
This is a simple switch allowing the head to be placed on the floor, not only walls.
b -> b.head().allowFloor().done()
This allows a global Y offset to be applied to the head model in rendering.
b -> b.head().offset(1F).done()