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.
The original intent of this mod is to provide developers with simplified entity manipulation APIs while achieving a certain level of strength under the premise of ensuring performance and compatibility. Therefore, please do not use this mod for mod power comparisons or endless code arms races. Additionally, in modpack survival environments, it is best to ensure that the Attack and Defence Radical Logic config options are disabled.
This mod also provides an MCreator plugin for MCreator users to conveniently use the APIs in this mod.
Usage for Players
Players can use the following /eca commands (requires 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 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, cosmos)/eca setFilter <targets> false- Remove all active filters from players/eca bossShow edit- Open the in-game cinematic editor (switches to spectator mode)/eca bossShow exit- Exit the editor and restore the previous game mode/eca bossShow list- List all loaded cutscenes/eca bossShow play <viewer> <target> <id>- Force-play a cutscene/eca bossShow stop <viewer>- Stop the viewer's current cutscene/eca bossShow reload- Reload all cutscene JSON definitions from disk/eca bossShow clearHistory <player>- Clear a player's "already seen" records/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.5-fix-fix). Go to ECA Modrinth page to find available versions.
Step 3: Declare dependency (mods.toml)
[[dependencies.your_mod_id]]
modId="eca"
mandatory=true
versionRange="[1.1.5,)"
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)- Staged health modification that escalates only when a verify step fails: Vanilla (write vanilla DATA_HEALTH_ID directly) → Symbolic (ASM bytecode dataflow analysis of getHealth() to locate and invert the real storage, with numeric fallback on a located storage) → Probe (behavioral probing of candidate numeric setters, name-agnostic) → Dynamic (runtime bytecode instrumentation, requires Attack Radical Logic config). Players only run the Vanilla stage. Each stage is verified againstgetHealth()withinmax(1.0, abs(target) * 2%); the first to pass is cached per entity class.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)getEntityExtensionRegistry()- 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 predicatestartResurrection()- Start the resurrection daemon thread (idempotent)stopResurrection()- Stop the resurrection daemon threadisResurrectionRunning()- Check whether the daemon is runningaddResurrectionTarget(entity)- Add an entity to the resurrection tracking setremoveResurrectionTarget(entity)- Remove an entity from the resurrection tracking setisResurrectionTracked(entity)- Check whether an entity is tracked for resurrectiongetResurrectionTrackedCount()- Get the number of currently tracked entitiesclearAllResurrectionTargets()- Remove all entities from the tracking setsetResurrectionPollInterval(ms)- Set the daemon poll interval (ms, clamped 1–10000, default 25)getResurrectionPollInterval()- Get the current poll interval in msgetResurrectionTotalRevived()- Get the total number of entities revived since startgetResurrectionTotalChecks()- Get the total number of entity checks performed since startcheckResurrectionTarget(level, entity)- Perform a one-shot container integrity checkreviveResurrectionTarget(level, entity)- Manually force-revive a tracked entity immediately
Here is a simple example:
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
EcaAPI.startResurrection();
EcaAPI.addResurrectionTarget(entity);
boolean tracked = EcaAPI.isResurrectionTracked(entity);
EcaAPI.removeResurrectionTarget(entity);
EcaAPI.setResurrectionPollInterval(50);
EcaAPI.stopResurrection();
Entity Extensions
This mod also provides a customizable entity type extension feature for adding special visual effects to your entities. You need to create a subclass extending EntityExtension and annotate it with @RegisterEntityExtension to register the extension. Here is a 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
@Override public float getAlpha() { return 1.0f; } // overall boss bar opacity, 0.0~1.0 (default 1.0)
};
}
// 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
You can create item extensions to add shader rendering effects to specific items: create a subclass extending ItemExtension and annotate it with @RegisterItemExtension to register.
import net.eca.api.RegisterItemExtension;
import net.eca.client.render.StarlightRenderTypes;
import net.eca.util.ItemUtil;
import net.eca.util.item_extension.EcaTooltipLine;
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;
import net.minecraft.world.item.TooltipFlag;
import java.util.List;
@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
}
@Override
public float getAlpha() {
return 0.8f; // shader layer transparency, 0.0~1.0 (default 1.0 = fully opaque)
}
@Override
public List<EcaTooltipLine> getTooltipLines(ItemStack stack, TooltipFlag flag) {
return List.of(
EcaTooltipLine.head(ItemUtil.of("Starlight Forged")
.addEffect.GRADIENT(0x7C4DFF, 0x00E5FF)
.addEffect.BOLD()),
EcaTooltipLine.body(ItemUtil.of("Arcane resonance: unstable")
.addEffect.SOLID(0xFFAA00)
.addEffect.ITALIC()),
EcaTooltipLine.tail(ItemUtil.of("Hold Shift for hidden lore")
.addEffect.RAINBOW())
);
}
}
Structured tooltip lines can choose their own insertion position:
EcaTooltipLine.head(...): below the item name.EcaTooltipLine.body(...): in the main tooltip body, before advanced item id/NBT/disabled lines when present.EcaTooltipLine.tail(...): at the end of the tooltip.
Each line accepts either a normal Component or an EcaText built through ItemUtil.of(...), so tooltip text supports the same rich effects as item names: gradient, rainbow, solid color, shimmer, glitch, bold, italic, underline, and strikethrough. The older appendTooltip(ItemStack, TooltipFlag, List<Component>) hook is still available when you need to directly edit the final tooltip list.
Note: Like entity extensions, each item can only have one extension. Duplicate registrations are rejected with an error log. Both entity layer extensions (EntityLayerExtension.getAlpha(), default 0.5) and item extensions (ItemExtension.getAlpha(), default 1.0) support adjustable transparency for their shader overlay layers.
Shader Presets
This mod also provides several shader presets for the entity extension and item extension systems, which can be used directly in your extensions. Simply replace CustomRenderTypes in the example code with the corresponding preset name. Each preset provides 4 RenderTypes: BOSS_BAR, BOSS_LAYER, SKYBOX for entity extensions, and ITEM for item extensions. Entity texture overlays are supported through EntityLayerExtension.getTexture() — return a texture to overlay it on the entity model, optionally combined with the shader RenderType for a texture‑plus‑shader effect (matching the boss‑bar overlay technique).
Available presets:
TheLastEndRenderTypes— The Last EndDreamSakuraRenderTypes— Dream SakuraForestRenderTypes— ForestOceanRenderTypes— OceanStormRenderTypes— StormVolcanoRenderTypes— VolcanoArcaneRenderTypes— ArcaneAuroraRenderTypes— AuroraHackerRenderTypes— HackerStarlightRenderTypes— StarlightCosmosRenderTypes— CosmosBlackHoleRenderTypes— Black Hole
Screen Filters
This mod provides a set of full-screen post-processing filter presets that 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.
Filter presets:
SKETCH— sketchSPOTLIGHT— spotlightMATRIX— matrixRAIN— rainDESERT— desertSNOW— snowTOXIC— toxicCOSMOS— cosmos
Shader Generator
ECA provides an in-game shader preset generator for building portable Minecraft core shader presets without writing GLSL by hand. Open it with:
/eca shaderGenerator
The generator edits a layered composition project. Each layer can contain multiple visual modules, including basic shapes, starry sky effects, magic symbols, and image elements. The editor supports live preview, undo/redo, layer visibility, layer ordering, blend modes, canvas editing, project save/load, and five-file shader export.
Preview targets currently include plane, item, entity, skybox, and Boss bar. The exported preset uses the standard core shader five-file layout:
assets/<namespace>/shaders/core/<name>.fsh
assets/<namespace>/shaders/core/<name>_block.vsh
assets/<namespace>/shaders/core/<name>_block.json
assets/<namespace>/shaders/core/<name>_entity.vsh
assets/<namespace>/shaders/core/<name>_entity.json
The fragment shader is shared by both profiles. The two vertex profiles are generated separately because Minecraft uses different vertex formats for different render targets:
<name>_block.*usesDefaultVertexFormat.BLOCK, for skybox, plane preview, and Boss bar rendering.<name>_entity.*usesDefaultVertexFormat.NEW_ENTITY, for entity layers, item layers, and textured entity effects.
Export modes:
PORTABLE: standard Minecraft core shader output with no ECA-specific uniforms.PORTABLE_WITH_ECA_HINTS: includes ECA uniform hooks with harmless defaults, while remaining usable without ECA.ECA_ENHANCED: includes ECA-specific uniforms and expects ECA's enhanced shader runtime.
Project files are saved under config/eca/shadergenerator/<namespace>/<name>/project.json. Use File -> Export As <shader> to export a runtime-loadable five-file preset into config/eca/shadergenerator/<namespace>/<name>/. ECA automatically discovers presets from both mod assets and exported config presets. A preset ID is always <namespace>:<name>.
For mod-packaged presets, place the five files under src/main/resources/assets/<namespace>/shaders/core/. You may also declare the preset with @RegisterShaderPreset. The annotation registers the preset ID during startup scanning and is useful for mods that want to expose custom presets through an explicit Java marker class:
import net.eca.api.RegisterShaderPreset;
@RegisterShaderPreset("mymod:my_nebula")
public final class MyNebulaPreset {
}
At runtime, use EcaPresets to obtain the generated RenderTypes:
import net.eca.client.render.preset.EcaPresets;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
public final class MyPresetRenderTypes {
public static RenderType bossBar() {
return EcaPresets.bossBar("mymod:my_nebula");
}
public static RenderType bossLayer() {
return EcaPresets.bossLayer("mymod:my_nebula");
}
public static RenderType skybox() {
return EcaPresets.skybox("mymod:my_nebula");
}
public static RenderType item() {
return EcaPresets.item("mymod:my_nebula");
}
}
You can also query the preset object through EcaAPI:
import net.eca.api.EcaAPI;
import net.eca.client.render.preset.ShaderPreset;
import net.minecraft.resources.ResourceLocation;
ShaderPreset preset = EcaAPI.shaderPreset(new ResourceLocation("mymod", "my_nebula"));
The returned ShaderPreset exposes four ready-made render targets: bossBar(), bossLayer(), skybox(), and item(). For entity texture overlays, use EntityLayerExtension.getTexture() with bossLayer().
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.
Default Editor Keybindings:
| 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.


