Compatibility
Minecraft: Java Edition
Platforms
Supported environments
Tags
Creators
Details
EpicCoreAPI
This mod provides entity manipulation APIs and commands based on CoreMod (ITransformationService), Java Agent, and VarHandle technologies. Note that while method names may resemble vanilla logic, the underlying implementation is completely different. For example, the set health API can modify entities using custom health values (including but not limited to entity data, numeric fields, and hash tables); the remove API performs low-level Minecraft container cleanup; the set invulnerable API provides a more powerful implementation than vanilla creative mode invulnerability. Additionally, this mod unlocks vanilla attribute limits to Double.MAX_VALUE by default. You can disable this in the config file with "Unlock Attribute Limits" option.
This mod also provides an MCreator plugin for MCreator users to conveniently use the APIs in this mod.
Usage for Players
Use /eca command (requires OP permission level 2):
/eca setHealth <targets> <health>- Set entity health/eca setMaxHealth <targets> <maxHealth>- Set entity max health (reverse-calculates attribute base value)/eca setInvulnerable <targets> <true|false>- Set entity invulnerability/eca lockHealth <targets> true <value>- Lock entity health at specific value/eca lockHealth <targets> false- Unlock entity health/eca lockMaxHealth <targets> true <value>- Lock entity max health at specific value/eca lockMaxHealth <targets> false- Unlock entity max health/eca banHealing <targets> true [value]- Ban healing for entities (value optional, defaults to current health)/eca banHealing <targets> false- Unban healing for entities/eca kill <targets>- Kill entities/eca remove <targets> [reason]- Remove entities from world/eca memoryRemove <targets>- DANGER! Requires Attack Radical Logic config. Remove entities via LWJGL internal channel/eca teleport <targets> <x> <y> <z>- Teleport entities/eca lockLocation <targets> <true|false> [x y z]- Lock/unlock entity location/eca cleanBossBar <targets>- Clean up boss bars/eca allReturn <targets> <true|false>- DANGER! Requires Attack Radical Logic config. Enable/disable return transformation on all boolean and void methods of the target entity's mod/eca allReturn global <true|false>- DANGER! Enable/disable global AllReturn for all non-whitelisted mods/eca restore <targets> <true|false>- DANGER! Requires Attack Radical Logic config. Force/cancel an entity instance delegating its core lifecycle methods (getHealth, setHealth, hurt, die, etc.) to vanilla LivingEntity behaviour/eca banSpawn <targets> <seconds>- Ban spawning of selected entities' types for specified duration/eca banSpawn clear- Unban all spawns in current dimension/eca setForceLoading <targets> <true|false>- Enable/disable force chunk loading for entities/eca setInvulnerable show_all- Show all invulnerable entities/eca entityExtension get_registry- Show entity extension registry/eca entityExtension get_active- Show active entity extension types in current dimension/eca entityExtension get_current- Show the currently effective entity extension/eca entityExtension clear- Clear active entity extension table and all global effects in current dimension/eca entityExtension set_skybox <preset>- Set global skybox shader preset/eca setFilter <targets> true <type>- Apply a screen filter to players (type: sketch, spotlight, matrix, rain, desert, snow, toxic)/eca setFilter <targets> false- Remove all active filters from players/eca resurrection start- Start the resurrection daemon thread/eca resurrection stop- Stop the resurrection daemon thread/eca resurrection status- Show daemon thread state and revival/check counts/eca resurrection add <targets>- Add entities to resurrection tracking (auto-revived on death every poll cycle)/eca resurrection remove <targets>- Remove entities from resurrection tracking/eca resurrection list- List all tracked entities with container integrity status/eca resurrection check <target>- One-shot container integrity check for an entity/eca resurrection revive <target>- Manually force-revive a tracked entity immediately/eca resurrection interval <ms>- Set poll interval in milliseconds (100–10000; default 25)
Added new command selectors, resolved through ECA's own entity lookup:
@eca_e[...]- all entities@eca_p[...]- nearest player@eca_a[...]- all players@eca_r[...]- random player@eca_s[...]- command source entity (self)
Usage for Developers
Adding ECA as Dependency
Step 1: Add Modrinth Maven repository (build.gradle)
repositories {
maven { url = "https://api.modrinth.com/maven"; content { includeGroup "maven.modrinth" } }
}
Step 2: Add ECA dependency (build.gradle)
dependencies {
implementation fg.deobf("maven.modrinth:epic-core-api:VERSION")
}
Replace
VERSIONwith the version you need (e.g.1.1.2-fix-dev). Go to ECA Modrinth page to find available versions. Use the-devversion for development environments to avoid mixin remapping issues.
Step 3: Declare dependency (mods.toml)
[[dependencies.your_mod_id]]
modId="eca"
mandatory=true
versionRange="[1.1.2,)"
ordering="NONE"
side="BOTH"
API Reference
lockHealth(entity, value)- Lock entity health at specific value (for invincibility, heal negation, etc.)unlockHealth(entity)- Remove health lockgetLockedHealth(entity)- Get current health lock value (null if not locked)isHealthLocked(entity)- Check if entity health is lockedbanHealing(entity, value)- Ban healing for entity at specified value (entity cannot heal but can take damage)unbanHealing(entity)- Unban healing for entitygetHealBanValue(entity)- Get current heal ban value (null if not banned)isHealingBanned(entity)- Check if entity has healing bannedgetHealth(entity)- Get vanilla health via VarHandlesetHealth(entity, health)- Three-phase health modification: (1) write vanilla DATA_HEALTH_ID directly; (2) reflectively invoke the entity's own setter (set/modify/update + health/hp); (3) ASM bytecode dataflow analysis to locate and write the real health storage. Players skip phases 2–3.setMaxHealth(entity, maxHealth)- Set max health by reverse-calculating attribute base value from current modifierslockMaxHealth(entity, value)- Lock entity max health at specific value (enforced every tick)unlockMaxHealth(entity)- Unlock entity max healthgetLockedMaxHealth(entity)- Get current max health lock value (null if not locked)isMaxHealthLocked(entity)- Check if entity max health is lockedaddHealthWhitelistKeyword(keyword)- Add keyword to health modification whitelistremoveHealthWhitelistKeyword(keyword)- Remove keyword from health modification whitelistgetHealthWhitelistKeywords()- Get all health whitelist keywordsaddHealthBlacklistKeyword(keyword)- Add keyword to health modification blacklistremoveHealthBlacklistKeyword(keyword)- Remove keyword from health modification blacklistgetHealthBlacklistKeywords()- Get all health blacklist keywordskill(entity, damageSource)- Kill entity (loot + advancements + removal)revive(entity)- Clear death state and restore healthrevive(level, uuid)- Clear death state and restore health by UUID in specified levelreviveAllContainers(entity)- Revive all critical entity containers (tickList, lookup, sections, tracker)reviveAllContainers(level, uuid)- Revive all critical entity containers by UUID in specified levelteleport(entity, x, y, z)- Teleport via VarHandle with client synclockLocation(entity)- Lock entity location at current positionlockLocation(entity, position)- Lock entity location at specified positionunlockLocation(entity)- Unlock entity locationisLocationLocked(entity)- Check if entity location is lockedgetLockedLocation(entity)- Get locked position (null if not locked)remove(entity, reason)- Complete removal (AI, boss bars, containers, passengers)memoryRemove(entity, reason)- DANGER! Requires Attack Radical Logic config. Remove entity via LWJGL internal channelcleanupBossBar(entity)- Remove boss bars without removing entityisInvulnerable(entity)- Check if entity is invulnerable (ECA internal invulnerability logic)setInvulnerable(entity, invulnerable)- Set invulnerability (enable: revive + lock health + block damage + remove harmful effects per tick + prevent mob targeting + protect player inventory; disable: clear all protections)enableAllReturn(entity)- DANGER! Requires Attack Radical Logic config. Performs return transformation on all boolean and void methods of the target entity's modsetGlobalAllReturn(enable)- DANGER! Requires Attack Radical Logic config. Enable/disable global AllReturn for all non-whitelisted modsdisableAllReturn()- Disable AllReturn and clear targetsisAllReturnEnabled()- Check if AllReturn is enabledaddAllReturnWhitelist(prefix)- Add package prefix to AllReturn whitelist (skip AllReturn, defensive hooks still apply)removeAllReturnWhitelist(prefix)- Remove package prefix from AllReturn whitelist (built-in entries cannot be removed)addTransformWhitelist(prefix)- Add package prefix to transform whitelist (skip ALL ECA transformations including defensive hooks)removeTransformWhitelist(prefix)- Remove package prefix from transform whitelist (built-in entries cannot be removed)isAllReturnWhitelisted(className)- Check if a class is protected from AllReturnisTransformWhitelisted(className)- Check if a class is protected from all ECA transformationsgetAllWhitelistedPackages()- Get all whitelist prefixes (both levels, built-in + custom)addProtectedPackage(prefix)- Add a package prefix to the system-protected list (never transformed by ECA)removeProtectedPackage(prefix)- Remove a package prefix from the protected list (built-in entries cannot be removed)isPackageProtected(className)- Check if a class is system-protectedgetAllProtectedPackages()- Get all protected package prefixes (built-in + custom)restoreEntity(entity)- DANGER! Requires Attack Radical Logic config. Retransform the entity's custom class chain so this instance delegates getHealth/getMaxHealth/setHealth/hurt/die and other core lifecycle methods to vanilla LivingEntity behaviour (per-instance, reversible)unrestoreEntity(entity)- Cancel the class-restore, returning the entity to its custom implementationgetEntityExtensionRegistry()- Get all registered entity extensions (Map<EntityType, EntityExtension>)getActiveEntityExtensionTypes(level)- Get active entity extension types in current dimension (Map<EntityType, Integer>)getActiveEntityExtension(level)- Get the currently effective entity extension (highest priority)clearActiveEntityExtensionTable(level)- Clear active entity extension table in current dimensionsetGlobalFog(level, fogData)- Set global fog effect override for a dimension (does not change effect priority)clearGlobalFog(level)- Clear global fog effect overridesetGlobalSkybox(level, skyboxData)- Set global skybox effect override for a dimension (does not change effect priority)clearGlobalSkybox(level)- Clear global skybox effect overridesetGlobalMusic(level, musicData)- Set global combat music effect override for a dimension (does not change effect priority)clearGlobalMusic(level)- Clear global combat music effect overrideclearAllGlobalEffects(level)- Clear all global effect overrides (fog, skybox, music) for a dimensionenableFilter(player, filterType)- Apply a screen filter to a player (FilterType: SKETCH, SPOTLIGHT, MATRIX, RAIN, DESERT, SNOW, TOXIC, COSMOS)disableFilter(player, filterType)- Remove a screen filter from a playerisFilterEnabled(player, filterType)- Check whether a filter is active on a playergetActiveFilters(player)- Get a player's active filters (unmodifiable Set<FilterType>)playBossShow(viewer, target, cutsceneId)- Force-play a BossShow cutscene for a viewer (ignores watch history)playBossShowIfNew(viewer, target, cutsceneId)- Play a BossShow cutscene only if the viewer hasn't seen it beforestopBossShow(viewer)- Stop the viewer's current BossShow cutsceneisBossShowPlaying(viewer)- Check whether the viewer is currently in a BossShow cutscenelaunchBossShowEvent(eventName, viewer, target)- Trigger all Custom-trigger BossShows matching the event name (returns count launched)banSpawn(level, entityType, seconds)- Ban entity type from spawning for specified durationisSpawnBanned(level, entityType)- Check if entity type is banned from spawninggetSpawnBanTime(level, entityType)- Get remaining spawn ban time in secondsunbanSpawn(level, entityType)- Unban entity type, allowing it to spawn againgetAllSpawnBans(level)- Get all spawn bans in level (Map<EntityType, Integer>)unbanAllSpawns(level)- Unban all entity types in levelsetForceLoading(entity, level, forceLoad)- Enable/disable force chunk loading for entityisForceLoaded(entity)- Check if entity is force loaded (via EntityExtension or API)getEntity(level, entityId)- Resolve entity by runtime id in specified level (ECA selector path)getEntity(level, uuid)- Resolve entity by UUID in specified level (ECA selector path)getEntity(level, entityId, entityClass)- Resolve typed entity by idgetEntity(level, uuid, entityClass)- Resolve typed entity by UUIDgetEntity(server, entityId)- Resolve entity by id across all levelsgetEntity(server, uuid)- Resolve entity by UUID across all levelsgetEntities(level)- Get all entities in levelgetEntities(level, area)- Get entities in AABB areagetEntities(level, filter)- Get entities using custom predicategetEntities(level, area, filter)- Get entities in area using custom predicategetEntities(level, entityClass)- Get all entities of specified type in levelgetEntities(level, area, entityClass)- Get entities of specified type in areagetEntities(server)- Get all entities across all server levelsgetEntities(server, filter)- Get entities across all levels using custom predicate
import net.eca.api.EcaAPI;
import net.eca.network.EntityExtensionOverridePacket.FogData;
import net.eca.network.EntityExtensionOverridePacket.MusicData;
import net.eca.network.EntityExtensionOverridePacket.SkyboxData;
import net.eca.util.entity_extension.EntityExtension;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.Map;
import java.util.Set;
// Health Lock
EcaAPI.lockHealth(entity, 20.0f);
Float locked = EcaAPI.getLockedHealth(entity);
EcaAPI.unlockHealth(entity);
// Heal Ban
EcaAPI.banHealing(entity, entity.getHealth()); // Ban healing at current health
Float banValue = EcaAPI.getHealBanValue(entity);
EcaAPI.unbanHealing(entity);
// Basic Health Access
float realHealth = EcaAPI.getHealth(entity);
EcaAPI.setHealth(entity, 50.0f);
// Max Health
EcaAPI.setMaxHealth(entity, 1024.0f);
EcaAPI.lockMaxHealth(entity, 1024.0f);
Float lockedMax = EcaAPI.getLockedMaxHealth(entity);
EcaAPI.unlockMaxHealth(entity);
// Keyword Management
EcaAPI.addHealthWhitelistKeyword("mana");
EcaAPI.addHealthBlacklistKeyword("timer");
Set<String> whitelist = EcaAPI.getHealthWhitelistKeywords();
Set<String> blacklist = EcaAPI.getHealthBlacklistKeywords();
EcaAPI.removeHealthWhitelistKeyword("mana");
EcaAPI.removeHealthBlacklistKeyword("timer");
// Entity Control
EcaAPI.kill(entity, damageSource);
EcaAPI.revive(entity);
EcaAPI.revive(serverLevel, uuid); // Revive by UUID
Map<String, Boolean> containerResults = EcaAPI.reviveAllContainers(entity); // Revive all containers
EcaAPI.reviveAllContainers(serverLevel, uuid); // Revive all containers by UUID
EcaAPI.teleport(entity, x, y, z);
EcaAPI.lockLocation(entity); // Lock at current position
EcaAPI.lockLocation(entity, new Vec3(100, 64, 200)); // Lock at specified position
boolean locationLocked = EcaAPI.isLocationLocked(entity);
Vec3 lockedPos = EcaAPI.getLockedLocation(entity);
EcaAPI.unlockLocation(entity);
EcaAPI.remove(entity, Entity.RemovalReason.KILLED);
EcaAPI.memoryRemove(entity, Entity.RemovalReason.CHANGED_DIMENSION); // Remove using LWJGL internal Unsafe instance
EcaAPI.cleanupBossBar(entity);
// ECA Entity Selector API
Entity byId = EcaAPI.getEntity(level, 123);
Entity byUuid = EcaAPI.getEntity(level, uuid);
List<Entity> allInLevel = EcaAPI.getEntities(level);
List<Entity> inArea = EcaAPI.getEntities(level, new AABB(0, 0, 0, 16, 256, 16));
List<Entity> filtered = EcaAPI.getEntities(level, e -> e.getType() == EntityType.ZOMBIE);
List<LivingEntity> livingInArea = EcaAPI.getEntities(level, new AABB(0, 0, 0, 32, 256, 32), LivingEntity.class);
List<Entity> allServerEntities = EcaAPI.getEntities(server);
// Invulnerability
EcaAPI.setInvulnerable(entity, true);
boolean isInv = EcaAPI.isInvulnerable(entity);
EcaAPI.setInvulnerable(entity, false);
// AllReturn (DANGER! Requires Attack Radical Logic config)
EcaAPI.enableAllReturn(entity); // Enable for entity's mod
EcaAPI.setGlobalAllReturn(true); // Enable for ALL non-whitelisted mods
boolean enabled = EcaAPI.isAllReturnEnabled();
EcaAPI.disableAllReturn(); // Disable and clear all AllReturn
// Whitelist — AllReturn level (skip AllReturn only, defensive hooks still apply)
EcaAPI.addAllReturnWhitelist("com.yourmod.");
boolean removed = EcaAPI.removeAllReturnWhitelist("com.yourmod.");
boolean isProtected = EcaAPI.isAllReturnWhitelisted("com.yourmod.YourClass");
// Whitelist — Transform level (skip ALL ECA transformations including defensive hooks)
EcaAPI.addTransformWhitelist("com.yourmod.");
boolean removedTransform = EcaAPI.removeTransformWhitelist("com.yourmod.");
boolean isFullyProtected = EcaAPI.isTransformWhitelisted("com.yourmod.YourClass");
Set<String> allWhitelisted = EcaAPI.getAllWhitelistedPackages();
// Spawn Ban
EcaAPI.banSpawn(serverLevel, EntityType.ZOMBIE, 300); // Ban zombies for 5 minutes
boolean banned = EcaAPI.isSpawnBanned(serverLevel, EntityType.ZOMBIE);
int remaining = EcaAPI.getSpawnBanTime(serverLevel, EntityType.ZOMBIE);
EcaAPI.unbanSpawn(serverLevel, EntityType.ZOMBIE);
Map<EntityType<?>, Integer> allBans = EcaAPI.getAllSpawnBans(serverLevel);
EcaAPI.unbanAllSpawns(serverLevel);
// Force Loading
EcaAPI.setForceLoading(livingEntity, serverLevel, true);
boolean forceLoaded = EcaAPI.isForceLoaded(livingEntity);
EcaAPI.setForceLoading(livingEntity, serverLevel, false);
// Entity Extension
Map<EntityType<?>, EntityExtension> registry = EcaAPI.getEntityExtensionRegistry();
Map<EntityType<?>, Integer> activeTypes = EcaAPI.getActiveEntityExtensionTypes(serverLevel);
EntityExtension active = EcaAPI.getActiveEntityExtension(serverLevel);
EcaAPI.clearActiveEntityExtensionTable(serverLevel);
// Global Effect Override (directly override fog/skybox/music without entity extension, does not change effect priority)
EcaAPI.setGlobalFog(serverLevel, new FogData(true, 8.0f, 0.0f, 0.0f, 0.0f, 0.02f, 0.25f, 0.0f, 1.0f, 0));
EcaAPI.clearGlobalFog(serverLevel);
EcaAPI.setGlobalSkybox(serverLevel, new SkyboxData(false, null, true, new ResourceLocation("eca", "the_last_end"), 1.0f, 100.0f, 1.0f, 1.0f, 1.0f, 1.0f));
EcaAPI.clearGlobalSkybox(serverLevel);
EcaAPI.setGlobalMusic(serverLevel, new MusicData(new ResourceLocation("your_mod", "music.boss"), 0, 1.0f, 1.0f, true, true));
EcaAPI.clearGlobalMusic(serverLevel);
EcaAPI.clearAllGlobalEffects(serverLevel);
Resurrection
Runs a dedicated daemon thread outside the Forge event bus and MC tick loop, continuously watching a set of tracked entities and auto-reviving any that die or lose container registration. Gives a stronger liveness guarantee than a one-shot reviveAllContainers call.
Commands (OP level 2):
/eca resurrection start— Start the daemon thread/eca resurrection stop— Stop the daemon thread/eca resurrection status— Show thread state + total revival / check counts/eca resurrection add <targets>— Add entities to tracking/eca resurrection remove <targets>— Remove entities from tracking/eca resurrection list— List tracked entities with per-container status/eca resurrection check <target>— One-shot container integrity check (does not revive)/eca resurrection revive <target>— Manually force-revive a tracked entity right now/eca resurrection interval <ms>— Set poll interval (100–10000 ms; default 25 ms)
Direct API (not exposed through EcaAPI; import net.eca.util.ResurrectionManager):
ResurrectionManager.start(); // start daemon (idempotent)
ResurrectionManager.add(entity); // begin tracking
ResurrectionManager.remove(entity); // stop tracking
boolean tracked = ResurrectionManager.isTracked(uuid);
ResurrectionManager.setPollIntervalMs(50); // adjust poll frequency
ResurrectionManager.stop(); // stop daemon
The daemon thread runs at slightly below normal priority. Default poll interval is 25 ms, meaning a tracked entity is revived within ~25 ms of death. Do not use on entities that spawn in large numbers.
Entity Extensions
Attach special effects to a specific entity type by extending EntityExtension and annotating the subclass with @RegisterEntityExtension.
Quick start example:
@RegisterEntityExtension
public class MyBossExtension extends EntityExtension {
static {
EntityExtensionManager.register(new MyBossExtension());
}
public MyBossExtension() {
super(EntityType.WITHER, 8); // entity type + priority (in a dimension, some global extension effects like fog, skybox, combat music only apply to the entity extension with the highest priority among existing entities)
}
@Override
public boolean enableForceLoading() {
return true; // mark this entity type as force-loaded, avoid using on entities that spawn in large numbers to prevent lag
}
@Override
protected String getModId() {
return "your_mod_id"; // your mod id, used for all resource path resolution (textures, sounds, etc.)
}
@Override
public boolean enableBossBar() {
return true; // master switch for boss bar takeover: whether ECA clears the entity's native boss bar and replaces it with ECA's. Default false — when not enabled, ECA never touches the entity's own boss bar. Must be true for bossBarExtension() / shouldShowBossBar() / custom health override to have any effect
}
@Override
public boolean shouldShowBossBar(LivingEntity entity) {
return entity != null && entity.isAlive(); // boss bar display condition (only effective when enableBossBar() is true)
}
@Override
public boolean enableCustomHealthOverride() {
return true; // if true, ECA custom boss bar current health will be read from getCustomHealthValue() instead of vanilla getHealth()
}
@Override
public Number getCustomHealthValue(LivingEntity entity) {
return entity.getEntityData().get(YOUR_CUSTOM_HEALTH_DATA); // the actual value to use as current health (e.g. entity data, custom field), null = fallback to vanilla
}
@Override
public boolean enableCustomMaxHealthOverride() {
return true; // if true, ECA custom boss bar max health will be read from getCustomMaxHealthValue() instead of vanilla getMaxHealth()
}
@Override
public Number getCustomMaxHealthValue(LivingEntity entity) {
return entity.getEntityData().get(YOUR_CUSTOM_MAX_HEALTH_DATA); // the actual value to use as max health (e.g. entity data, custom field), null = fallback to vanilla
}
// Custom boss health bar (requires enableBossBar() = true)
@Override
public BossBarExtension bossBarExtension() {
return new BossBarExtension() {
@Override public boolean enabled() { return true; } // enable boss bar
@Override public ResourceLocation getFrameTexture() { return texture("boss/frame.png"); } // frame texture (null to skip). If both texture and RenderType are set, shader renders masked by texture alpha
@Override public ResourceLocation getFillTexture() { return texture("boss/fill.png"); } // fill texture (null to skip)
@Override public RenderType getFrameRenderType() { return CustomRenderTypes.BOSS_BAR; } // frame shader/render type (null to skip)
@Override public RenderType getFillRenderType() { return CustomRenderTypes.BOSS_BAR; } // fill shader/render type (null to skip), can use a different preset
@Override public int getFrameWidth() { return 420; } // frame pixel width (RenderType-only mode requires this, texture mode auto-detects)
@Override public int getFrameHeight() { return 40; } // frame pixel height
@Override public int getFillWidth() { return 400; } // fill pixel width (RenderType-only mode requires this, texture mode auto-detects)
@Override public int getFillHeight() { return 30; } // fill pixel height
@Override public int getFrameOffsetX() { return 0; } // frame X offset
@Override public int getFrameOffsetY() { return -10; } // frame Y offset
@Override public int getFillOffsetX() { return 0; } // fill X offset
@Override public int getFillOffsetY() { return 0; } // fill Y offset
};
}
// Entity extra render layer
@Override
public EntityLayerExtension entityLayerExtension() {
return new EntityLayerExtension() {
@Override public boolean enabled() { return true; } // enable render layer
@Override public RenderType getRenderType() { return CustomRenderTypes.BOSS_LAYER; } // render layer shader/render type
@Override public boolean isGlow() { return true; } // extra render layer glowing
@Override public boolean isHurtOverlay() { return true; } // show hurt overlay effect on this layer
@Override public float getAlpha() { return 0.8f; } // render layer transparency (0.0 ~ 1.0)
};
}
// Global fog
@Override
public GlobalFogExtension globalFogExtension() {
return new GlobalFogExtension() {
@Override public boolean enabled() { return true; } // enable fog
@Override public boolean globalMode() { return true; } // global mode (ignore radius, always active in dimension)
@Override public float radius() { return 8.0f; } // fog activation radius around entity
@Override public int fogColor() { return 0x000000; } // fog color as packed RGB int (e.g. 0xFF0000 = red, 0x800080 = purple, 0x000000 = black). Override fogRed/Green/Blue() instead to mix your own color.
@Override public float terrainFogStart(float renderDistance) { return renderDistance * 0.02f; } // terrain fog start distance
@Override public float terrainFogEnd(float renderDistance) { return renderDistance * 0.25f; } // terrain fog end distance
@Override public float skyFogStart(float renderDistance) { return 0.0f; } // sky fog start distance
@Override public float skyFogEnd(float renderDistance) { return renderDistance; } // sky fog end distance
@Override public FogShape fogShape() { return FogShape.SPHERE; } // fog shape (SPHERE or CYLINDER)
};
}
// Global custom skybox
@Override
public GlobalSkyboxExtension globalSkyboxExtension() {
return new GlobalSkyboxExtension() {
@Override public boolean enabled() { return true; } // enable skybox
@Override public boolean enableTexture() { return true; } // enable texture-based skybox rendering
@Override public ResourceLocation texture() { return texture("sky/skybox.png"); } // skybox texture resource location
@Override public boolean enableShader() { return true; } // enable shader-based skybox rendering
@Override public RenderType shaderRenderType() { return CustomRenderTypes.SKYBOX; } // skybox shader/render type
@Override public float alpha() { return 0.9f; } // skybox transparency (0.0 ~ 1.0)
@Override public float size() { return 100.0f; } // skybox quad size
@Override public float textureUvScale() { return 16.0f; } // texture UV scale
@Override public float textureRed() { return 1.0f; } // texture color red (0.0 ~ 1.0)
@Override public float textureGreen() { return 1.0f; } // texture color green (0.0 ~ 1.0)
@Override public float textureBlue() { return 1.0f; } // texture color blue (0.0 ~ 1.0)
};
}
// Global combat music
@Override
public CombatMusicExtension combatMusicExtension() {
return new CombatMusicExtension() {
@Override public boolean enabled() { return true; } // enable combat music
@Override public ResourceLocation soundEventId() { return sound("music.boss_battle"); } // sound event id (must be registered in sounds.json)
@Override public SoundSource soundSource() { return SoundSource.MUSIC; } // sound category
@Override public float volume() { return 1.0f; } // playback volume (0.0 ~ 1.0)
@Override public float pitch() { return 1.0f; } // playback pitch
@Override public boolean loop() { return true; } // loop playback
@Override public boolean strictMusicLock() { return true; } // block all other MUSIC sounds while active
};
}
// Conditional gate: fog/skybox/music activate only when the matching shouldEnableXxx returns true; re-checked ~once/sec against the dimension's primary entity
@Override
public boolean shouldEnableFog(LivingEntity entity) {
return entity.getHealth() < entity.getMaxHealth() * 0.5f; // example: fog activates when entity is below 50% health
}
@Override
public boolean shouldEnableSkybox(LivingEntity entity) {
return true; // skybox activation condition per entity instance
}
@Override
public boolean shouldEnableMusic(LivingEntity entity) {
return true; // combat music activation condition per entity instance
}
/*
* Conditional switching — override the entity-aware overload (on all five extension methods: bossBar / entityLayer / globalFog /
* globalSkybox / combatMusic) to return a different extension object based on entity state. ECA invokes the entity-aware overload only
* with a non-null entity (other cases use the no-arg variant) and catches any exception it throws, falling back to the no-arg variant.
* Global effects re-evaluate ~once/sec against the dimension's primary entity; instance effects (boss bar/render layer) re-evaluate per frame.
*/
@Override
public GlobalSkyboxExtension globalSkyboxExtension(LivingEntity entity) {
if (entity.getHealth() < entity.getMaxHealth() * 0.5f) {
return phase2Skybox; // below 50% health: switch to another GlobalSkyboxExtension you defined
}
return globalSkyboxExtension(); // default: the no-arg skybox above
}
}
Item Extensions
Add shader rendering effects to a specific item by extending ItemExtension and annotating the subclass with @RegisterItemExtension. Item extensions are client-only — no network synchronization is needed.
import net.eca.api.RegisterItemExtension;
import net.eca.client.render.StarlightRenderTypes;
import net.eca.util.item_extension.ItemExtension;
import net.eca.util.item_extension.ItemExtensionManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
@RegisterItemExtension
public class DiamondSwordExtension extends ItemExtension {
static {
ItemExtensionManager.register(new DiamondSwordExtension());
}
public DiamondSwordExtension() {
super(Items.DIAMOND_SWORD); // target item
}
@Override
protected String getModId() {
return "your_mod_id";
}
@Override
public boolean enabled() {
return true; // global master switch — return false to disable this extension entirely
}
@Override
public boolean shouldRender(ItemStack stack) {
return true; // per-stack activation condition (e.g. check NBT, enchantment, custom name)
}
@Override
public RenderType getRenderType() {
return StarlightRenderTypes.ITEM; // shader overlay RenderType
}
@Override
public float[] getColorKey() {
/*
* Only pixels matching this RGB (0.0~1.0) will be overlaid with the shader.
* Return null to apply the shader to the entire item texture.
*/
return new float[]{0.25f, 0.83f, 0.73f};
}
@Override
public float getColorKeyTolerance() {
return 0.3f; // RGB distance tolerance, 0.0~1.0
}
}
Activation condition switches:
enabled()— global master switch. Returnfalseto completely disable this extension without removing the registration.shouldRender(ItemStack stack)— per-stack runtime check. Called every frame for every rendered stack, so you can gate the effect on NBT, enchantments, custom names, durability, etc.
Notes:
- Each
Itemcan only have ONE extension. Duplicate registrations are rejected with an error log. - The shader is rendered as an additional overlay pass on top of normal item rendering (works in GUI, first-person, third-person, ground item, and item frames).
- Color-Key masking uses local UV bounds automatically, so centered shader math (spiral, stars, rings) stays centered on the item geometry regardless of where the texture lives on the block atlas.
Shader Presets
ECA ships several built-in shader presets for both entity and item extensions. Just replace CustomRenderTypes in the examples above with a preset name. Each preset provides 4 RenderTypes: BOSS_BAR, BOSS_LAYER, SKYBOX for entity extensions, and ITEM for item extensions.
Available presets:
TheLastEndRenderTypes— The Last EndDreamSakuraRenderTypes— Dream SakuraForestRenderTypes— ForestOceanRenderTypes— OceanStormRenderTypes— StormVolcanoRenderTypes— VolcanoArcaneRenderTypes— ArcaneAuroraRenderTypes— AuroraHackerRenderTypes— HackerStarlightRenderTypes— StarlightCosmosRenderTypes— CosmosBlackHoleRenderTypes— Black Hole
Screen Filters
A set of full-screen post-processing filters the server can apply per player, either by command or through the API. A filter is synced to the client and rendered as a shader pass over the level. Each player can have only one filter active at a time — applying a new one replaces the current one — and a player's filter is cleared automatically on logout.
Filter types (net.eca.util.filter.FilterType):
SKETCH— sketchSPOTLIGHT— spotlightMATRIX— matrixRAIN— rainDESERT— desertSNOW— snowTOXIC— toxic
Apply a filter with /eca setFilter <targets> true <type> or EcaAPI.enableFilter(player, FilterType.SKETCH); clear filters with /eca setFilter <targets> false or EcaAPI.disableFilter(player, type).
BossShow Cinematics
BossShow plays a cutscene that locks the player's camera onto a pre-recorded path around a target entity, with subtitles and server-side event callbacks. Camera paths are recorded with the built-in in-game editor — you don't need to write keyframes by hand.
Commands (OP level 2):
/eca bossShow edit— Open the in-game editor (switches to spectator mode;/eca bossShow exitto restore)/eca bossShow list— List all loaded cutscenes/eca bossShow play <viewer> <target> <id>— Force-play a cutscene/eca bossShow stop <viewer>— Stop the current cutscene/eca bossShow reload— Reload all JSON definitions from disk/eca bossShow clearHistory <player>— Clear "already seen" records for a player
Editor Keybindings (rebindable under Controls → EpicCoreAPI):
| Key | Action |
|---|---|
J |
Start / resume recording |
I |
Pause recording |
K |
Mark current frame as keyframe |
ENTER |
Save recording |
ESC |
Discard recording |
Editor Workflow:
- Run
/eca bossShow editnear at least one LivingEntity (within 64 blocks). - In the Home GUI, click + New cutscene from entity → aim at an entity → right-click to select it as the camera anchor. Or click Edit on an existing cutscene.
- Configure trigger type (Range / Custom), target entity type, cinematic bars, allow repeat, etc.
- Click ● Record, press
Jto start. Move the camera freely in spectator mode — each tick is captured as a frame. PressKto mark the current frame as a keyframe at any point. - Edit each keyframe's
event_id(triggers server-side Java callbacks),subtitle(displayed text), andcurve(easing from this keyframe to the next). - Press
ENTERto save,ESCto discard. Saved files go toconfig/eca/bossshow/<namespace>/<path>.json.
Timeline editing (after recording, in the editor GUI): the bottom timeline bar shows the playhead, keyframe ticks, and the in/out range. Click or drag the bar to scrub — the camera previews that frame in first person. Use Set In / Set Out to mark a range, then Copy / Cut / Delete to operate on it, and Paste to insert the clipboard at the playhead. All edits ripple: frames after the cut shift to close the gap, since a frame's array index is its tick.
For Mod Developers
Two ways to define a cutscene:
-
JSON only — place a file at
data/<modid>/bossshow/<path>.json. Loaded automatically on startup. No Java code needed if you don't need server-side event handling. -
Java + JSON — extend
BossShowand annotate with@RegisterBossShowto get server-side event callbacks during playback.
JSON example — frames are generated by the recorder; you typically only hand-edit the keyframe sub-objects:
{
"target_type": "minecraft:warden",
"trigger": { "type": "range", "effect_radius": 32.0 },
"cinematic": true,
"allow_repeat": false,
"anchor_yaw": 0.0,
"frames": [
{ "dx": 0.0, "dy": 1.8, "dz": -6.0, "yaw": 0.0, "pitch": 10.0 },
{ "dx": 0.0, "dy": 1.8, "dz": -5.8, "yaw": 2.0, "pitch": 10.0,
"keyframe": { "event_id": "intro", "subtitle": "mymod.bossshow.warden.intro", "curve": "ease_in_out" } },
{ "dx": 0.0, "dy": 1.8, "dz": -4.0, "yaw": 8.0, "pitch": 10.0,
"keyframe": { "event_id": "finisher", "curve": "step" } }
]
}
frames: one object per tick, in playback order. A frame's index in the array is its tick — there is no separate time field. Generated by the editor.frames[].dx/dy/dz: camera offset in anchor-local coordinates.frames[].yaw/pitch: camera orientation (yaw is anchor-local).frames[].keyframe: optional. Its presence marks this frame as a keyframe (an empty object{}is a valid bare keyframe). Fields inside:event_id: delivered toBossShow.onKeyframeEvent()on the server. Optional.subtitle: shown on the viewer's screen. Plain text or a translation key (see subtitle override below). Optional.curve: playback easing from this keyframe to the next keyframe —none(default),ease_in,ease_out,ease_in_out,ease_out_in,step,bezier. Only affects camera interpolation speed, not event timing.
trigger:{"type":"range","effect_radius":N}auto-triggers when a player enters range of a matching entity.{"type":"custom","event_name":"..."}only fires viaEcaAPI.launchBossShowEvent(...).
The old
samples+markersformat is no longer recognized — files using it load as zero-frame cutscenes. Re-record or migrate toframes.
Event handler example — the event_id strings in the JSON above are dispatched to onKeyframeEvent on the server at the corresponding tick:
@RegisterBossShow
public class WardenIntroShow extends BossShow {
public static final ResourceLocation ID = new ResourceLocation("mymod", "warden_intro");
static { BossShowManager.register(new WardenIntroShow()); }
public WardenIntroShow() { super(ID, EntityType.WARDEN); }
@Override
public void onKeyframeEvent(String eventId, BossShowContext ctx) {
LivingEntity target = ctx.target();
ServerPlayer viewer = ctx.viewer();
if (target == null || !target.isAlive()) return;
switch (eventId) {
case "intro" -> target.level().playSound(null, target.blockPosition(),
SoundEvents.WARDEN_ROAR, SoundSource.HOSTILE, 2f, 1f);
case "phase2" -> EcaAPI.setInvulnerable(target, true);
case "finisher" -> EcaAPI.kill(target, target.level().damageSources().generic());
}
}
@Override
public void onStart(BossShowContext ctx) {
// called when playback begins
}
@Override
public void onEnd(BossShowContext ctx, boolean skipped) {
// called when playback ends; skipped = true if the viewer pressed ESC
}
}
Triggering from code:
EcaAPI.playBossShow(viewer, target, cutsceneId); // force play (ignores history)
EcaAPI.playBossShowIfNew(viewer, target, cutsceneId); // only if viewer hasn't seen it
EcaAPI.launchBossShowEvent("phase2", viewer, target); // match all Custom triggers with this event name
EcaAPI.stopBossShow(viewer); // stop current cutscene
EcaAPI.isBossShowPlaying(viewer); // check if viewer is in a cutscene
If a
@RegisterBossShowclass has no matching JSON on first launch, an empty template JSON is auto-generated atconfig/eca/bossshow/<namespace>/<path>.json.
For Modpack Developers
- Override cutscenes — place your modified JSON at
config/eca/bossshow/<namespace>/<path>.json. Config files override mod-bundled definitions (data/<modid>/bossshow/) with the same id. - Edit in-game —
/eca bossShow editlets you re-record camera paths, adjust triggers, or re-time keyframes (split / copy / cut / delete / paste frame ranges on the timeline). Saves go toconfig/eca/bossshow/, leaving the mod jar untouched. - Translate or rewrite subtitles — create
config/eca/bossshow/lang/<locale>.json(e.g.en_us.json,zh_cn.json). These take priority over the mod's own lang files for subtitle keys:{ "mymod.bossshow.warden.intro": "A sound echoes from the deep..." } - Hot reload —
/eca bossShow reloadpicks up all JSON changes without restarting.
ECA Transformer Whitelist
Although I've added as many common libraries and mods to the ECA Transformer whitelist as possible, there may still be mods that crash due to ECA transformation. So I've prepared a JSON configuration for modpack developers to add package prefixes to the whitelist. You can add JSON files under the config/eca/ folder. If the folder is empty on first launch, example files will be generated automatically.
Only the type and packages fields are required, other fields are ignored:
Single mod example (allreturn — skip AllReturn only, defensive hooks still apply):
{
"type": "allreturn",
"packages": [
"com.example.yourmod."
]
}
Multiple mods example (transform — skip ALL ECA transformations):
{
"type": "transform",
"packages": [
"com.example.modA.",
"com.example.modB.",
"net.example.modC."
]
}
Any .json filename works, and you can have multiple files.


