Assembly System (Structured Display)
The assembly system is EmakiCoreLib's structured item display engine, responsible for managing layered rendering of item names and lore. Multiple modules can each inject display layers into the same item, and the system merges and renders them by priority into the final item name and lore.
Core Concepts
Namespace
A namespace identifies the source module of a display layer. Each namespace has a unique ID and a priority weight. The weight determines the ordering of lore sections (lower values appear first).
Layer Snapshot
A layer snapshot is a complete description of an item's display contribution from a given namespace at a point in time, containing name contributions and lore sections.
Assembly Request
An assembly request combines multiple layer snapshots together and submits them to the assembly service for merging and rendering into the final ItemStack.
Pre-registered Namespaces
CoreLib pre-registers the following namespaces, which other modules can use directly:
| Namespace ID | Weight | Purpose |
|---|---|---|
forge | 100 | Forging / enhancement base attributes |
strengthen | 200 | Enhancement additional attributes |
gem | 300 | Gem socketing |
cooking | 10000 | Cooking system (highest weight, lore appears last) |
Tip
Weight values have gaps between them to allow other modules to insert custom namespaces in between. For example, a weight of 150 would be placed between forge and strengthen.
EmakiItemLayerSnapshot
EmakiItemLayerSnapshot is a record class that describes a single namespace's contribution to an item's display:
| Field | Type | Description |
|---|---|---|
namespaceId | String | Namespace ID |
schemaVersion | int | Data structure version number for migration |
audit | AuditInfo | Audit information (creation time, modification time, source) |
stats | Map<String, Object> | Statistical data (e.g., enhancement level, gem count) |
structuredPresentation | EmakiStructuredPresentation | Structured presentation data |
EmakiItemLayerSnapshot snapshot = new EmakiItemLayerSnapshot(
"forge", // namespaceId
1, // schemaVersion
AuditInfo.now("emaki-forge"), // audit
Map.of("level", 5), // stats
presentation // structuredPresentation
);EmakiStructuredPresentation
EmakiStructuredPresentation defines a layer's specific contribution to an item's name and lore:
| Field | Type | Description |
|---|---|---|
baseNamePolicy | BaseNamePolicy | Base name policy |
baseNameTemplate | String | Name template (only used with EXPLICIT_TEMPLATE policy) |
nameContributions | List<NameContribution> | Name contribution list (prefix/suffix) |
loreSections | List<LoreSection> | Lore section list |
EmakiStructuredPresentation presentation = EmakiStructuredPresentation.builder()
.baseNamePolicy(BaseNamePolicy.SOURCE_EFFECTIVE_NAME)
.nameContribution(NameContribution.prefix("<red>[+5] "))
.nameContribution(NameContribution.suffix(" <gray>(锻造)"))
.loreSection(LoreSection.of(
"<gray>───── 锻造属性 ─────",
"<white>攻击力:<red>+15",
"<white>暴击率:<gold>+5%",
"<gray>─────────────────"
))
.build();BaseNamePolicy Enum
| Enum Value | Description |
|---|---|
SOURCE_EFFECTIVE_NAME | Use the item source's actual display name (default) |
SOURCE_TRANSLATABLE | Use the item's translatable name (client-side localization) |
EXPLICIT_TEMPLATE | Use the template specified by baseNameTemplate |
// Use the item's original name
EmakiStructuredPresentation.builder()
.baseNamePolicy(BaseNamePolicy.SOURCE_EFFECTIVE_NAME)
.build();
// Use the translatable name
EmakiStructuredPresentation.builder()
.baseNamePolicy(BaseNamePolicy.SOURCE_TRANSLATABLE)
.build();
// Use a custom template
EmakiStructuredPresentation.builder()
.baseNamePolicy(BaseNamePolicy.EXPLICIT_TEMPLATE)
.baseNameTemplate("<gradient:gold:red>传说之剑</gradient>")
.build();Usage Flow
The typical flow for other modules using the assembly system:
1. Register a Namespace
NamespaceRegistry registry = EmakiServiceRegistry.get(NamespaceRegistry.class);
// Register a custom namespace (if not using pre-registered ones)
registry.register("enchant_plus", 250); // Weight 250, placed between strengthen and gem2. Build a Layer Snapshot
EmakiStructuredPresentation presentation = EmakiStructuredPresentation.builder()
.baseNamePolicy(BaseNamePolicy.SOURCE_EFFECTIVE_NAME)
.nameContribution(NameContribution.prefix("<light_purple>[附魔+] "))
.loreSection(LoreSection.of(
"",
"<light_purple>✦ 附魔增强",
"<white> 火焰附加 III",
"<white> 锋利 V",
""
))
.build();
EmakiItemLayerSnapshot snapshot = new EmakiItemLayerSnapshot(
"enchant_plus",
1,
AuditInfo.now("my-plugin"),
Map.of("enchant_count", 2),
presentation
);3. Create an Assembly Request
AssemblyService assemblyService = EmakiServiceRegistry.get(AssemblyService.class);
AssemblyRequest request = AssemblyRequest.builder()
.baseSource("craftengine-iron_longsword")
.layer(snapshot)
.layer(forgeSnapshot) // Multiple layers can be stacked
.layer(gemSnapshot)
.build();4. Preview or Give the Item
// Preview — only generates the ItemStack without modifying the player's inventory
ItemStack preview = assemblyService.preview(request);
// Give — generates and places into the player's inventory
assemblyService.give(request, player);Rendering Result Example
Assuming a sword has three layers — forge, gem, and cooking — the final rendering result:
Item Name: [+5] Iron Longsword (Forged)
Lore:
───── Forge Attributes ───── ← forge (weight 100)
Attack Power: +15
Crit Rate: +5%
─────────────────
◆ Gem Sockets ← gem (weight 300)
[Ruby] Attack Power +3
[Sapphire] Mana +10
───── Cooking Effects ───── ← cooking (weight 10000)
Restores 5 hunger points when consumed
─────────────────PDC Data Structure
The assembly system persists layer snapshot data into the item's PersistentDataContainer with the key format:
emaki:assembly.<namespaceId>Data is stored as YAML serialization with the following structure:
# PDC internal storage format (not directly editable)
emaki:assembly.forge:
schema_version: 1
audit:
created_at: 1700000000000
modified_at: 1700000000000
source: "emaki-forge"
stats:
level: 5
presentation:
base_name_policy: "SOURCE_EFFECTIVE_NAME"
name_contributions:
- type: PREFIX
value: "<red>[+5] "
- type: SUFFIX
value: " <gray>(锻造)"
lore_sections:
- lines:
- "<gray>───── 锻造属性 ─────"
- "<white>攻击力:<red>+15"
- "<white>暴击率:<gold>+5%"
- "<gray>─────────────────"Cache
The assembly service has a built-in rendering cache to avoid redundant rendering for identical inputs:
| Parameter | Value |
|---|---|
| Max Entries | 128 |
| TTL (Time to Live) | 30 seconds |
| Eviction Policy | LRU (Least Recently Used) |
Tip
The cache key is computed based on the hash values of all layer snapshots. When any layer changes, the cache is automatically invalidated. The 30-second TTL ensures performance in high-frequency operation scenarios while keeping data up to date.
Warning
The cache is only for rendering results. Layer snapshot data is always persisted in the item's PDC and is not affected by the cache. The cache is cleared after a server restart, but item data is not lost.