Patchnote

Complete changelog of new features, changes, and bug fixes for each EriAPI release.

v1.8.5 May 26, 2026

Critical fix — cosmetic: ModelBakeEvent registered too late (3rd iteration)

True root cause of the pink/black cosmetic texture bug, identified from client logs. The 1.8.3 and 1.8.4 fixes addressed plausible hypotheses (wrong GL texture slot, stale MISSING_TEXTURE cache, GUI alpha cull) but the bug persisted because no ArmorCosmeticBakedModel was ever installed in the model registry.

Exact cause: ArmorCosmeticManager.bootstrap() — called from ClientProxy.init() (FMLInit) — registered the BakeHandler after the initial ModelBakeEvent had already been dispatched. That event fires during the resource-pack reload that happens between FMLPreInit and FMLInit. As a result, our baked model never replaced the default one, and vanilla rendered the model from the items atlas sprite — which rejected any non-POT texture as MISSING_TEXTURE at stitch time.

  • New public entry point: EriCosmetics.preInit() (alias for ArmorCosmeticManager.registerBakeHandler()) — to be called from FMLPreInitializationEvent in consumer mods.
  • Automatic preInit registration: EriAPI.ClientProxy.preInit() now calls ArmorCosmeticManager.registerBakeHandler(), registering the listener BEFORE the first ModelBakeEvent. Existing consumer mods need no change.
  • Refactor of bootstrap(): the method now only attaches the ArmorCosmeticLayer to RenderPlayer variants. The ModelBakeEvent registration logic moved into the new public registerBakeHandler(). Backward-compatible — bootstrap() defensively calls registerBakeHandler() so consumers that never migrate still get the handler registered (only too late for the initial bake).
  • Late-registration detector: if bootstrap() runs with at least one registered cosmetic but zero swaps applied, a loud WARN is logged with the actionable fix.

No API breakage. Recommended upgrade for any mod using the cosmetic module — the fix applies implicitly; calling EriCosmetics.preInit() remains optional and documented.

v1.8.4 May 26, 2026

Fix — cosmetic: follow-up on the missing-texture fix (2nd iteration)

Version 1.8.3 fixed the texture unit (forcing GL_TEXTURE0 before every bind) but the item-context rendering remained broken in practice — the pink/black missing-texture pattern persisted in the inventory, hotbar, hand, ground, and GUI contexts. 1.8.4 is a follow-up that adds three complementary measures: texture preload at bake-time, alpha-test neutralisation plus explicit GL_TEXTURE_2D enable in the TEISR, and a one-shot diagnostic log to identify the exact cause if the bug were still present.

  • Texture preload on ModelBakeEventArmorCosmeticManager.BakeHandler now forces TextureManager.loadTexture(rl, new SimpleTexture(rl)) for every texture of every cosmetic at resource-pack-load time. This guarantees that the SimpleTexture is uploaded to a valid GL ID before any rendering, and that any I/O error (corrupted PNG, missing resource) is logged immediately with the expected on-disk path. An additional check detects whether the loaded texture is the TextureUtil.MISSING_TEXTURE sentinel and emits an explicit WARN.
  • ArmorCosmeticTEISR: added GlStateManager.enableTexture2D() plus disableAlpha()/enableAlpha() around the render to neutralise any pipeline-side disables (item glint, the alpha cull at 0.1 applied by renderItemModelIntoGUI).
  • BlockbenchRenderer.bindTexture: one-shot diagnostic log per texture per session showing the ResourceLocation, glTextureId, and a missing=true/false flag. No per-frame or per-face spam.

No public API change. Modifications are purely internal to the rendering and registration classes.

v1.8.3 May 26, 2026

Fix — cosmetic: missing-texture (pink/black) in the item context

Fixed the texture rendering of armor cosmetics (ArmorCosmeticItem) in the item context — inventory, hotbar, first person, third person, ground, item frame, GUI. The 3D Blockbench model showed up correctly (geometry was fine) but the texture was replaced by the characteristic pink/black missing-texture checkerboard. Player rendering through ArmorCosmeticLayer was not affected.

Root cause — combination of two issues:

  • Wrong texture unit: the RenderItem.renderItem(stack, IBakedModel) pipeline may leave GL_TEXTURE1 (used for the lightmap) as the active texture unit when delegating to TileEntityItemStackRenderer.renderByItem. TextureManager.bindTexture() binds the texture on the currently active unit — if that is slot 1, the fragment shader keeps sampling slot 0 (which still holds the item atlas or any stale residual texture), which is why the rendering was broken.
  • Potentially stale static cache: the BlockbenchRenderer.lastBoundTexture field skipped the rebind when the same texture had already been bound, without accounting for the fact that another rendering system (player layer, another TESR, GUI) may have bound a different texture in between.

Solution — three cumulative measures:

  • ArmorCosmeticTEISR.renderByItem forces GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit) before any draw, and restores that slot on exit (defensive).
  • BlockbenchRenderer.bindTexture forces setActiveTexture(defaultTexUnit) right before every TextureManager.bindTexture() call, as a safety net regardless of the call context.
  • The lastBoundTexture = null reset at the start of every public BlockbenchRenderer entry point was already in place — confirmed correct to avoid stale caches across successive calls.

No public API change — no migration required for consumers. The fix is purely internal to the rendering classes.

v1.8.2 May 25, 2026

Feat — cosmetic: 3D Blockbench rendering in every item context

ArmorCosmeticItem instances now render their Blockbench model in 3D in every display context: inventory, hotbar, first person (right / left hand), third person (right / left hand), on the ground, fixed (item frame), head (worn on the head). Before this patch, only the player rendering via ArmorCosmeticLayer drew the 3D model — the inventory and all other views went through a 2D extruded sprite (item/generated) routed through the item atlas. That forced square power-of-two textures and rejected non-POT PNGs (black/pink missing-texture output).

The new pipeline relies on three pieces: ArmorCosmeticTEISR (a TileEntityItemStackRenderer that draws the Blockbench geometry via BlockbenchRenderer), ArmorCosmeticBakedModel (an empty IBakedModel that flags isBuiltInRenderer() and isGui3d() to true, and exposes per-context transforms built from the JSON display block), and a ModelBakeEvent handler in ArmorCosmeticManager that swaps every cosmetic's vanilla baked model with an ArmorCosmeticBakedModel on every resource pack reload. Textures are bound via TextureManager.bindTexture() (goes through SimpleTexture, off-atlas), so any PNG dimension works — POT not required, multi-texture supported, custom UVs honored.

Feat — cosmetic: respects the Blockbench display field

The JSON model's display block (keys thirdperson_righthand, thirdperson_lefthand, firstperson_righthand, firstperson_lefthand, head, gui, ground, fixed) is converted into a vanilla ItemCameraTransforms and applied automatically by RenderItem before the TEISR call. Translations in Blockbench pixels (1/16 block) are divided by 16 to match the vanilla OpenGL transform convention. Rotation in degrees and unit-less scale are passed through as-is.

Feat — anim: AnimatedBlockModel.getDisplayTransforms()

AnimatedBlockModel now publicly exposes the per-context transform map via getDisplayTransforms() (plus hasDisplayTransforms() and getDisplayTransform(String) for targeted lookups). The map is extracted by BlockbenchModelParser from the JSON display field, with an identity fallback when the block is missing — existing models remain backward compatible.

v1.8.1 May 25, 2026

Fix — cosmetic: Y-flip in ArmorCosmeticLayer

Fixed the Y-axis flip in ArmorCosmeticLayer.renderGroupAtBone — Blockbench models now render right-side up on biped bones. The Blockbench convention (Y+ up, X+ right) is now correctly converted to the MC entity bone-local space (Y- up, ModelBiped.addBox convention) via a GlStateManager.scale(-1, -1, 1) applied right after bone.postRender(0.0625f). The two sign flips preserve the normal orientation (winding order) without needing glFrontFace. Before this fix, cosmetic models rendered upside-down, hanging below the bone.

The public API of the cosmetic module is unchanged — no consumer-side migration required. The default offsets (DEFAULT_BONE_OFFSETS) and the group-name-to-bone mapping table (GROUP_NAME_TO_BONE) were already correct for the flipped space.

v1.8.0 May 25, 2026

New module — cosmetic (3D armor cosmetics)

New package fr.eri.eriapi.cosmetic for attaching a Blockbench 3D model to any of the 4 vanilla armor slots (HEAD, CHEST, LEGS, FEET). Cosmetic items inherit zero stats — no damage reduction, no durability, no enchantability — they only serve as a visual layer rendered on top of the player via a LayerRenderer<EntityPlayer> attached to every RenderPlayer in the skinMap (default + slim).

Item mask = EriCosmetics.armor()
    .modId("yourmod")
    .registryName("barry_mask")
    .displayName("Barry Mask")
    .creativeTab(CreativeTabs.COMBAT)
    .slot(EntityEquipmentSlot.HEAD)
    .model(new ResourceLocation("yourmod", "barry_mask"))
    .build();

The model automatically follows the vanilla biped bone matching the slot via postRender(0.0625f): HEAD → bipedHead, CHEST → bipedBody, LEGS / FEET → bipedRightLeg — head tilt, body sneak, arm swing and leg walking work natively without any custom rig.

Multi-bone convention — Blockbench group naming

A model may contain multiple root groups whose name determines which vanilla bone they attach to (case-insensitive): head, body, right_arm, left_arm, right_leg, left_leg (variants without underscore are also accepted). Any group not matching a bone is rendered on the slot's primary bone. Lets you build a full cosmetic armor set that follows the entire vanilla biped.

Refactor — BlockbenchRenderer extracted from the TESR

The geometry code from AnimatedBlockTESR (AnimatedBlockModel rendering, ModelGroup / ModelElement handling, texture atlas, Blockbench transform application) is now exposed in the public class fr.eri.eriapi.cosmetic.BlockbenchRenderer. It is reused by the cosmetic module and by AnimatedBlockTESR (which now delegates to renderModelWithOverrides()). The TESR keeps its IDLE optimization and perf tracking — 100% backward compatible, no public API change for existing users.

  • renderModel(AnimatedBlockModel) — full render of a model at rest.
  • renderModel(model, AnimationPose) — render with applied pose.
  • renderGroup(ModelGroup, ...) — render of a single group (used by multi-bone dispatch).
  • renderModelWithOverrides(model, pose, textureOverrides) — signature preserving TESR semantics.

Documentation

New page cosmetic.html (FR + EN) covering the full builder API, bone mapping, multi-bone convention, default offsets, the public BlockbenchRenderer API and a complete troubleshooting section.

v1.7.2 May 13, 2026

Added — LineChart.yLabelDecimals(int)

The number of decimals displayed on Y-axis labels (and in the point hover tooltip) is now configurable. Previously, the internal formatValue method was static and hardcoded to 1 decimal, with no public setter — making it impossible to display cent-precise prices or integer-only values without a stray decimal.

LineChart chart = new LineChart()
    .yLabelDecimals(2)  // 12.34 instead of 12.3
    .addDataset("Price", points);

Default: 1 (backward compatible). Setting 0 forces integer display. Negative values are clamped to 0.

v1.6.8 May 07, 2026

Critical fix — EventBuilder.filter() overwrote previous filters

Each call to .filter(...) on an EventBuilder replaced the previous filter outright instead of combining them. As a result, listeners configured with multiple chained .filter(...) calls only honored the last one, leaking events that should have been rejected by earlier filters.

Real-world example: a PlayerTickEvent handler scoped to a specific dimension, configured like this:

EriEvents.on(TickEvent.PlayerTickEvent.class)
    .filter(e -> e.phase == TickEvent.Phase.END)
    .filter(e -> e.player.world.provider.getDimension() == TARGET_DIM)
    .filter(e -> !e.player.world.isRemote)   // this filter OVERWROTE the previous ones
    .handle(e -> applyEffect(e.player));

The dimension check was lost, and the effect was applied across every dimension on the server side.

Fixed: successive calls to filter(...) are now combined with logical AND via Predicate.and(). All predicates must return true for the handler to be invoked. The expected and natural behavior is restored, with no impact on listeners that used a single filter.

v1.6.7 May 05, 2026

Fix — 3D animated models offset from their hitbox

Blockbench models rendered by AnimatedEntityRenderer appeared visually shifted by half a block on X and Z compared to the entity hitbox. Cause: Blockbench centers models on (8, 0, 8) in pixels (= 0.5, 0, 0.5 in GL units), but the entity origin (posX/posY/posZ) is the bottom-center of the bounding box. Without an offset, the model's (0,0,0) corner is rendered at the hitbox center.

Fixed in doRender(): a GlStateManager.translate(-0.5f, 0.0f, -0.5f) is now applied right after the yaw rotation to recenter the model. Yaw rotation now happens around the model's actual vertical axis (its center) instead of an off-center pivot. Cascade-fixes movement animations that looked buggy — they were simply applied around the wrong pivot.

Fix — Hostile mobs targeting Creative / Spectator players

The HOSTILE_MELEE, HOSTILE_RANGED and BOSS presets of EriEntity created EntityAINearestAttackableTarget tasks without any predicate. Result: staff/admins in creative mode were targeted by mobs like any survival player.

  • EriEntityBase, GeneratedEntity and PathfinderBuilder.targetPlayers() — all target tasks aimed at EntityPlayer.class now use the 6-arg constructor with a Predicate<EntityPlayer> that rejects players in isCreative() or isSpectator().
  • The predicate is stored as a static singleton per class to avoid one lambda allocation per spawned entity — critical at 500-1000 concurrent players.

Perf — AnimatedEntityRenderer: per-texture draw call batching

The previous implementation called buffer.begin() + tessellator.draw() per face. A model with 200 elements and 6 faces per element therefore issued 1200 GPU draw calls per entity per frame — catastrophic FPS drop with even a handful of visible entities.

Refactor of renderElement(): faces of the same element are now grouped by texture into a single begin() / draw() pair. Most elements only use one or two textures — the reduction is typically 6x to 12x depending on the model. For a complex 200-element single-texture mob: 1200 draw calls -> 200.

  • New internal methods: resolveTexture(String) -> ResourceLocation (resolution without binding) and appendFaceVertices(buffer, ...) (appends a face's 4 vertices into an already-open buffer).
  • The old drawFace() method was replaced — no public API change, only the internal pipeline.
  • Compatible with all existing animations (translation, rotation, scale, cumulativeRotation, texture overrides). Matrix transforms are unchanged; only the draw-flushing is batched.

100% backward compatible — no public API change.

v1.6.6 May 05, 2026

Fix — EriEntityBase: eriDef resolved before AI / attribute setup

The EntityLiving constructor calls applyEntityAttributes() and initEntityAI() via super(world) before the EriEntityBase subclass can assign this.eriDef. Result: mobs spawned with no AI and vanilla default stats, regardless of what was declared via EriEntity.create(...).

Restructured: the applyEntityAttributes() and initEntityAI() overrides are now no-ops, and the constructor calls applyEriAttributes() + setupEriAI() after resolving eriDef via DEF_BY_CLASS. Subclasses of EriEntityBase (used for custom Java logic) now correctly receive their stats, AI and hitbox.

v1.6.5 May 02, 2026

Java animations on the builder — .javaAnimation()

The EriAnimBlock and EriEntity builders now expose .javaAnimation(String name, EriAnimJava anim) to register a Java animation directly on the fluent builder — no more need to create a custom TileEntity subclass nor to call addJavaAnimation() manually in the constructor. Anims are stored in the BlockDefinition / EntityDefinition and retrievable via the matching helpers.

  • EriAnimBlock.javaAnimation(name, anim) — appends to the AnimBlockDefinition.javaAnimations map. GeneratedBlock.createTileEntity() now calls configure() and loops to wire the anims onto the auto-generated AnimatedBlockTileEntityGeneric.
  • EriEntity.javaAnimation(name, anim) — appends to the EntityDefinition.javaAnimations map. Retrievable via ContentRegistry.getEntityDef(modId, name).getJavaAnimation("idle") in the entity constructor.
  • Coexists with .animation().erianim files and EriAnimJava classes can be mixed on the same builder.
  • New helper ContentRegistry.getEntityDef(modId, registryName) — direct lookup by name (previously you had to iterate getEntityDefs()).

Fix — AnimatedBlockTileEntityGeneric.configure() never called

Latent bug since v1.6.4: the no-arg constructor + configure() pattern of AnimatedBlockTileEntityGeneric was never invoked — animated blocks generated by EriAnimBlock without a custom .tileEntity(MyTE.class) had no modelId, no animFileId, and no defaultAnimation. Resolved in GeneratedBlock.createTileEntity(): configuration is now applied at TE creation, alongside the Java anims declared on the builder.

100% backward compatible — existing .erianim files and custom TEs continue to work unchanged.

v1.6.4 May 02, 2026

Java Animation API — write animations in pure Java

New API to write animations directly in Java, bypassing .erianim files. Ideal for procedural animations (sin/cos oscillations, reactions to entity state, parametric movements) impossible or tedious to express in JSON. Coexists with .erianim via the same te.playAnimation("name") API.

  • EriAnimJava — abstract class to extend. Two methods: defineKeyframes(ctx) (declarative, cached) and compute(ctx, pose) (procedural, per-frame). duration() required, endBehavior() optional.
  • AnimContext — context with animTick, partial, worldTick, target, helpers asEntity()/asTileEntity(), and DSL at(tick).group(name)....
  • GroupPoseBuilder — fluent API accessible via pose.group("name"). add* methods (additive on keyframes) and set* (replace).
  • Overlayte.playOverlay("name", anim) plays a second Java anim on top of the main one (overlay wins on touched groups). Client-local, never goes through packets.
  • Registrationte.addJavaAnimation("walk", new WalkAnim()) in the TileEntity constructor. Registry shared server+client.

See the full documentation in Animation System — Java Animation API.

No bug fixes — additions only, 100% backward compatible. Existing .erianim files continue to work unchanged.

v1.6.1 April 28, 2026

ListRenderContext — Proportional vertical offset (drawTextProp)

New method ListRenderContext.drawTextProp(String, int offsetX, int offsetYPermil, int maxWidth, int color). Unlike drawText() where offsetY is in raw screen pixels, drawTextProp expresses the vertical offset in permil of the row height (1/1000) — the layout stays correct at every window size.

Symptom before: a fixed -14 px offset that correctly placed the name above the center at fullscreen (itemHeight ~96 px) pushed the text out of the row bounds in a smaller window when the row was resized. drawTextProp(text, x, -150, w, color) places the text ~15% of the row height above the center, regardless of the actual row height.

Java
// BEFORE — offsetY in screen pixels (breaks in small windows)
ctx.drawText(name,   textX,         -14, textMaxW,      0xFFFFFFFF);
ctx.drawText(cost,   textX,         +14, textMaxW,      0xFF4ADE80);
ctx.drawText(status, statusOffsetX, -14, statusW + gap, 0xFFBB55FF);

// AFTER — offset in permil of the row height (correct at every resolution)
ctx.drawTextProp(name,   textX,         -150, textMaxW,      0xFFFFFFFF);
ctx.drawTextProp(cost,   textX,         +150, textMaxW,      0xFF4ADE80);
ctx.drawTextProp(status, statusOffsetX, -150, statusW + gap, 0xFFBB55FF);

The original drawText method is preserved — no breaking change.

v1.6.0 April 24, 2026

EriBlock — Block variants and entity callbacks

The EriBlock builder gains six new methods that cover most vanilla patterns (logs, leaves, plants) and expose entity walk/collision callbacks. No more custom BlockRotatedPillar or BlockLeaves subclasses: a simple .logLike() or .leavesLike(sapling) is enough.

  • .renderLayer(BlockRenderLayer) — sets the client render layer (SOLID, CUTOUT, CUTOUT_MIPPED, TRANSLUCENT). Required for leaves, plants, glass.
  • .onEntityWalk(TriConsumer<World, BlockPos, Entity>) — low-level callback when an entity walks on top of the block.
  • .onEntityCollision(TriConsumer<World, BlockPos, Entity>) — callback when an entity passes through the block (useful for traps / toxic plants).
  • .logLike() — generates a BlockRotatedPillar (AXIS property) for orientable logs.
  • .leavesLike(Block sapling) — generates a full BlockLeaves (CHECK_DECAY + DECAYABLE, natural decay, 5% sapling drop). Forces CUTOUT_MIPPED automatically.
  • .plantLike() — decorative plant: reduced hitbox, no collision, CUTOUT. The blockstate must point to block/cross.

TriConsumer — New functional interface

Java 8 does not provide a native TriConsumer<A, B, C>. EriAPI adds this interface in fr.eri.eriapi.content.TriConsumer for 3-parameter callbacks (used by onEntityWalk / onEntityCollision and available for your own builders).

Internal refactor — GeneratedBlockCommons

To avoid duplication between the 4 generated block variants (GeneratedBlock, GeneratedLogBlock, GeneratedLeavesBlock, GeneratedPlantBlock), shared logic (drops, hitbox, callbacks, TileEntity) has been extracted into GeneratedBlockCommons. No visible API change for users.

Full example

Java
// Custom tree log (automatic X/Y/Z axis)
Block ERINA_LOG = EriBlock.create("mymod", "erina_log")
    .material(Material.WOOD)
    .hardness(2.0f)
    .harvestTool("axe", 0)
    .soundType(SoundType.WOOD)
    .logLike()
    .register();

// Leaves that decay + 5% sapling drop
Block ERINA_LEAVES = EriBlock.create("mymod", "erina_leaves")
    .material(Material.LEAVES)
    .hardness(0.2f)
    .soundType(SoundType.PLANT)
    .leavesLike(ERINA_SAPLING)
    .register();

// Toxic plant that damages on contact
Block TOXIC_PLANT = EriBlock.create("mymod", "toxic_plant")
    .material(Material.PLANTS)
    .hardness(0.0f)
    .soundType(SoundType.PLANT)
    .plantLike()
    .onEntityCollision((world, pos, entity) -> {
        if (entity instanceof EntityLivingBase && !world.isRemote) {
            ((EntityLivingBase) entity).addPotionEffect(
                new PotionEffect(MobEffects.POISON, 60, 0)
            );
        }
    })
    .register();

v1.5.2 April 22, 2026

EriBlock — Custom hitboxes (.hitbox / .hitboxes)

EriBlock now supports custom collision and selection boxes directly in the builder, without manually subclassing Block.

  • .hitbox(AxisAlignedBB) — Replaces the default full-block hitbox with a single custom box. Automatically sets isFullCube and isOpaque to false. Unit: 1.0 = 1 block = 16 pixels.
  • .hitboxes(AxisAlignedBB...) — Defines multiple independent collision boxes. Each box is tested separately during entity collision via addCollisionBoxToList. The selection outline (hover highlight) shows the union of all boxes. Automatically sets isFullCube and isOpaque to false.
Java — Examples
// Single hitbox — half-block height (1.0 = 1 block = 16 pixels)
EriBlock.create("mymod", "half_slab")
    .material(Material.ROCK)
    .hardness(2.0f)
    .hitbox(new AxisAlignedBB(0, 0, 0, 1, 0.5, 1))
    .register();

// Multiple hitboxes — flat base + central pillar
EriBlock.create("mymod", "machine")
    .material(Material.IRON)
    .hardness(4.0f)
    .hitboxes(
        new AxisAlignedBB(0,    0,    0,    1,    0.25, 1),
        new AxisAlignedBB(0.25, 0.25, 0.25, 0.75, 1,    0.75)
    )
    .register();

v1.5.1 April 18, 2026

Element-level animation (elementTracks)

.erianim.json animations can now target individual elements inside a Blockbench group, not only the group as a whole. Each element has its own rotationOrigin (pivot) defined in the model, and animations can apply rotation, translation, scale, visibility and spin around that pivot. Fully backward compatible: existing animations without elementTracks render exactly as before.

  • JSON format — New optional "elementTracks" field alongside "tracks" in each AnimationDef. Keys use the "groupName:elementIndex" format (e.g. "body:2"). The structure of an element track is identical to a group track (rotation, rotate, translation, scale, visible, spin).
  • AnimationDef — New getElementTracks() getter, hasElementTracks() helper, shortcut getElementTrack(String groupName, int elementIndex), and static utility elementTrackKey(String, int) to format the canonical key.
  • AnimationPose — New elementPoses map parallel to the existing groupPoses, with getElement(groupName, elementIndex) and getOrCreateElement(...). The methods reset(), copyFrom() and lerpToward() now include element poses.
  • AnimationController — The sampler iterates elementTracks after the group tracks and populates element poses via a factored helper populatePoseFromTrack(...). Animation-to-animation blending (START / END) also applies to element poses.
  • AnimatedBlockTESR / AnimatedEntityRenderer — Both renderers read the element pose and apply a local OpenGL matrix around each element's rotationOrigin before rendering its faces. Static JSON rotation is preserved and applied first; the animated pose is applied on top.
JSON — elementTracks example
{
  "formatVersion": 1,
  "modelId": "mymod:block/turret",
  "animations": {
    "idle": {
      "length": 40,
      "loop": true,
      "tracks": {
        "base": { }
      },
      "elementTracks": {
        "body:2": {
          "rotation": [
            { "tick": 0,  "value": [0, 0, 0] },
            { "tick": 20, "value": [0, 180, 0] },
            { "tick": 40, "value": [0, 360, 0] }
          ]
        }
      }
    }
  }
}
Java — Reading an element pose
AnimationPose pose = controller.getCurrentPose(partialTicks);
AnimationPose.GroupPose bodyPose = pose.getGroup("body");
AnimationPose.GroupPose elemPose = pose.getElement("body", 2);
if (elemPose != null && !elemPose.visible) return;

Bug fixes

  • No bug fixes in this release — additions only, 100% backward compatible.

v1.4.2 April 17, 2026

EriProjectile — custom projectile builder

New fluent builder for declaring custom projectiles (thrown entities) without Forge boilerplate. Supports velocity, direct damage and area-of-effect (AOE) damage, physics (gravity, drag), hit effects, and lifecycle callbacks. Projectiles are generated at runtime via GeneratedProjectile and allocated a slot in GeneratedProjectileSlots (pool of 32 static subclasses) — CleanRoom compatible, no ASM.

  • EriProjectile — Fluent builder: model(), texture(), velocity(), gravity(), drag(), damage(), aoe(), effectOnHit(), onHitBlock(), onHitEntity(), onTick(), register().
  • ProjectileDefinition — Configuration POJO containing all projectile parameters.
  • GeneratedProjectile / GeneratedProjectileSlots — Entities generated dynamically from the definition, each in its own slot to respect Forge's "one Class per EntityEntry" constraint.
  • ContentRegistry — Automatic registration of projectiles during the RegistryEvent.Register<EntityEntry> event.

Bug fixes

  • No bug fixes in this release — additions only.

v1.4.1 April 17, 2026

PathfinderBuilder — custom AI tasks for EriEntity

Fluent builder to configure an EriEntity's AI tasks beyond the presets (PASSIVE, HOSTILE_MELEE, ...). Lets you add fine-grained behaviors (swim, melee attack, wander, panic, open doors, leap at target, etc.) in a single chain. Tasks are stored as factories (Function<EntityLiving, EntityAIBase>) to defer instantiation until the entity exists.

  • APIEriEntity.ai(ai -> ai.swim().meleeAttack(1.0, false).wander(1.0).watchPlayers(12f).lookIdle().targetPlayers().hurtByTarget(true))
  • Goals availableswim, meleeAttack, wander, watchClosest, watchPlayers, lookIdle, panicOnHurt, leapAtTarget, avoidEntity, openDoors, task(...) (custom).
  • Target goals availabletargetPlayers, targetClass, hurtByTarget, targetTask(...) (custom).

Bug fixes

  • No bug fixes in this release — additions only.

v1.4.0 April 17, 2026

Full Entity Framework — stats, AI presets, drops, spawn rules, callbacks

Major extension of EriEntity (39 fluent methods) to declare a full entity in a single chain: stats (health/armor/damage/speed/range), sounds, AI preset (PASSIVE / HOSTILE_MELEE / HOSTILE_RANGED / NEUTRAL / BOSS), drops, spawn rules (biomes/light/height/dimension), and lifecycle callbacks (onSpawn, onDeath, onUpdate, onAttack, onHurt, onInteract).

  • EntityDefinition — Extended with all the new fields + inner class EntityDrop.
  • AiPreset / EriSpawner — New configuration types for behavior and spawning.
  • GeneratedEntity — Generic entity (extends EntityMob) that applies the definition: applyEntityAttributes, initEntityAI (switches on preset), dropLoot, getExperiencePoints, sounds. onSpawn uses an NBT-persisted flag to avoid re-fires on chunk reload.
  • GeneratedEntitySlots — Pool of 32 static subclasses (Slot0..Slot31) to respect Forge 1.12.2's "one Class per EntityEntry" constraint without ASM (CleanRoom compatible).
  • ContentRegistry.onRegisterEntities — Automatic registration (egg, tracker, spawn rules) and client-side wiring of AnimatedEntityRenderer.

Bug fixes

  • No bug fixes in this release — additions only.

v1.3.2 April 16, 2026

AnimatedItemController: animation playback for animated items

Items created with EriItem.animatedModel() can now play .erianim.json animations in real time. The new AnimatedItemController exposes a simple static API (play / stop / isPlaying) and shares playback state per modelId for every item using the same model.

  • AnimatedItemController — New client-only controller in fr.eri.eriapi.anim. Handles looping (EndBehavior LOOP), one-shot playback, and per-modelId state.
  • AnimatedBlockItemRenderer — The renderer now queries the controller every frame and feeds the pose to the TESR pipeline through a proxy TileEntity. No more static pose.
Java — Example
EriItem.create("mymod", "sword")
    .animatedModel("mymod:item/sword")
    .onRightClick(ctx -> {
        if (ctx.world.isRemote) {
            AnimatedItemController.play("mymod:item/sword", "swing");
        }
    })
    .register();

Bug fixes

  • Documentation animation.html — removed the “no animation playback” warning and replaced it with documentation for the new controller.

v1.3.1 April 16, 2026

EriItem: .animatedModel() support for items with Blockbench 3D rendering

Items created via EriItem can now use an animated 3D Blockbench model when held or displayed in inventory, using the new .animatedModel(String modelId) method. Rendering is delegated to the same AnimatedBlockItemRenderer used for animated blocks.

  • EriItem.animatedModel(String) — New fluent method that configures an item to use a Blockbench model in "modid:item/name" format.
  • ContentRegistry — Automatically wires the TEISR (TileEntityItemStackRenderer) on animated items client-side, and registers the builtin/entity model so Forge delegates rendering.
  • ItemDefinition — New animatedModelId field to store the model identifier.
Java — Example
EriItem.create("eriniumfaction", "faction_sword")
    .maxStackSize(1)
    .rarity(EnumRarity.EPIC)
    .animatedModel("eriniumfaction:item/faction_sword")
    .register();

Bug fixes

  • No bug fixes in this release — additions only.

v1.3.0 April 16, 2026

New: AnimatedEntityRenderer — animated rendering for entities

Extension of the Blockbench rendering pipeline to entities. The renderer uses exactly the same pipeline as AnimatedBlockTESR (groups, elements, faces, UV, animated textures).

  • AnimatedEntityRenderer<T>RenderLiving that renders a Blockbench model with animations. Static factory methods for registration via RenderingRegistry.
  • IAnimatedEntity — Interface that entities implement to provide their animation pose to the renderer. Methods getCurrentPose(partialTicks) and getAnimState().

New: EriEntity builder

Fluent builder to define custom entities with Blockbench model, textures, animations, hitbox, passenger seats, and spawn eggs. Definitions are stored in ContentRegistry for the mod to register in Forge manually.

  • EriEntity — Fluent builder: model(), texture(), animation(), hitbox(), seat() (3 overloads), spawnEgg(), creativeTab(), register().
  • EntityDefinition — Configuration POJO containing all entity parameters.
  • EntitySeat — Passenger seat definition with Blockbench position, yaw offset, attached group, and camera lock.
  • ContentRegistry.registerEntity() / getEntityDefs() — Storage and access for entity definitions.

Category field in AnimationFile

.erianim.json files now support an optional "category" field ("block", "item", or "entity"). This field is informational and indicates which renderer type expects this animation file. Backwards compatible — defaults to "block".

Bug fixes

  • No bug fixes in this release — additions only.

v1.2.1 March 26, 2026

Label: new scaleToFit() and wrapped() modes

  • scaleToFit(boolean) — Instead of truncating text with "...", automatically reduces the text scale so it fits exactly within the component width. Mutually exclusive with wrapped() (wrapped takes priority).
  • wrapped(boolean) — Text wraps to multiple lines at word boundaries. If the wrapped content exceeds the component height, vertical mouse scroll is enabled (scissor clipped). Mutually exclusive with scaleToFit() (wrapped wins).

Tooltip: literal \n support from .lang files

Tooltip.render() now normalizes literal \\n (from .lang files) to real newlines before splitting. Both \n in Java strings and \\n from .lang files now work.

Dropdown: deferred rendering (always on top)

  • The drop-down list is now rendered AFTER all other components (deferred rendering), ensuring it always appears on top.
  • Click handling works even when the Dropdown is inside a ScrollPanel.
  • New static methods: Dropdown.renderDeferredDropdown(), Dropdown.handleDeferredClick(), Dropdown.resetState().
  • EriGuiScreen calls these methods automatically — no action needed for standard screens.

ItemStackRenderer: separate showDurability() from showCount()

New method showDurability(boolean) independent from showCount(boolean). Allows rendering the durability bar without the count text, or vice versa. Both default to true.

Bug fixes

  • No bug fixes in this release — additions and improvements only.

v1.2.0 March 22, 2026

Overlay editor: auto-snap alignment system

The in-game overlay editor now features an auto-snap system. When dragging an overlay in edit mode, it automatically snaps to the edges and centers of other overlays as well as to screen reference points, enabling precise positioning without manually entering coordinates.

  • Overlay edge snapping — The left, right, top and bottom edges of an overlay snap to the corresponding edges of neighboring overlays when within 5 design pixels. 10 snap types are supported: edge-to-edge and center-to-center alignment on both axes.
  • Screen edge snapping — The overlay snaps to all four screen borders (x = 0, x = 1920, y = 0, y = 1080) when approaching within 5 design pixels.
  • Screen center snapping — The overlay can align to the absolute center of the screen (960, 540), both horizontally and vertically.
  • Center-to-center alignment — The horizontal and vertical centers of two overlays can align with each other for symmetric layouts.
  • Cyan guide lines — When a snap is active, a semi-transparent cyan guide line appears on the snap axis to visualize the alignment.
  • Red overlap indicator — If an overlay overlaps another during dragging, it turns red to signal the overlap.
  • Hold Shift to bypass snap — Hold Shift while dragging to disable snapping and move the overlay freely pixel by pixel.

Bug fixes

  • No bug fixes in this release — additions only.

v1.1.0 March 22, 2026

New module: Security Framework

Added the fr.eri.eriapi.security package — a complete server-side anti-duplication and exploit prevention toolkit. Designed for high-player-count servers (500-1000 players).

  • GuiRateLimiter — Per-player action throttling with configurable sliding window. Prevents container click spam.
  • ItemIntegrityValidator — Snapshot/validate/rollback system to verify that the total item count doesn't change after a container operation. Automatically detects duplication attempts.
  • ContainerLock — Synchronized per-key locks to prevent concurrent access to shared containers (e.g. faction chest).
  • DupeAlertManager — Real-time alerts sent to all online operators via EriChat when a duplication attempt is detected.

New documentation: Network GUI

Added the Network GUI page documenting the client-server communication system for GUI interactions: GuiNetworkHandler, IGuiDataReceiver, packet formats, and complete examples.

GUI documentation expanded

  • Added visual effect components: GradientRectangle, Aurora, Starfield, ParticleSystem, SmokeFog
  • Network section moved to the dedicated Network GUI page
  • Sidebar links updated with Security and Network

Bug fixes

  • No bug fixes in this release — additions only.

v1.0.0 Initial release

Included modules

  • GUI Framework — Complete UI toolkit in 1920x1080 design pixels with 30+ components, animations, sounds, and automatic scaling.
  • Overlay HUD — Draggable overlay system with design-pixel positioning, anchoring, and in-game editor.
  • Config System — Annotation-based configuration with auto-generated GUI, client-server sync, and validation.
  • Command Framework — Command builder with auto-completion, typed arguments, permissions, cooldowns, and sub-commands.
  • Content Builder — Create items, blocks, recipes and entities via fluent API without Forge boilerplate.
  • Data & Storage — Persistent storage via annotated POJOs with automatic client sync via @Sync.
  • Scheduler — Schedule delayed, repeating, chained, and async tasks.
  • Chat Builder — Rich chat messages with colors, clicks, hover, and pagination.
  • Capability Helpers — Simplified Forge capabilities via annotations with auto-attach and auto-sync.
  • Event Simplifier — One-line Forge event subscriptions with filters, priority, and expiration.
  • Keybinding Manager — Fluent keyboard shortcuts with combos, double-tap, hold, and contexts.
  • Network GUI — Client-server communication for GUI interactions.