For mod developers
Adding Familiars Lib to a mod and making your own familiar.
Getting started
Add the Curse Maven snippet of the Familiars Lib version you want to your dependencies in build.gradle:
implementation "curse.maven:familiarslib-1316458:7415920"Creating your own familiar
You need a few things before coding:
- The familiar's model: a
.geo.jsonfile, exported from your GeckoLib Blockbench project. - The familiar's texture: saved from your Blockbench project.
- Animations, saved as JSON:
- idle: loop animation when idle
- interact: plays when right-clicking the familiar
- long_cast: plays when casting spells with a long cast time
- skill: plays when casting instant or short spells
- walk: loop animation when walking
- spawn: plays when the familiar spawns
- sleep: plays when sleeping in a familiar bed, and when stunned if the familiar has no sit animation
- continuous: loop animation for continuous spells
- sit (optional): loop animation when sitting or stunned. Only used if the familiar enables it (see Sitting and sleeping)
Put them here:
- Model:
resources/assets/<your_mod_id>/geo/ - Texture:
resources/assets/<your_mod_id>/textures/entity/ - Animations: all of them (except the optional ones you don't use), with the exact names above, in one animation file in
resources/assets/<your_mod_id>/animations/
Then create the entity class. Pick the base class that matches how your familiar moves:
| Base class | For |
|---|---|
AbstractTerrestrianSpellCastingPet | Walking familiars. No fall damage, and a random stroll goal that respects sitting and houses |
AbstractFlyingSpellCastingPet | Flying casters, with flying navigation and hovering |
AbstractFlyingMeleeSpellCastingPet | Flying melee fighters with Vex-style movement. Its attack damage grows with its spell power. Pair it with FlyingSkirmisherAttackGoal |
All of them extend AbstractSpellCastingPet, which holds everything else in this section (taming, colors, cosmetics, compendium...). A flying caster example:
public class ExampleFamiliarEntity extends AbstractFlyingSpellCastingPet {
public static final ResourceLocation textureResource = ResourceLocation.fromNamespaceAndPath(YourMainClass.MODID, "textures/entity/familiar_texture.png");
public ExampleFamiliarEntity(EntityType<? extends AbstractFlyingSpellCastingPet> pEntityType, Level pLevel) {
super(pEntityType, pLevel);
}
@Override
protected void registerGoals() {
super.registerGoals();
initializeAttackGoal(2);
//Any other goal specific to this familiar
}
@Override
protected WizardAttackGoal createAttackGoal(float min, float max) {
//The spells the familiar uses. Spell tags are recommended for easier management and flexibility.
List<AbstractSpell> attackSpells = HelperMethods.getSpellsFromTag(YourModTags.EXAMPLE_ATTACK_SPELLS);
List<AbstractSpell> defenseSpells = HelperMethods.getSpellsFromTag(YourModTags.EXAMPLE_DEFENSE_SPELLS);
List<AbstractSpell> movementSpells = HelperMethods.getSpellsFromTag(YourModTags.EXAMPLE_MOVEMENT_SPELLS);
List<AbstractSpell> supportSpells = HelperMethods.getSpellsFromTag(YourModTags.EXAMPLE_SUPPORT_SPELLS);
//Only return one of these goals
//If it's a flying familiar
return new FamiliarGoals.FlyingWizardAttackGoal(this, 1f, 30, 40)
.setPreferredHeight(4.0f)
.setCombatRanges(5.0f, 7.0f, 10.0f)
.setThreatAwarenessRadius(10.0f)
.setSpells(attackSpells, defenseSpells, movementSpells, supportSpells)
.setSingleUseSpell(SpellRegistry.RAY_OF_FROST_SPELL.get(), 160, 300, 3, 5) //Optional signature spell
.setIsFlying()
.setAllowFleeing(true)
.setSpellQuality(min, max);
//If it's a terrestrian familiar
return new WizardAttackGoal(this, 1.25f, 40, 50)
.setSpells(attackSpells, defenseSpells, movementSpells, supportSpells)
.setSingleUseSpell(SpellRegistry.ROOT_SPELL.get(), 340, 400, 8, 10) //Optional signature spell
.setAllowFleeing(true)
.setSpellQuality(min, max);
}
//Be careful: some attributes differ between terrestrian, melee and flying familiars
public static AttributeSupplier.Builder prepareAttributes() {
return LivingEntity.createLivingAttributes()
.add(Attributes.ATTACK_DAMAGE, 3.0)
.add(Attributes.ATTACK_KNOCKBACK, 0.0)
.add(Attributes.MAX_HEALTH, 30.0)
.add(Attributes.FOLLOW_RANGE, 24.0)
.add(Attributes.FLYING_SPEED, .1F)
.add(Attributes.MOVEMENT_SPEED, .2);
}
}The model class:
public class ExampleFamiliarModel extends DefaultedEntityGeoModel<ExampleFamiliarEntity> {
public static final ResourceLocation MODEL = ResourceLocation.fromNamespaceAndPath(YourMainClass.MODID, "geo/familiar_model.geo.json");
public static final ResourceLocation ANIMATIONS = ResourceLocation.fromNamespaceAndPath(YourMainClass.MODID, "animations/familiar_animations.animation.json");
public ExampleFamiliarModel() {
super(ResourceLocation.fromNamespaceAndPath(YourMainClass.MODID, "example_familiar"));
}
protected TransformStack transformStack = new TransformStack();
@Override
public ResourceLocation getModelResource(ExampleFamiliarEntity object) {
return MODEL;
}
@Override
public ResourceLocation getAnimationResource(ExampleFamiliarEntity animatable) {
return ANIMATIONS;
}
@Override
public void setCustomAnimations(ExampleFamiliarEntity entity, long instanceId, AnimationState<ExampleFamiliarEntity> animationState) {
super.setCustomAnimations(entity, instanceId, animationState);
if (Minecraft.getInstance().isPaused() || !entity.shouldBeExtraAnimated())
return;
transformStack.popStack();
}
}The renderer class. It uses culled render types: models with zero-thickness planes (capes, crowns, wings...) flicker with the default no-cull types, because both faces of each plane are drawn in the same place.
public class ExampleFamiliarRenderer extends HumanoidRenderer<ExampleFamiliarEntity> {
public ExampleFamiliarRenderer(EntityRendererProvider.Context renderManager, ExampleFamiliarModel model) {
super(renderManager, model);
//Color layers, hat and weapon layers go here, before the glow layer
addRenderLayer(new AutoGlowingGeoLayer<>(this));
}
@Override
public ResourceLocation getTextureLocation(ExampleFamiliarEntity entity) {
return ExampleFamiliarEntity.textureResource;
}
@Override
public void render(ExampleFamiliarEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
SpellRenderingHelper.renderSpellHelper(ClientMagicData.getSyncedSpellData(animatable), animatable, poseStack, bufferSource, partialTick);
}
@Override
public RenderType getRenderType(ExampleFamiliarEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return animatable.isInvisible() ? RenderType.itemEntityTranslucentCull(texture) : RenderType.entityCutout(texture);
}
}Then register the entity like any other modded entity, and your familiar is complete. The Familiar Camera works with it automatically.
