Skip to content

Entity Registration

itsmeow edited this page Jan 4, 2022 · 5 revisions

About

IMDLib's EntityRegistrarHandler and EntityTypeContainer system handles the following:

  • Creation and registration of EntityTypes
  • 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.

Examples

All Features Example

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();
}

Actual Use Examples

Usage

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())

Spawning

Spawns are added via the spawn method.

Parameters: MobCategory spawnType, int spawnWeight, int minSpawnGroup, int maxSpawnGroup

b -> b.spawn(MobCategory.CREATURE, 10, 1, 5)

Spawn Biomes

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.

List of types

This will add the spawn to all biomes within the type.

b -> b.biomes(BiomeTypes.FOREST, BiomeTypes.HOT)

List of types (overworld)

This will filter to only biomes with the OVERWORLD type.

b -> b.biomesOverworld(BiomeTypes.FOREST, BiomeTypes.HOT)

List of biomes (supplier)

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))

BiomeListBuilder function

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

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.

General Placement

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).

Default Placement

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)

Water 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)

Spawn Costs

This will register spawn costs.

Parameters: double costPer, double biomeMaxCost

b -> b.spawnCosts(1D, 10D)

Eggs

This will create and register a spawn egg.

Parameters: int solidColor, int spotColor

b -> b.egg(0x000000, 0xffffff)

Size

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)

Despawn

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()

Configuration

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.

Usage

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().

Variants

This provides a builtin way for entities to include texture variants.

List of IVariant

This is a quick and dirty way to provide a list of variant instances to use. The default implementation provides EntityVariants, 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"))

List of variant names

This is the most common method to use, it will create EntityVariants 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.

Integer

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.

Constructor + list

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")

Heads

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.

HeadType Builder

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

Item Group (Required)

This specified what creative tab / item group to place the head into.

b -> b.head().itemGroup(ExampleMod.ITEM_GROUP).done()

Mappings (Required)

The head IDs are dynamically mapped to the entity's variants via three methods.

Singleton

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

Names

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()
Numbers

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()
Custom

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()

Model (Required)

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()

Allow Floor

This is a simple switch allowing the head to be placed on the floor, not only walls.

b -> b.head().allowFloor().done()

Offset

This allows a global Y offset to be applied to the head model in rendering.

b -> b.head().offset(1F).done()