Files
Algent 6ebec208df feat: add TileMultipart.refreshInventorySlots for AE2
AE2 casts the tile to the raw scalatraits TIInventoryTile to call
rebuildSlotMap after its cable bus inventory size changes. Give it a
supported entry point on TileMultipart instead.

- refreshInventorySlots is a no-op on base tiles; the generated inventory
  trait rebuilds its flattened slot mapping without rebinding parts
- rebuildSlotMap stays supported for released AE2 jars
- Functional tests cover resize, tiles without inventories, and a
  slotted plus inventory tile; smoke test freezes the new trait method
- Document it in the tile trait guide, API index and adoption ledger
2026-10-16 15:17:42 +02:00

13 KiB

ForgeMultipart Java API

Start here when integrating with ForgeMultipart for Minecraft 1.7.10 / Forge. This describes the algent/java migration branch. Its new APIs are implemented and tested but do not yet have a minimum published dependency version. Use a dev artifact containing the methods you need; source Javadocs are packaged with the sources artifact.

Consumers can use the Java surface without importing Scala types. FMP itself still needs Scala at runtime. A Scala-authored mod can adopt the Java API without converting the rest of its code to Java.

Every guide links a compiling example. ./gradlew compileJavaApiExamples (also run by check) compiles all of them as Java 8 against the packaged dev jar with no scala-* library on the classpath, so a documented example that needs Scala fails the build. Add new documented examples to the test or functional-test examples packages.

Rebuilding an existing mod against this branch? Read the release notes first. Shipped binaries keep working, but recompiling has three classes of break, and the trait-composition one compiles cleanly and changes behavior at runtime. Use the consumer migration checklist to carry a source change through runtime validation, release and target-pack adoption.

Direct calls and optional integration

The intended consumer API uses direct typed public calls, without reflection, private-field mixins or Scala companion lookup. Reuse existing entry points where they cover the task. For optional support, isolate FMP-typed code in a compatibility class loaded only after mod-presence and supported-version checks; test absent and present mod loading. Reflection snippets in older guides are legacy interoperability options, not the migration target. Existing reflective binaries remain supported until consumer release and pack adoption.

Choose a guide

Task API and guide
Define, place, persist, synchronize and remove a custom part TMultiPart, canPlacePart, addPart, remPart - end-to-end custom-part lifecycle
Register block converters registerConverter(IPartConverter) - registration, state ownership and committed lifecycle
Register part factories during mod initialization registerPartFactory(IPartFactory2, String...) - factory timing, payload ownership and migration
Find the factory registered for a part type getPartFactory(String) - lookup ownership and Schematica reflection migration
Enumerate microblock materials by numeric ID materialCount(), materialName(int), getMaterial(int) - material enumeration
Read a microblock material's block and metadata BlockMicroMaterial.block(), meta() - typed GuideNH query, identity and overrides
Create a microblock with the requested material and side MicroblockGenerator.create(MicroblockClass, int, boolean) - construction, material traits and GuideNH migration
Call generated tile capabilities from Java TileMultipart, IRedstoneTile and ordinary interfaces - safe compilation and raw-trait pitfalls
Add aggregate or lifecycle behavior to generated multipart tiles registerTrait(marker, trait) - custom Java tile traits, stable capabilities and transformer constraints
Refresh slots after a stored part changes shape refreshPartSlots(TMultiPart) - ownership, equality and notification responsibilities
Refresh the tile inventory after a stored part changes its inventory size refreshInventorySlots() - mapping, scope and AE2 migration
Add material-specific behavior to generated microblocks registerTrait(String) and IGeneratedMaterial - illuminated Java extension, compilation and side contracts
Access built-in saws and change their cutting strength MicroblockProxy.sawStone() and ItemSaw.setHarvestLevel(int) - state, rendering and Iguana migration
Read/index/search a tile's parts jPartList() - part collection ownership and order
Run callbacks only on parts still owned by the tile forEachPart(Consumer) - callback and override behavior
Rebuild parts on an already prepared composite tile loadPartList(Collection) - part loading
Select client/server tile capabilities before preparing state and loading MultipartGenerator.generateCompositeTile(TileEntity, Iterable, boolean) - staged generation and reflection migration
Build and render an uninstalled client tile from saved multipart NBT MultipartHelper.createPreviewTileFromNBT(World, NBTTagCompound) - client preview lifecycle and reflection migration
Assign stored parts during reconstruction, without binding or notifications setPartList(List) - storage assignment
Test a candidate against a selected collection of parts testOcclusion(Collection, candidate) - occlusion queries and generated hooks
Test two groups of bounding boxes directly NormalOcclusionTest.testBoxes(Iterable, Iterable) - box-versus-box queries
Read FMP's global render registration ID TileMultipart.getRenderID() - render-ID meaning, lifecycle and setter
Find which part a ray-trace hit struck BlockMultipart.hitPartIndex(MovingObjectPosition) - index meaning, staleness and GT5U migration
Inspect an existing tile or converted placeholder with a named result getOrConvertTileResult(World, BlockCoord) - conversion outcomes and placement lifecycle
Add or remap multipart button attachment orientations ButtonPart.setOrientation(int, ForgeDirection) - metadata, face mapping and Et Futurum migration
Read a custom button's orientation metadata from its attachment face ButtonPart.metaForSide(int) - live inverse lookup and ProjectRed migration

Each guide explains ownership, lifecycle, legacy replacements and limitations. The loading/setter APIs are advanced reconstruction operations; ordinary placement and removal should use the existing world APIs below.

Existing Java entry points

These entry points already exist. The links lead to their source/Javadocs; detailed migration guides and checks for compilation without Scala are still pending in these areas. A Java-typed overload can still require Scala on the compile classpath for overload resolution, as the loading guide explains.

Area Starting points
Construct a server composite tile from parts, or restore saved multipart NBT MultipartHelper: createTileFromParts(Iterable), createTileFromNBT(World, NBTTagCompound)
Register microblock materials MicroMaterialRegistry and BlockMicroMaterial
Register generated tile traits or pass-through interfaces MultipartGenerator; see custom tile-trait authoring and safe capability access

New API examples use Java 8 language/library features. The source path src/main/scala also contains Java classes because of the current joint-compilation layout; it does not imply that those APIs require Scala source in a consumer.

Supported API and internal hooks

Public visibility alone does not make a method a consumer entry point. Scala's former package-private methods are public in bytecode. Source Javadocs now mark the following audited implementation hooks Internal FMP; keep consumer code on the public operations in the right column. These markers change no visibility, descriptor or behavior and do not deprecate or authorize removing a method. Trait callbacks and generated overrides still run.

Internal hooks Consumer operation or responsibility
TileMultipart.addPart_impl, addPart_do, writeAddPart Place through TileMultipart.addPart(world, pos, part); use part loading for prepared reconstruction
TileMultipart.remPart_impl Remove through tile.remPart(part) on the server and retain the returned tile
TileMultipart.partAdded, partRemoved Lifecycle callbacks for trait overrides; callers use placement/removal APIs
TileMultipart.from, copyFrom, loadFrom, setValid FMP composite transitions; use staged generation and loading for consumer reconstruction
MicroMaterialRegistry.setupIDMap, calcMaxCuttingStrength FMP initializes IDs and cutting strength; register materials at initialization and read the resulting values
MicroMaterialRegistry.loadIcons FMP dispatches client texture callbacks; override the supported IMicroMaterial.loadIcons() material callback
MicroMaterialRegistry.writeIDMap, readIDMap FMP's whole-map handshake; part packets use writeMaterialID / readMaterialID for individual IDs

The registry's matching companion methods carry the same internal marker. This is a bounded consumer audit, not a claim that every other public member is a supported API. See TileMultipart's Javadocs and registry Javadocs for method-specific details.

Four advanced methods remain supported because consumers use them directly:

  • bindPart(part) updates capability caches through generated overrides. It does not insert the part, change its tile binding or perform placement/notifications. The legacy OpenComputers integration clears a print part's old slot entries before calling it to populate a changed slot mask. A repeated call does not clear obsolete slots and may append entries in other trait caches. Use refreshPartSlots for that migration; full reconstruction uses loadPartList.
  • refreshPartSlots(part) replaces OpenComputers' direct generated-array mutation. Generated slotted tiles clear equal cached entries and invoke the virtual bind chain once; base tiles do nothing. It leaves ownership, storage, validation and notifications to the caller. See the slot-refresh contract.
  • refreshInventorySlots() replaces AE2's direct TIInventoryTile.rebuildSlotMap() call. Generated inventory tiles recompute their flattened slot mapping from the inventories they already track; other tiles do nothing. It does not bind parts or notify. See refreshing inventory slots.
  • internalPartChange(part) sends local onPartChanged callbacks through the retained operate hook. The base traversal captures list order, visits only parts still bound to the receiving tile, and excludes parts equal to the changed part using part.equals(p). Null broadcasts to all eligible parts. Detached or transferred parts are skipped. Callback failures stop traversal and propagate. ProjectRed deliberately handles dirty state, packets and external neighbors separately; this method does none of those. notifyPartChange also performs world update/neighbor/lighting notifications when needed.

No reflection is needed to call any of these methods. operate and getOrConvertTile2 retain their documented legacy contracts and Java replacements; neither receives an internal-only marker. Removing those bridges still requires consumer and internal migration gates. The consumer audit records the checked source calls and validation.

Legacy entry points and project status

Deprecated Scala-facing entry points remain callable, with their descriptors and supported override dispatch retained. Follow the method-specific guide: a Java sibling is not automatically a replacement override hook, and reflection must select the intended parameter types when a method is overloaded.

Remaining work, consumer adoption and the Scala-removal gates are tracked in the migration status.

Need Document
What is complete and what comes next? Migration status
Which consumers need changes, releases and pack adoption? Consumer adoption ledger
Which binary names, reflective lookups and runtime contracts must survive? ABI inventory and consumer audit
What has been tested and what still needs a client/pack run? Migration status, manual release checks
Why is some Scala or older Java syntax retained? Build boundaries, compiler constraints