Files
ForgeMultipart/docs/api/OCCLUSION.md
T

5.1 KiB

Testing part occlusion from Java

API index

The algent/java branch adds TileMultipart.testOcclusion(Collection<? extends TMultiPart>, TMultiPart). It tests a supplied collection of parts through the tile's existing occlusion hook, including generated partial-occlusion checks. This addition is not yet tied to a minimum published dependency version.

Example

Use a NormallyOccludedPart to test a normal bounding shape against a supplied group of parts:

public static boolean fitsShape(TileMultipart tile, Collection<? extends TMultiPart> parts, Cuboid6 bounds) {
    return tile.testOcclusion(parts, new NormallyOccludedPart(bounds));
}

The complete Java example compiles and runs in the test suite. It also compiles with Scala excluded from its classpath and has no Scala bytecode references. FMP still retains its Scala runtime internally. Collections of part subclasses, such as List<NormallyOccludedPart>, are accepted without a cast.

Pass tile.jPartList() to test against the tile's currently stored parts, or pass a separate collection for a preview or selected subset. To exercise a real candidate's custom occlusion behavior, pass that part instead of the shape wrapper. The wrapper represents normal bounding geometry, not all the capabilities of a custom part.

What the query checks

The base tile tests each supplied part against the candidate, then tests the candidate against that same part. It proceeds in input iteration order and stops immediately when either direction rejects. Unbound/detached parts and duplicate entries participate; the candidate is not automatically excluded if it is also in the collection.

Generated partial-occlusion tiles retain their extra aggregate check. When the candidate implements JPartialOcclusion, the existing trait checks the group plus the candidate before the ordinary pair callbacks. A plain TileMultipart does not gain that capability just because the supplied parts need it. Use the appropriate composite tile; this method does not generate or select tile traits.

This is an occlusion query, not full placement approval. It does not check placement permissions, duplicate membership, slot occupancy or world replaceability, and does not bind/store parts or send notifications. For ordinary placement and replacement, continue using canPlacePart, canAddPart and canReplacePart as appropriate. In particular, canReplacePart(outgoing, candidate) performs its existing outgoing-part exclusion; this collection query does not.

Ownership, callbacks and failures

The Java entry copies the input into an immutable sequence before calling the legacy hook. Order, duplicates, null entries and part identities are preserved. Callback edits to the caller's collection do not change the current query's sequence. Part objects are shared, so mutations of their state can affect later callbacks. A nested query takes its own copy.

Copying failures, including a null collection, propagate before the legacy hook is called. There is no eager candidate/null-entry validation: with the base implementation, an empty collection accepts even a null candidate, and an early rejection can avoid a later null dereference. Callback exceptions propagate unchanged and stop the query; callback side effects already performed are not undone. Overrides may add checks or customize these boundaries.

Use the game thread when querying live parts. A geometric query may invoke arbitrary part callbacks, including ones that read or modify world/part state; it is not a thread-safety guarantee.

Overrides and migration

Dispatch is testOcclusion(Collection, candidate) → virtual occlusionTest(scala.collection.Seq, candidate). The old method keeps its descriptor and body, and is deprecated for callers. Existing subclasses and generated traits continue to override it. canAddPart and canReplacePart still call that hook directly; overriding only the new Java method does not intercept their checks. The distinct Java name avoids introducing another Scala/Java overload pair, following the loader compilation finding.

No direct consumer calls or overrides of the two-argument tile hook were found in the supplied source/ABI audit. Consumers such as ProjectRed and ForgeRelocationFMP already use the Java-typed canReplacePart API and need no rename for that contract. Part-level occlusionTest(TMultiPart) remains supported as-is. The Scala NormalOcclusionTest.apply box-versus-box entry is a separate, still-used contract whose Java replacement remains pending.

Validation

JVM tests cover both directions, early rejection, original exceptions, null boundaries, snapshot ownership, duplicates, subtype/non-list collections, override routing and the shape example. A Forge test demonstrates that the generated partial trait rejects overlapping partial parts that plain pair tests accept. Existing descriptors, generated output, and frozen pre-change callers are checked separately. Actual client previews and full-pack adoption remain covered by the manual release checks.