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 Mixin technologies, plus a set of feature modules: BossShow, entity extensions, block extensions, item extensions, screen filters, the ECA shader generator, custom factions, and custom raids. Note that while the entity-manipulation methods may share names with 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 hurt <targets> <amount>- Force entity damage (vanilla hurt first, forced write when the health loss does not land; kill credit goes to the executor when run by a living entity)/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 mod file owning the target entity, retransforming its already-loaded classes. Vanilla entities (players included) own no transformable mod file, so the target falls back to the mod files owning their equipped items/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 shaderGenerator- Open the in-game shader preset generator/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)/eca faction create <id> <displayName> [color]- Create a faction (color accepts a preset name such as red/gold/teal)/eca faction remove <id>- Remove a faction definition and drop every entity binding pointing at it/eca faction join <factionId> [targets]- Bind entities to a faction (defaults to the command source entity)/eca faction leave [targets]- Unbind entities from their current faction/eca faction list- List all registered factions/eca faction info [factionId]- Show a faction's color, members and relation overrides/eca faction relation <factionA> <factionB> <relation>- Set A's relation toward B.hostile/neutral/friendlystore a relation override;same_factioninstead merges B into A (B's members are rebound to A, relations are folded in, and B is deleted)/eca faction leader <factionId>- Show a faction's leader and whether it is currently loaded/eca faction leader <factionId> set [target]- Set the leader (defaults to the command source entity; joins the faction automatically)/eca faction leader <factionId> clear- Clear the leader; the former leader remains a member/eca raid defs- List all registered raid definitions/eca raid list- List active raids in the current dimension/eca raid start <definitionId> [pos]- Start a raid at a position inside its target structure (defaults to the command source position)/eca raid startat <definitionId> <pos>- Start a raid with an explicit center, skipping the structure lookup/eca raid info <instanceId>- Show details of one running raid/eca raid end <instanceId> <victory|defeat>- End a raid and clear every surviving raider
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.7-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)- Read the health observed through the entity's active life protocol: the analyzer's health anchor first, falling back to vanillaDATA_HEALTH_IDwhen no anchor resolves (0.0f if the entity is null)getRealHealth(entity)- The same authoritative observation, returning NaN instead of 0.0f for a null entitysetHealth(entity, health)- Verified health transaction that escalates through channels only when the previous one fails verification: vanilla write (writeDATA_HEALTH_IDdirectly) → dataflow reversal (ASM dataflow analysis ofgetHealth()locates the real storage and inverts its read expression) → external scan (reverseisAlive/isDeadOrDying/hurt/actuallyHurtto locate storage, including effective-health models that need conversion) → method probe (borrow the entity's own writer: reflective setters, functional fields, injected bridges) → numeric inversion (search the object graph for writable numeric cells when the storage cannot be inverted). Each attempt is judged by reading the health anchor back withinmax(0.5, abs(target) * 2%); every write snapshots the affected state beforehand and rolls the whole transaction back when verification fails. A successful server-side write is broadcast to tracking clients and registered for delayed re-verification; classes whose health is reverted a tick later additionally get an out-of-entity health mirror written. Players run the vanilla write only. Every channel past the vanilla write requires Attack Radical Logic plus its own switch underAttack → setHealth(Const Override / External Scan / Method Probe / Numeric Inversion), and all four default to off.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 keywordshurt(entity, damageSource, amount)- Damage an entity and guarantee the health loss lands. Vanillahurtwrites health only once, insideactuallyHurt, assetHealth(getHealth() - damage)— through the entity's own getter and setter, so an overridden or storage-decoupled entity runs the whole pipeline and fires all events while losing no health. This method clears the invulnerability cooldown and calls vanillahurtfirst (mitigation, knockback, aggro and hurt animation all happen normally), then compares the health anchor againstbefore - amountwithinmin(1.0, amount * 50%). On mismatch it restores the damage-source bookkeeping vanilla would have left (lastHurtByMob, lastHurtByPlayer/Time, lastDamageSource/Stamp, combat tracker, hurt animation) and forces the health throughsetHealth, clamped at zero. A lethal result is never forced into the death path here: the entity is left at zero health so vanillatickDeathplays the death animation and removes it — usekillwhen an immediate kill is wanted. Entities under ECA's own health lock or invulnerability are left to those systems — vanillahurtstill runs, but no forced write is attempted.hurt(entity, attacker, amount)- Same pipeline with the damage source derived from the attacker:playerAttackfor players,mobAttackfor every other living entity, so kill credit and loot attribution behave as expectedkill(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 direct field access 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 mod file owning the target entity, and retransforms that mod's already-loaded classes. Vanilla entities (players included) fall back to the mod files owning their equipped itemsdisableAllReturn(entity)- Disable AllReturn for that entity's owning mod file, using the same target resolution including the equipped-item fallbacksetGlobalAllReturn(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 predicategetNearestEntity(level, pos, filter)- Get the nearest entity matching a predicate (ECA resolver, so invulnerable entities are included)getNearestEntity(level, pos, area, filter)- Same, narrowed to an AABBgetNearestEntity(level, pos, entityClass)- Get the nearest entity of a given typegetNearestEntity(level, pos, area, entityClass)- Get the nearest entity of a given type inside an AABBshaderPreset(id)- Get a shader preset by id, exposing its ready-made render targetsstartResurrection()- 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 immediatelycreateFaction(id, displayName, color)- Create and register a faction (memory only)createFaction(id, displayName, color, level)- Create and register a faction, persisted to world SavedDataremoveFaction(id)- Remove a faction definition (memory only)removeFaction(id, level)- Remove a faction definition and drop every entity binding pointing at itmergeFactions(intoId, fromId, level)- Merge one faction into another: members are rebound, relation overrides are folded in, and the dissolved faction is removed. Returns the number of members moved, or -1 if the merge could not rungetFaction(id)- Get a faction definition by idgetAllFactions()- Get all registered factionsjoinFaction(entity, factionId)- Bind an entity to a factionleaveFaction(entity)- Unbind an entity from its factiongetEntityFaction(entity)- Get the faction id an entity belongs to (null if none; tamed animals fall back to their owner's faction)areSameFaction(a, b)- Check whether two entities share a factionisFriendly(a, b)- Check the complete friendly relationship: same/friendly ECA faction, vanilla scoreboard alliance, or owner-pet alliance (excludes creative, spectator and ECA invulnerability)getFactionMembers(level, factionId)- Resolve the faction's member table to live entities in one levelkickAllFromFaction(factionId, level)- Remove every explicit member globally, including unloaded and cross-dimension memberssetFactionRelation(a, b, relation)- Set faction A's relation toward faction B (memory only)setFactionRelation(a, b, relation, level)- Set faction A's relation toward B, persistedgetFactionRelation(a, b)- Get the explicit relation from A to B (null if no override)getEffectiveFactionRelation(source, target)- Resolve the effective relation between two entitiescanHarm(source, target)- Check whether ECA faction relations allow source to harm the targetcanTarget(source, target)- Check whether complete faction and protection rules allow source to deliberately target the targetalertFactionMembers(factionId, attacker, victim, level)- Make nearby untargeted allies retaliate against an attackergetFactionMemberTypes(factionId)- Get the entity type pool a faction declares, mapped to spawn weightsrollFactionMemberType(factionId, random)- Pick one entity type from a faction's pool by weightjoinFaction(uuid, typeId, isPlayer, factionId, level)- Bind an entity to a faction by UUID, without requiring it to be loadedleaveFaction(uuid, level)- Remove a member from its faction by UUID, without requiring it to be loadedgetEntityFaction(uuid)- Get the faction bound to a UUID (pure index lookup; no pet inheritance, which needs a live entity)isFactionMember(uuid, factionId)- Check whether a UUID belongs to a specific factiongetFactionMemberRecords(factionId)- Get every member record (UUID + entity type) without loading entitiesgetFactionMemberUuids(factionId)- Get every member UUID without loading entitiesgetFactionMembersByType(factionId, typeId)- Filter members by entity type without loading entitiesgetFactionMemberCount(factionId)- Get a faction's member count without loading entitiesresolveFactionMembers(factionId, level)- Resolve a faction's members to live entities in one levelsetFactionLeader(factionId, leader, level)- Set a faction's leader (joins the faction automatically if needed)clearFactionLeader(factionId, level)- Clear the leader; the former leader remains a membergetFactionLeader(factionId)- Get the leader record without loading the entitygetFactionLeaderUuid(factionId)- Get the leader's UUIDresolveFactionLeader(factionId, server)- Resolve the leader to a live entity, searching every dimensionisFactionLeader(entity)- Check whether an entity leads any factiongetFactionByLeader(uuid)- Find which faction an entity leadsstartRaid(level, pos, raidId)- Start a raid at a position inside its target structure (center taken from the structure)startRaidAt(level, center, raidId)- Start a raid with an explicit center, skipping the structure lookupendRaid(level, raid, victory)- End a raid, discarding every surviving raiderendRaid(level, raidId, victory)- End a raid by its instance id, discarding every surviving raidergetRaid(level, raidId)- Get an active raid by its instance idgetActiveRaids(level)- Get every active raid in a levelgetNearestRaid(level, pos, maxDistance)- Find the nearest active raid within a distancegetAllRaidDefinitions()- Get all registered raid definitions
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.
Entity, item, and block shader overlays share the same ShaderMaskPass pipeline. Every pass supplies a RenderType, an optional UV-aligned mask texture, a target RGB color (black by default), a near-color tolerance, and opacity. An extension may return multiple passes so different colors in one mask use different shaders. Passes render in list order, and later passes draw over earlier passes where selected regions overlap. Transparent and non-matching mask pixels are discarded.
Block Extensions
Block extensions add shader overlays without replacing the normal model. Ordinary baked and falling blocks use BLOCK-profile passes from getBlockShaderPasses(); GeckoLib block entities use NEW_ENTITY-profile passes from getGeoShaderPasses(texture). A logical preset id still supplies the default RenderTypes for both profiles.
External masks on baked blocks are sampled with sprite-local UVs even though the model uses an atlas. Geo masks use the model texture's normal UV layout and combine with overlayGeoBones() as an intersection. Normal world blocks are indexed by section and batched into a separate overlay pass; falling blocks and GeckoLib block entities are handled automatically. Block items remain part of ItemExtension. The old Color-Key and single-mask getters are deprecated compatibility adapters.
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.
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.
Item mask passes use sprite-local UVs automatically. ShaderMaskPass.masked(...) samples an external mask texture, while ShaderMaskPass.baseTexture(...) selects colors directly from the item texture. The old getRenderType(), Color-Key, and single-mask getters are deprecated compatibility adapters.
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 use the corresponding preset class in place of your own RenderType holder. Each built-in preset class exposes 4 ready RenderTypes — BOSS_BAR, BOSS_LAYER, SKYBOX for entity extensions and ITEM for item extensions — plus createEntityEffect(texture) for entity texture overlays. 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, five-file shader export, and project deletion (File -> Delete Current Project, which asks for confirmation and then permanently removes the project directory with its source, textures and imported dependencies).
Each project also owns a five-file source workspace. Use File -> Source Editor to switch the same project to manual GLSL/JSON editing with a single-row menu, comment-based quick navigation, undo/redo, save, compile shortcuts, and debounced live preview. The right side places the preview above a scrollable compiler-output panel. Generated fragment shaders emit // @eca-nav layer: ... and // @eca-nav element: ... markers; manually written // @eca-nav ... comments create custom navigation points. Returning to the visual editor does not discard either representation. File -> Import Shader Folder opens the native folder picker at Forge's canonical game directory and copies a selected standard JSON/VSH/FSH core shader into a new local ECA project. A folder may contain multiple shader programs, in which case the editor asks which one to import. When a source path contains assets/<modid>/shaders/core, the project dialog pre-fills that Mod ID; otherwise the field remains empty. Standard three-file shaders are duplicated into the BLOCK and NEW_ENTITY source slots for independent compile validation; folders containing ECA's shared-fragment _block/_entity five-file layout preserve both profiles directly. The source folder is never modified.
Import supports standard Minecraft core shader JSON/VSH/FSH resources. Common time, camera, scale, opacity, and cosmic-UV uniforms receive preview bindings. A shader that depends on a mod-specific render pipeline, Java callbacks, textures, or uniforms may still need a dedicated adapter; unsupported fragment structure is reported as a compile error instead of being silently rewritten.
Texture dependencies are resolved as well. When an imported shader references a numbered sequence of PNG files, ECA attempts to copy those files, combine them into one preview texture, and provide the matching sampler and uniform with the UV range of each image. A PNG with a .mcmeta animation section updates frame by frame inside that combined texture while preserving frame order, per-frame duration, and interpolation. If ECA cannot determine how the files correspond to a sampler or uniform, the compiler-output panel names the unresolved dependency and scanned directory instead of failing silently; the shader compilation itself can still succeed.
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.
At runtime, obtain the generated RenderTypes through EcaPresets, or query the preset object through EcaAPI.shaderPreset(id). The returned ShaderPreset exposes bossBar(), bossLayer(), skybox(), item(), block(), geoBlock(texture) and entityForPreview(texture); EcaPresets mirrors the first six as static lookups by preset id. block() and geoBlock(texture) are the BLOCK and NEW_ENTITY profiles used by block extensions. For entity texture overlays, use EntityLayerExtension.getTexture() with bossLayer().
AI Assistant
The source editor also has an AI assistant that drives the same project through a model of your choice. Three API formats are supported — OpenAI Responses, OpenAI Chat compatible, and Anthropic Messages — each stored as a profile with its own base URL, model, API key or key environment variable, custom headers, and a 10–600 second timeout in config/eca/shadergenerator/settings.json.
The model acts through tools, not free text: read the project summary and module schemas, edit layers and elements, import PNG images, modify any of the five source files, save the project, export the five shader files, compile and read diagnostics, capture the preview, and undo or redo its own mutations. Three switches bound how far it acts alone — allow automatic edits, compile after every edit, send preview images to vision models. With automatic editing off it can still inspect and explain but every write is refused; with automatic compiling on it recompiles after each edit and repairs from the diagnostics, capped by an automatic-repair limit and an overall tool-round limit.
MCP
The MCP button on the AI assistant page starts a local ECA Shader MCP that lets an external agent drive the current shader project. The service speaks Streamable HTTP, binds only to 127.0.0.1, and needs no access token; the MCP page shows the port and connected agents, and the port is stored in config/eca/shadergenerator/mcp_settings.json. The URL port must match the MCP page, and both Minecraft and the MCP service must stay running.
Order of operations: start the service in-game first via Shader Generator → AI Assistant → MCP, then launch and connect the agent. An agent cannot connect while the service is not running.
Codex
Register from the command line:
codex mcp add eca_shader --url http://localhost:8767/mcp
Claude Code
Register from the command line:
claude mcp add --transport http eca_shader http://127.0.0.1:8767/mcp
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.
The event_id strings in the JSON above are dispatched to onKeyframeEvent on the server at the corresponding tick, and cutscenes can also be triggered from code through EcaAPI.playBossShow(...) / launchBossShowEvent(...).
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.
Custom Factions
ECA provides a faction system that controls targeting and damage relationships. Binding an entity makes vanilla alliance checks and target assignment respect same-faction, friendly and neutral rules without requiring an interface. Faction-bound mobs periodically acquire nearby faction-bound entities with a HOSTILE relation through Mob.setTarget; their existing combat goals still perform movement and attacks. Entities without a faction are never selected by this faction acquisition pass. Standard LivingEntity damage paths enforce friendly protection; direct state-changing APIs remain the caller's responsibility. FactionUtil.isFriendly resolves alliances, while FactionUtil.canAttack additionally enforces creative/spectator and ECA invulnerability protection.
EcaAPI.isFriendly(a, b) is the public complete friendly check. It returns true for the same ECA faction, friendly ECA factions, vanilla scoreboard allies, owner-pet pairs, pets with the same owner, and pets whose owners are scoreboard allies. Creative mode, spectator mode and ECA invulnerability are deliberately excluded because they are attack protections rather than alliance relationships. Use areSameFaction only when exact ECA faction identity matters; canHarm checks ECA faction relations only, while canTarget also rejects neutral relations and complete target immunity.
Factions are registered by extending FactionDefinition and annotating the class with @RegisterFaction. Definitions are scanned during FMLLoadCompleteEvent; duplicate ids are logged and skipped (first one scanned wins). Factions can also be created at runtime through EcaAPI.createFaction, with or without persistence. An EntityExtension may declare getFactionId() so that every entity of that type joins a faction automatically — the faction has to be registered before those entities spawn, otherwise the binding is refused and logged.
Four relations are available:
SAME_FACTION— same faction id, fully immune to each other and never targetedFRIENDLY— different factions but allied, no damage and no targetingNEUTRAL— not deliberately targeted, but incidental damage still appliesHOSTILE— normal combat
SAME_FACTION is derived, not stored: it is produced whenever both sides resolve to the same faction id, so storing it as a cross-faction override would never be read back. Making two factions genuinely one therefore means merging them — EcaAPI.mergeFactions(intoId, fromId, level), which /eca faction relation A B same_faction calls with A as the survivor. Members of B are rebound to A, B's relation overrides are inherited only where A has none of its own, third-party entries pointing at B are retargeted to A or dropped, and B is then deleted. A keeps its own display name, color, default relation and leader, inheriting B's leader only when it has none.
Relation resolution runs in this order, and the first match wins:
- Same faction id →
SAME_FACTION - A's
getRelation(self, target)conditional override - A's static
hostileTo/friendlyTo/neutralToarrays - Symmetric fallback — the same two checks evaluated from B's side
- A's
getDefaultRelation(self, target)conditional override (only when the other side has no faction) - A's static default relation
Each faction owns its member table. A member is recorded as a UUID plus its entity type, so a roster can be listed, filtered by type and counted without loading a single entity — members sitting in unloaded chunks or other dimensions are still fully visible and manageable. Factions live in the overworld's SavedData, so membership is global across dimensions and survives restarts.
A binding is dropped when the entity is permanently removed; chunk unloads and dimension changes keep it, and players keep theirs across death and respawn. Membership cannot outlive its faction — unregistering a faction drops its whole member table, and joining a faction that does not exist is refused rather than silently recorded.
Tamed animals inherit their owner's faction automatically, so a pet is protected by its owner's allies and can answer nearby faction alerts. Inheritance is resolved at lookup time rather than stored: an inherited pet is not included in the persistent member table, offline queries, counts or table-wide leader propagation. It follows its owner across faction changes and never creates a binding of its own — calling leaveFaction on such a pet therefore does nothing. Bind a pet explicitly if it must belong elsewhere or participate in member-table operations; an explicit binding always takes precedence over inheritance.
A faction may optionally declare which entity types it consists of through getMemberEntityTypes(), mapping types to spawn weights. This lets other systems spawn "some members of this faction" without naming concrete types — the raid system uses it for faction-drawn waves.
Leaders: A faction may designate one member as its leader. Setting a leader adds it to the faction automatically if it was not a member — a leader outside its own faction would be a contradictory state. Leaving the faction also vacates the post, and a leader that is permanently removed is cleared automatically.
Threat propagation: When a leader attacks a hostile faction member, or is attacked by one, that entity is offered as the target of every resolvable mob in the faction member table. Existing targets and faction target permissions may still prevent a switch. Ordinary member alerts are answered by nearby mobs from both the victim's own faction and friendly factions. Two mechanisms coexist:
| Trigger | Range | |
|---|---|---|
| Leader protection | the leader attacks or is attacked | the entire member table |
| Member alert | any member is hurt | configurable radius around the victim |
Leader protection is deliberately not range-limited: the member table is walked directly, so summons far from their master still answer. Members that cannot be resolved in the leader's dimension are skipped, and propagation never hands a member a target it is forbidden to attack. Repeat propagation of the same target within one tick is dropped, so a rapidly attacking leader does not walk the table on every hit.
Both mechanisms are governed entirely by config — there are no per-faction overrides, so every faction behaves the same way on a given server:
Leader Protection Enabled(defaulttrue)Immediate Leader Protection(defaultfalse)Alert Enabled(defaulttrue) /Alert Range(default32) /Immediate Member Alert(defaultfalse)
"Immediate" off means only members that currently have no target will engage; on means they abandon whatever they were fighting.
Querying: Membership can be inspected from either direction, and the methods that do not resolve entities work entirely offline:
| Direction | Methods |
|---|---|
| entity relationship | areSameFaction(a, b) (same ECA faction only), isFriendly(a, b) (complete ECA + vanilla friendly check), getEffectiveFactionRelation(a, b), canHarm(a, b) (ECA faction harm rules only), canTarget(a, b) (neutral and immunity-aware target check) |
| member → faction | getEntityFaction(entity) (includes pet inheritance), getEntityFaction(uuid), isFactionMember(uuid, id) |
| faction → members | getFactionMemberRecords(id), getFactionMemberUuids(id), getFactionMembersByType(id, typeId), getFactionMemberCount(id) |
| faction → entities | resolveFactionMembers(id, level) |
| faction → leader | getFactionLeader(id), getFactionLeaderUuid(id), resolveFactionLeader(id, server) (searches every dimension) |
| leader → faction | getFactionByLeader(uuid), isFactionLeader(entity) |
joinFaction and leaveFaction both have UUID overloads for managing members whose entity is not loaded.
Faction members can also glow in their relation color for nearby players, which is configurable and off by default.
Custom Raids
ECA provides a customizable raid system. The vanilla raid only works on villages, only accepts entities implementing Raider, and hardcodes its victory condition and rewards; an ECA raid can target any structure, use any entity type, and replace every rule that governs how it progresses and ends.
Raids are registered by extending RaidDefinition and annotating with @RegisterRaid. Scanning runs after faction scanning, so a raid definition may freely reference faction ids. Only getId(), getDisplayName() and getWaves() are required — everything else has a working default modelled on the vanilla raid.
Targeting: Override getTargetStructure() for a single structure, or getTargetStructureTag() to match any structure carrying a tag so one raid applies to several structure types. Anchoring drives the default defeat condition: the raid is lost when the target structure no longer covers the raid center. Declaring neither runs the raid unanchored, in which case it can only end by victory, timeout, or an explicit end call.
Waves: Each RaidWave mixes two spawn sources freely — explicit entity entries, and faction draws that pull from a faction's getMemberEntityTypes() pool by weight.
Raiders: Spawned raiders are bound to getRaiderFactionId(). Spawned Mob instances also receive an injected goal that paths them to the raid center. The goal sits at priority 3 by default, matching vanilla's PathfindToRaidGoal — below the usual melee attack goal, so raiders fight an already acquired hostile-faction target and otherwise advance. Any entity type can be spawned and no interface is required, but non-Mob entities receive neither faction target acquisition, the navigation goal, nor mob callbacks. Override getRaiderGoalPriority() or return a negative value to change or disable goal injection.
Boss: A wave may declare a leader with RaidWave.setLeader(type). The spawned entity becomes the leader of the raid's raider faction, so the faction's threat propagation applies to it for free. Eligible, loaded mobs without an existing target respond by default; Immediate Leader Protection allows them to replace an existing target. Declaring a leader requires getRaiderFactionId(); without a faction there is nothing to lead and the entry spawns as an ordinary raider.
Note that propagation walks the entire faction member table, not just this raid's participants. If the raider faction has other members elsewhere in the world, they answer too. Use a raid-specific faction if you want the response confined to the raid.
Validation: Starting a raid verifies the factions it references. A non-empty but unregistered raider faction refuses the start outright because the requested friendly-fire and alert rules could not be applied. Returning null intentionally is allowed and leaves each spawned entity governed by its own AI. A wave drawing from a faction that is unregistered or declares no member pool logs an error and skips that group, but the raid still starts.
Progression: shouldAdvanceWave, checkVictory and checkDefeat are all overridable. The defaults reproduce vanilla semantics: the next wave spawns once the previous one is dead, and the defenders win when every wave has spawned and every raider is gone.
Timing and callbacks: getMaxDurationTicks() defaults to 48000, getWaveCooldownTicks() to 300, getParticipantRadius() to 96 blocks and getCelebrationTicks() to 600. A wave can add its own spawnDelay() and spawnRadius(). Lifecycle hooks are onStart, onWaveStart, onWaveEnd, onVictory, onDefeat and onStop. Client-side bossBarExtension() can replace the raid bar appearance while retaining server-synced raid state.
Endless raids: isEndless() cycles the wave list forever and never satisfies the default victory condition. Finish one with EcaAPI.endRaid, which discards every surviving raider.
Raids run per dimension and automatically restore their latest periodic checkpoint after a restart. Permanent casualties and terminal operations are saved immediately; ordinary progression is checkpointed once per second. The center chunk is force-loaded for the duration, but raiders that travel into other unloaded chunks are not force-loaded with it.
Registering a definition does not start anything. Raids are started explicitly through EcaAPI.startRaid(...) / startRaidAt(...) so that any trigger condition can drive them — entering a region, using an item, a command, a scheduled event.
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.


