← Назад к ресурсам

Epic Core API

A library mod that provides powerful entity manipulation APIs through CoreMod (ITransformationService), Java Agent, and VarHandle technologies.

Modrinth library optimization utility

Описание

# 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, Blender GLB models and animation, 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](https://mcreator.net/plugin/121284/20244epic-core-api-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 [report]` - Set entity health. Add `report` to write one structured `_health_report_.txt` file per living target directly under `logs/`; the report includes the health-model judgment, channel outcome, delayed verification, complete per-source dataflow inversion trace, and related background analysis. - `/eca setMaxHealth ` - Set entity max health (reverse-calculates attribute base value) - `/eca setInvulnerable ` - Set entity invulnerability - `/eca lockHealth true ` - Lock entity health at specific value - `/eca lockHealth false` - Unlock entity health - `/eca lockMaxHealth true ` - Lock entity max health at specific value - `/eca lockMaxHealth false` - Unlock entity max health - `/eca banHealing true [value]` - Ban healing for entities (value optional, defaults to current health) - `/eca banHealing false` - Unban healing for entities - `/eca hurt ` - 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 ` - Kill entities - `/eca remove [reason]` - Remove entities from world - `/eca memoryRemove ` - DANGER! Requires Attack Radical Logic config. Remove entities via LWJGL internal channel - `/eca teleport ` - Teleport entities - `/eca lockLocation [x y z]` - Lock/unlock entity location - `/eca cleanBossBar ` - Clean up boss bars - `/eca allReturn ` - 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 ` - DANGER! Enable/disable global AllReturn for all non-whitelisted mods - `/eca banSpawn ` - Ban spawning of selected entities' types for specified duration - `/eca banSpawn clear` - Unban all spawns in current dimension - `/eca setForceLoading ` - 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 ` - Set global skybox shader preset - `/eca setFilter true ` - Apply a screen filter to players (type: sketch, spotlight, matrix, rain, desert, snow, toxic, cosmos) - `/eca setFilter 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 ` - Force-play a cutscene - `/eca bossShow stop ` - Stop the viewer's current cutscene - `/eca bossShow reload` - Reload all cutscene JSON definitions from disk - `/eca bossShow clearHistory ` - 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 ` - Add entities to resurrection tracking (auto-revived on death every poll cycle) - `/eca resurrection remove ` - Remove entities from resurrection tracking - `/eca resurrection list` - List all tracked entities with container integrity status - `/eca resurrection check ` - One-shot container integrity check for an entity - `/eca resurrection revive ` - Manually force-revive a tracked entity immediately - `/eca resurrection interval ` - Set poll interval in milliseconds (100–10000; default 25) - `/eca faction create [color]` - Create a faction (color accepts a preset name such as red/gold/teal) - `/eca faction remove ` - Remove a faction definition and drop every entity binding pointing at it - `/eca faction join [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 ` - Set A's relation toward B. `hostile` / `neutral` / `friendly` store a relation override; `same_faction` instead merges B into A (B's members are rebound to A, relations are folded in, and B is deleted) - `/eca faction leader ` - Show a faction's leader and whether it is currently loaded - `/eca faction leader set [target]` - Set the leader (defaults to the command source entity; joins the faction automatically) - `/eca faction leader 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 [pos]` - Start a raid at a position inside its target structure (defaults to the command source position) - `/eca raid startat ` - Start a raid with an explicit center, skipping the structure lookup - `/eca raid info ` - Show details of one running raid - `/eca raid end ` - 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) ```groovy repositories { maven { url = "https://api.modrinth.com/maven"; content { includeGroup "maven.modrinth" } } } ``` **Step 2: Add ECA dependency** (build.gradle) ```groovy dependencies { implementation fg.deobf("maven.modrinth:epic-core-api:VERSION") } ``` > Replace `VERSION` with the version you need (e.g. `1.1.7-fix-fix`). Go to [ECA Modrinth page](https://modrinth.com/mod/epic-core-api) to find available versions. **Step 3: Declare dependency** (mods.toml) ```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 lock - `getLockedHealth(entity)` - Get current health lock value (null if not locked) - `isHealthLocked(entity)` - Check if entity health is locked - `banHealing(entity, value)` - Ban healing for entity at specified value (entity cannot heal but can take damage) - `unbanHealing(entity)` - Unban healing for entity - `getHealBanValue(entity)` - Get current heal ban value (null if not banned) - `isHealingBanned(entity)` - Check if entity has healing banned - `getHealth(entity)` - Read the health observed through the entity's active life protocol: the analyzer's health anchor first, falling back to vanilla `DATA_HEALTH_ID` when 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 entity - `setHealth(entity, health)` - Verified health transaction that escalates through channels only when the previous one fails verification: vanilla write (write `DATA_HEALTH_ID` directly) → dataflow reversal (ASM dataflow analysis of `getHealth()` locates the real storage and inverts its read expression) → external scan (reverse `isAlive` / `isDeadOrDying` / `hurt` / `actuallyHurt` to 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 within `max(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 under `Attack → 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 modifiers - `lockMaxHealth(entity, value)` - Lock entity max health at specific value (enforced every tick) - `unlockMaxHealth(entity)` - Unlock entity max health - `getLockedMaxHealth(entity)` - Get current max health lock value (null if not locked) - `isMaxHealthLocked(entity)` - Check if entity max health is locked - `addHealthWhitelistKeyword(keyword)` - Add keyword to health modification whitelist - `removeHealthWhitelistKeyword(keyword)` - Remove keyword from health modification whitelist - `getHealthWhitelistKeywords()` - Get all health whitelist keywords - `addHealthBlacklistKeyword(keyword)` - Add keyword to health modification blacklist - `removeHealthBlacklistKeyword(keyword)` - Remove keyword from health modification blacklist - `getHealthBlacklistKeywords()` - Get all health blacklist keywords - `hurt(entity, damageSource, amount)` - Damage an entity and guarantee the health loss lands. Vanilla `hurt` writes health only once, inside `actuallyHurt`, as `setHealth(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 vanilla `hurt` first (mitigation, knockback, aggro and hurt animation all happen normally), then compares the health anchor against `before - amount` within `min(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 through `setHealth`, clamped at zero. A lethal result is never forced into the death path here: the entity is left at zero health so vanilla `tickDeath` plays the death animation and removes it — use `kill` when an immediate kill is wanted. Entities under ECA's own health lock or invulnerability are left to those systems — vanilla `hurt` still runs, but no forced write is attempted. - `hurt(entity, attacker, amount)` - Same pipeline with the damage source derived from the attacker: `playerAttack` for players, `mobAttack` for every other living entity, so kill credit and loot attribution behave as expected - `kill(entity, damageSource)` - Kill entity (loot + advancements + removal) - `revive(entity)` - Clear death state and restore health - `revive(level, uuid)` - Clear death state and restore health by UUID in specified level - `reviveAllContainers(entity)` - Revive all critical entity containers (tickList, lookup, sections, tracker) - `reviveAllContainers(level, uuid)` - Revive all critical entity containers by UUID in specified level - `teleport(entity, x, y, z)` - Teleport via direct field access with client sync - `lockLocation(entity)` - Lock entity location at current position - `lockLocation(entity, position)` - Lock entity location at specified position - `unlockLocation(entity)` - Unlock entity location - `isLocationLocked(entity)` - Check if entity location is locked - `getLockedLocation(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 channel - `cleanupBossBar(entity)` - Remove boss bars without removing entity - `isInvulnerable(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 items - `disableAllReturn(entity)` - Disable AllReturn for that entity's owning mod file, using the same target resolution including the equipped-item fallback - `setGlobalAllReturn(enable)` - DANGER! Requires Attack Radical Logic config. Enable/disable global AllReturn for all non-whitelisted mods - `disableAllReturn()` - Disable AllReturn and clear targets - `isAllReturnEnabled()` - Check if AllReturn is enabled - `addAllReturnWhitelist(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 AllReturn - `isTransformWhitelisted(className)` - Check if a class is protected from all ECA transformations - `getAllWhitelistedPackages()` - Get all whitelist prefixes (both levels, built-in + custom) - `getEntityExtensionRegistry()` - Get all registered entity extensions (Map) - `getActiveEntityExtensionTypes(level)` - Get active entity extension types in current dimension (Map) - `getActiveEntityExtension(level)` - Get the currently effective entity extension (highest priority) - `clearActiveEntityExtensionTable(level)` - Clear active entity extension table in current dimension - `playAnimation(entity, animation)` - Start a named GLB animation from the beginning at normal speed without looping (logical server only) - `playAnimation(entity, animation, speed, loop)` - Start or restart a named GLB animation with explicit playback settings (logical server only) - `stopAnimation(entity)` - Stop explicit playback and return to the extension-selected or model-default animation - `pauseAnimation(entity)` - Pause explicit playback while holding its current position - `resumeAnimation(entity)` - Resume explicit playback from its preserved position - `isAnimationPlaying(entity[, animation])` - Query explicit playback, optionally matching an exact animation name - `setGlobalFog(level, fogData)` - Set global fog effect override for a dimension (does not change effect priority) - `clearGlobalFog(level)` - Clear global fog effect override - `setGlobalSkybox(level, skyboxData)` - Set global skybox effect override for a dimension (does not change effect priority) - `clearGlobalSkybox(level)` - Clear global skybox effect override - `setGlobalMusic(level, musicData)` - Set global combat music effect override for a dimension (does not change effect priority) - `clearGlobalMusic(level)` - Clear global combat music effect override - `clearAllGlobalEffects(level)` - Clear all global effect overrides (fog, skybox, music) for a dimension - `enableFilter(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 player - `isFilterEnabled(player, filterType)` - Check whether a filter is active on a player - `getActiveFilters(player)` - Get a player's active filters (unmodifiable Set) - `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 before - `stopBossShow(viewer)` - Stop the viewer's current BossShow cutscene - `isBossShowPlaying(viewer)` - Check whether the viewer is currently in a BossShow cutscene - `launchBossShowEvent(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 duration - `isSpawnBanned(level, entityType)` - Check if entity type is banned from spawning - `getSpawnBanTime(level, entityType)` - Get remaining spawn ban time in seconds - `unbanSpawn(level, entityType)` - Unban entity type, allowing it to spawn again - `getAllSpawnBans(level)` - Get all spawn bans in level (Map) - `unbanAllSpawns(level)` - Unban all entity types in level - `setForceLoading(entity, level, forceLoad)` - Enable/disable force chunk loading for entity - `isForceLoaded(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 id - `getEntity(level, uuid, entityClass)` - Resolve typed entity by UUID - `getEntity(server, entityId)` - Resolve entity by id across all levels - `getEntity(server, uuid)` - Resolve entity by UUID across all levels - `getEntities(level)` - Get all entities in level - `getEntities(level, area)` - Get entities in AABB area - `getEntities(level, filter)` - Get entities using custom predicate - `getEntities(level, area, filter)` - Get entities in area using custom predicate - `getEntities(level, entityClass)` - Get all entities of specified type in level - `getEntities(level, area, entityClass)` - Get entities of specified type in area - `getEntities(server)` - Get all entities across all server levels - `getEntities(server, filter)` - Get entities across all levels using custom predicate - `getNearestEntity(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 AABB - `getNearestEntity(level, pos, entityClass)` - Get the nearest entity of a given type - `getNearestEntity(level, pos, area, entityClass)` - Get the nearest entity of a given type inside an AABB - `shaderPreset(id)` - Get a shader preset by id, exposing its ready-made render targets - `startResurrection()` - Start the resurrection daemon thread (idempotent) - `stopResurrection()` - Stop the resurrection daemon thread - `isResurrectionRunning()` - Check whether the daemon is running - `addResurrectionTarget(entity)` - Add an entity to the resurrection tracking set - `removeResurrectionTarget(entity)` - Remove an entity from the resurrection tracking set - `isResurrectionTracked(entity)` - Check whether an entity is tracked for resurrection - `getResurrectionTrackedCount()` - Get the number of currently tracked entities - `clearAllResurrectionTargets()` - Remove all entities from the tracking set - `setResurrectionPollInterval(ms)` - Set the daemon poll interval (ms, clamped 1–10000, default 25) - `getResurrectionPollInterval()` - Get the current poll interval in ms - `getResurrectionTotalRevived()` - Get the total number of entities revived since start - `getResurrectionTotalChecks()` - Get the total number of entity checks performed since start - `checkResurrectionTarget(level, entity)` - Perform a one-shot container integrity check - `reviveResurrectionTarget(level, entity)` - Manually force-revive a tracked entity immediately - `createFaction(id, displayName, color)` - Create and register a faction (memory only) - `createFaction(id, displayName, color, level)` - Create and register a faction, persisted to world SavedData - `removeFaction(id)` - Remove a faction definition (memory only) - `removeFaction(id, level)` - Remove a faction definition and drop every entity binding pointing at it - `mergeFactions(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 run - `getFaction(id)` - Get a faction definition by id - `getAllFactions()` - Get all registered factions - `joinFaction(entity, factionId)` - Bind an entity to a faction - `leaveFaction(entity)` - Unbind an entity from its faction - `getEntityFaction(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 faction - `isFriendly(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 level - `kickAllFromFaction(factionId, level)` - Remove every explicit member globally, including unloaded and cross-dimension members - `setFactionRelation(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, persisted - `getFactionRelation(a, b)` - Get the explicit relation from A to B (null if no override) - `getEffectiveFactionRelation(source, target)` - Resolve the effective relation between two entities - `canHarm(source, target)` - Check whether ECA faction relations allow source to harm the target - `canTarget(source, target)` - Check whether complete faction and protection rules allow source to deliberately target the target - `alertFactionMembers(factionId, attacker, victim, level)` - Make nearby untargeted allies retaliate against an attacker - `joinFaction(uuid, typeId, isPlayer, factionId, level)` - Bind an entity to a faction by UUID, without requiring it to be loaded - `leaveFaction(uuid, level)` - Remove a member from its faction by UUID, without requiring it to be loaded - `getEntityFaction(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 faction - `getFactionMemberRecords(factionId)` - Get every member record (UUID + entity type) without loading entities - `getFactionMemberUuids(factionId)` - Get every member UUID without loading entities - `getFactionMembersByType(factionId, typeId)` - Filter members by entity type without loading entities - `getFactionMemberCount(factionId)` - Get a faction's member count without loading entities - `resolveFactionMembers(factionId, level)` - Resolve a faction's members to live entities in one level - `setFactionLeader(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 member - `getFactionLeader(factionId)` - Get the leader record without loading the entity - `getFactionLeaderUuid(factionId)` - Get the leader's UUID - `resolveFactionLeader(factionId, server)` - Resolve the leader to a live entity, searching every dimension - `isFactionLeader(entity)` - Check whether an entity leads any faction - `getFactionByLeader(uuid)` - Find which faction an entity leads - `startRaid(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 lookup - `endRaid(level, raid, victory)` - End a raid, discarding every surviving raider - `endRaid(level, raidId, victory)` - End a raid by its instance id, discarding every surviving raider - `getRaid(level, raidId)` - Get an active raid by its instance id - `getActiveRaids(level)` - Get every active raid in a level - `getNearestRaid(level, pos, maxDistance)` - Find the nearest active raid within a distance - `getAllRaidDefinitions()` - 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 extensions can opt into a custom boss bar through `enableBossBar()`, control visibility with `shouldShowBossBar(LivingEntity)`, and provide the client-side appearance through `bossBarExtension()`. `BossBarExtension.showValueText()` enables centered `current/max` text; override `getDisplayCurrentValue(LivingEntity)` and `getDisplayMaxValue(LivingEntity)` to provide custom display values. The default value sources are the entity's health and maximum health. 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. ### Blender GLB Models and Animation Entity extensions may attach or replace an entity model with a glTF 2.0 binary asset. A model id such as `example:guardian` resolves to `assets/example/eca/blender/guardian/model.glb` plus `definition.json`. Definitions select the GLB and configure scale, translation, rotation, a default animation, default looping, and hidden node subtrees. The renderer supports indexed triangle meshes, node hierarchies, base colors and textures, transparency, `STEP`/`LINEAR` node animation, and four-influence skeletal skinning through `skins`, `inverseBindMatrices`, `JOINTS_0`, and `WEIGHTS_0`. Skinning runs on the CPU and is submitted through the normal entity render buffers. `ADDITIVE` retains the original entity model, while `REPLACE` replaces its body and normal layers without bypassing nameplates, shadows, outlines, entity lighting, depth, or shader-pack passes. Explicit animation control is server-authoritative. `playAnimation` restarts even the same named clip and synchronizes timing, speed, loop and pause state to tracking clients; paused and completed non-looping clips remain active until replaced or stopped. Playback state is transient and is not saved to entity NBT. `stopAnimation` falls back to `BlenderModelExtension.animation(entity)`, then `definition.json`'s `default_animation`, then the unanimated pose. ECA deliberately leaves skills, waits, cooldowns, hit timing and damage to the calling mod. Blender Geometry Nodes must be applied or baked to ordinary mesh data before GLB export; ECA renders the result but does not execute Blender node graphs. Sparse accessors, `JOINTS_1`/`WEIGHTS_1`, morph targets and `CUBICSPLINE` animation are not currently supported. ### 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)` 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 Packaged ECA-specific files use the common `assets//eca//` layout. Server-side definitions use the matching `data//eca//` layout, while standard Minecraft resources such as textures and language files remain in their vanilla directories. Legacy locations documented by earlier ECA versions continue to load as fallbacks. 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 End - `DreamSakuraRenderTypes` — Dream Sakura - `ForestRenderTypes` — Forest - `OceanRenderTypes` — Ocean - `StormRenderTypes` — Storm - `VolcanoRenderTypes` — Volcano - `ArcaneRenderTypes` — Arcane - `AuroraRenderTypes` — Aurora - `HackerRenderTypes` — Hacker - `StarlightRenderTypes` — Starlight - `CosmosRenderTypes` — Cosmos - `BlackHoleRenderTypes` — 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` — sketch - `SPOTLIGHT` — spotlight - `MATRIX` — matrix - `RAIN` — rain - `DESERT` — desert - `SNOW` — snow - `TOXIC` — toxic - `COSMOS` — 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: ```mcfunction /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 JSON/VSH/FSH 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 is below `assets//`, 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: ```text assets//eca/shader_presets/.fsh assets//eca/shader_presets/_block.vsh assets//eca/shader_presets/_block.json assets//eca/shader_presets/_entity.vsh assets//eca/shader_presets/_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: - `_block.*` uses `DefaultVertexFormat.BLOCK`, for skybox, plane preview, and Boss bar rendering. - `_entity.*` uses `DefaultVertexFormat.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///project.json`. Use **File -> Export As ** to export a runtime-loadable five-file preset into `config/eca/shadergenerator///`. ECA automatically discovers presets from both mod assets and exported config presets. A preset ID is always `:`. For mod-packaged presets, place the five files under `src/main/resources/assets//eca/shader_presets/`. The legacy `assets//shaders/core/` location remains supported, but the canonical ECA path takes precedence when both define the same ID. 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: ```bash codex mcp add eca_shader --url http://localhost:8767/mcp ``` ##### Claude Code Register from the command line: ```bash 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 | | `ENTER` | Save recording | | `ESC` | Discard recording | **Editor Workflow**: 1. Run `/eca bossShow edit` near at least one LivingEntity (within 64 blocks). 2. 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. 3. Configure trigger type (Range / Custom), target entity type, cinematic bars, allow repeat, etc. 4. Click **● Record**, press `J` to start. Move the camera freely in spectator mode — each tick is captured as an editable frame. 5. After recording, click any tick on the timeline to edit its position and camera orientation. Use **Preview** to play the timeline. To resample a pose from the game view, click **Free camera write**, move the camera, then press Enter to write the current tick. 6. Press `ENTER` to save, `ESC` to discard. Saved files go to `config/eca/bossshow//.json`. **Timeline editing** (after recording, in the editor GUI): every frame is selectable. Click or drag the bar to scrub, use the arrow keys for single-tick navigation, and edit `dx/dy/dz/yaw/pitch` in the inspector. Use **Add content** to attach an event or subtitle to the current tick. **Set In** / **Set Out**, **Copy** / **Cut** / **Delete**, **Paste**, uniform/ramp range offsets, and `Ctrl+Z` / `Ctrl+Y` are available for timeline editing. Flight inertia is disabled during recording by default. Enable `BossShow -> Enable Recording Flight Inertia` in the configuration to preserve vanilla inertia. **For Mod Developers** Two ways to define a cutscene: 1. **JSON only** — place a file at `data//eca/bossshow/.json`. Loaded automatically on startup. The legacy `data//bossshow/.json` location remains supported. No Java code is needed if you don't need server-side event handling. 2. **Java + JSON** — extend `BossShow` and annotate with `@RegisterBossShow` to get server-side event callbacks during playback. JSON example — `frames` are generated by the recorder; `events` and `subtitles` are independent tick-based content tracks: ```json { "format_version": 3, "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" } }, { "dx": 0.0, "dy": 1.8, "dz": -4.0, "yaw": 8.0, "pitch": 10.0, "keyframe": { "event_id": "finisher" } } ], "events": [ { "tick": 20, "event_id": "intro" }, { "tick": 40, "event_id": "finisher" } ], "subtitles": [ { "tick": 20, "text": "mymod.bossshow.warden.intro" } ], "effects": [ { "tick": 20, "type": "camera_shake", "duration": 16, "fade_out": 8, "parameters": { "yaw": 1.5, "pitch": 1.0, "roll": 0.5, "frequency": 1.2 } }, { "tick": 40, "type": "shader_effect", "effect": "chromatic_aberration", "duration": 30, "fade_in": 3, "fade_out": 10, "parameters": { "strength": 0.012, "angle": 15.0, "pulse_amount": 0.4, "pulse_speed": 2.0 } } ] } ``` - `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). - `events`: independent event track. Each entry contains a `tick` and optional `event_id`; entries are delivered to `BossShow.onKeyframeEvent()` at that tick. - `subtitles`: independent subtitle track. Each entry contains a `tick` and optional `text`; text may be literal or a translation key. - `effects`: client-side screen-effect track with duration, fade, easing, and effect-specific parameters. Types are `camera_shake`, `shader_effect`, and `filter`. - `frames[].keyframe`: legacy compatibility field. New files should use `events` and `subtitles` instead. - `trigger`: `{"type":"range","effect_radius":N}` auto-triggers when a player enters range of a matching entity. `{"type":"custom","event_name":"..."}` only fires via `EcaAPI.launchBossShowEvent(...)`. > The old `samples` + `markers` format is no longer recognized — files using it load as zero-frame cutscenes. Re-record or migrate to `frames`. 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 `@RegisterBossShow` class has no matching JSON on first launch, an empty template JSON is auto-generated at `config/eca/bossshow//.json`. **For Modpack Developers** - **Override cutscenes** — place your modified JSON at `config/eca/bossshow//.json`. Config files override mod-bundled definitions (`data//eca/bossshow/`, with legacy support for `data//bossshow/`) with the same id. - **Edit in-game** — `/eca bossShow edit` lets you re-record camera paths, edit any tick's pose, adjust triggers, add event/subtitle cues, and operate on frame ranges (copy / cut / delete / paste). Saves go to `config/eca/bossshow/`, leaving the mod jar untouched. - **Translate or rewrite subtitles** — create `config/eca/bossshow/lang/.json` (e.g. `en_us.json`, `zh_cn.json`). These take priority over the mod's own lang files for subtitle keys: ```json { "mymod.bossshow.warden.intro": "A sound echoes from the deep..." } ``` - **Hot reload** — `/eca bossShow reload` picks 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 targeted - `FRIENDLY` — different factions but allied, no damage and no targeting - `NEUTRAL` — not deliberately targeted, but incidental damage still applies - `HOSTILE` — 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: 1. Same faction id → `SAME_FACTION` 2. A's `getRelation(self, target)` conditional override 3. A's static `hostileTo` / `friendlyTo` / `neutralTo` arrays 4. Symmetric fallback — the same two checks evaluated from B's side 5. A's `getDefaultRelation(self, target)` conditional override (only when the other side has no faction) 6. 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 only owns membership, relations and leadership. `FactionDefinition` does not provide entity composition or spawn-weight APIs; those values belong to the system that performs the spawning, so different systems can use the same faction without sharing spawn rules. **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` (default `true`) - `Immediate Leader Protection` (default `false`) - `Alert Enabled` (default `true`) / `Alert Range` (default `32`) / `Immediate Member Alert` (default `false`) "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 `addFaction(String factionId, int count, Map