Skip to content

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 IDWeightPurpose
forge100Forging / enhancement base attributes
strengthen200Enhancement additional attributes
gem300Gem socketing
cooking10000Cooking 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:

FieldTypeDescription
namespaceIdStringNamespace ID
schemaVersionintData structure version number for migration
auditAuditInfoAudit information (creation time, modification time, source)
statsMap<String, Object>Statistical data (e.g., enhancement level, gem count)
structuredPresentationEmakiStructuredPresentationStructured presentation data
java
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:

FieldTypeDescription
baseNamePolicyBaseNamePolicyBase name policy
baseNameTemplateStringName template (only used with EXPLICIT_TEMPLATE policy)
nameContributionsList<NameContribution>Name contribution list (prefix/suffix)
loreSectionsList<LoreSection>Lore section list
java
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 ValueDescription
SOURCE_EFFECTIVE_NAMEUse the item source's actual display name (default)
SOURCE_TRANSLATABLEUse the item's translatable name (client-side localization)
EXPLICIT_TEMPLATEUse the template specified by baseNameTemplate
java
// 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

java
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 gem

2. Build a Layer Snapshot

java
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

java
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

java
// 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:

yaml
# 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:

ParameterValue
Max Entries128
TTL (Time to Live)30 seconds
Eviction PolicyLRU (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.

Released under the GPL-3.0 License