diff --git a/JAVA_MIGRATION.md b/JAVA_MIGRATION.md index c038bc0..b1f1ebb 100644 --- a/JAVA_MIGRATION.md +++ b/JAVA_MIGRATION.md @@ -455,4 +455,7 @@ These ranges were formed before designing the characterization suite and should - Removed `IconHitEffects$` and its `MODULE$` field. No jar in the pack references it, in bytecode or by name, so it is dead under the inventory rule. This is the first public class the migration has dropped rather than a method, and it contrasts with `NormalOcclusionTest$`, which is referenced and was kept. - Preserved evaluation order inside `addDestroyEffects` rather than hoisting the tile read, because the characterization pins which work happens before a null tile fails. - Established that particle appearance cannot be asserted headless. Icon selection, side order and bounds scaling are covered by tests; the visual result stays on the manual checklist. +- Ported `ItemMultiPart.scala` into `JItemMultiPart`, `TItemMultiPart` and `TItemMultiPart$class`. The interface is descriptor-identical to the reference, `JItemMultiPart` gains only additive statics, and the bridge keeps all three public statics for ProjRed. +- Generalised the default-versus-abstract rule. `TItemMultiPart` extends `Item` rather than `TMultiPart`, and the same split applied: `getHitDepth` became a default because `Item` does not declare it, while `onItemUse` stayed abstract because `Item` does. What matters is the implementor's actual superclass, not `TMultiPart` specifically. +- Confirmed that placement logic is testable headless. `onItemUse` short-circuits as soon as `newPart` returns null, before any world, player or stack access, so the depth branch, the neighbour offset and the shared mutated `BlockCoord` can all be asserted with null arguments. - Added the first functional coverage of runtime class generation interacting with a ported type. Microblock classes are built by ASM at run time, so a plain-JVM test cannot show that a generated class resolves a Java interface default; the Forge server suite now generates a `FaceMicroblock` and asserts it inherits `redstoneConductionMap`, and generates a `HollowMicroblock` and asserts its own `0x10` and `false` still win. diff --git a/JAVA_MIGRATION_DIVERGENCES.md b/JAVA_MIGRATION_DIVERGENCES.md index 6c18312..c9a197f 100644 --- a/JAVA_MIGRATION_DIVERGENCES.md +++ b/JAVA_MIGRATION_DIVERGENCES.md @@ -358,3 +358,56 @@ loop produces no replacement class. - Clean `spotlessApply checkstyleTest build`: passing. - Java 8 Forge dedicated-server suite: 3 tests, 0 failures, 0 errors. - Particle appearance itself needs a client and stays on the manual checklist. + +## 2026-08-14 — TItemMultiPart Java port + +Covers `JItemMultiPart`, `TItemMultiPart` and `TItemMultiPart$class`, which shared `ItemMultiPart.scala`. + +### Observable behavior + +No known divergence. `getHitDepth` still projects a copy of the hit vector onto the axis for that side and adds 1 on +even sides, leaving the caller's vector untouched. `onItemUse` still tries the clicked block only when the depth is +below 1, then always tries the neighbour on that side, reusing and mutating a single `BlockCoord` across both attempts +and passing the same `Vector3` to every `newPart` call. Placement still short-circuits on a null part before touching +the world, still skips `addPart` on the client, and still decrements the stack only outside creative mode. + +### First trait extending a Minecraft class rather than TMultiPart + +`TItemMultiPart extends Item`, so the linearization check runs against `Item` instead of `TMultiPart`. The outcome is +the same split seen in the `TIconHitEffects` port: + +- `getHitDepth` becomes a real `default`. `Item` does not declare it, so nothing in an implementor's superclass chain + can beat it. +- `onItemUse` stays abstract. `Item` declares it, so a default would silently lose to the vanilla implementation and + placement would stop working. + +This confirms the rule generalises: what matters is the implementor's actual superclass, not `TMultiPart` specifically. + +There were no in-repo implementors beyond `JItemMultiPart` itself, so nothing else needed changing. + +### Supported JVM API + +- `TItemMultiPart` is descriptor-identical to the reference. +- `JItemMultiPart` keeps its constructor, both instance methods and their descriptors; WR-CBE links against the + constructor and the inherited `func_77658_a`. It gains two public statics, `hitDepth` and the `onItemUse` overload + taking the part, plus a private `place`. All additive. +- `TItemMultiPart$class` keeps all three public statics for ProjRed, its only consumer. It adds a private constructor + and drops the reference's private `place$1` helper, which was never visible to consumers. +- `getHitDepth` on the bridge ignores its part argument and calls the static directly, matching the reference, whose + trait body never used `this`. The `onItemUse` path still dispatches `getHitDepth` through the interface, so an + implementor's override is honoured exactly as before. + +### Compiler artifacts + +None removed or added. The emitted class-file set for this area is unchanged; the local `place` function compiled to a +private static method rather than a closure class in both versions. + +### Validation + +- `ItemMultiPartCharacterizationTest`: 7 tests, 0 failures, 0 errors, unchanged from the Scala baseline. +- `TItemMultiPartBinaryCompatibilityTest`: 1 test, 0 failures, 0 errors, driving a reference-compiled Scala consumer + through both attempt positions. +- Complete plain-JVM suite: 57 tests, 0 failures, 0 errors. +- Clean `spotlessApply checkstyleTest build`: passing. +- Java 8 Forge dedicated-server suite: 3 tests, 0 failures, 0 errors. +- Real placement into a world needs a client or a server scenario and stays on the functional and manual layers. diff --git a/src/main/scala/codechicken/multipart/ItemMultiPart.scala b/src/main/scala/codechicken/multipart/ItemMultiPart.scala deleted file mode 100644 index dbf640b..0000000 --- a/src/main/scala/codechicken/multipart/ItemMultiPart.scala +++ /dev/null @@ -1,68 +0,0 @@ -package codechicken.multipart - -import net.minecraft.item.Item -import codechicken.lib.vec.Vector3 -import codechicken.lib.vec.Rotation -import net.minecraft.item.ItemStack -import net.minecraft.entity.player.EntityPlayer -import net.minecraft.world.World -import codechicken.lib.vec.BlockCoord - -/** Java class implementation - */ -abstract class JItemMultiPart extends Item with TItemMultiPart - -/** Simple multipart item class for easy placement. Simply override the newPart - * function and it the part will be added to the block space if it passes the - * occlusion tests. - */ -trait TItemMultiPart extends Item { - def getHitDepth(vhit: Vector3, side: Int): Double = - vhit.copy.scalarProject(Rotation.axes(side)) + (side % 2 ^ 1) - - override def onItemUse( - item: ItemStack, - player: EntityPlayer, - world: World, - x: Int, - y: Int, - z: Int, - side: Int, - hitX: Float, - hitY: Float, - hitZ: Float - ): Boolean = { - val pos = new BlockCoord(x, y, z) - val vhit = new Vector3(hitX, hitY, hitZ) - val d = getHitDepth(vhit, side) - - def place(): Boolean = { - val part = newPart(item, player, world, pos, side, vhit) - if (part == null || !TileMultipart.canPlacePart(world, pos, part)) - return false - - if (!world.isRemote) - TileMultipart.addPart(world, pos, part) - if (!player.capabilities.isCreativeMode) - item.stackSize -= 1 - return true - } - - if (d < 1 && place()) - return true - - pos.offset(side) - return place() - } - - /** Create a new part based on the placement information parameters. - */ - def newPart( - item: ItemStack, - player: EntityPlayer, - world: World, - pos: BlockCoord, - side: Int, - vhit: Vector3 - ): TMultiPart -} diff --git a/src/main/scala/codechicken/multipart/JItemMultiPart.java b/src/main/scala/codechicken/multipart/JItemMultiPart.java new file mode 100644 index 0000000..91a9a5b --- /dev/null +++ b/src/main/scala/codechicken/multipart/JItemMultiPart.java @@ -0,0 +1,58 @@ +package codechicken.multipart; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import codechicken.lib.vec.BlockCoord; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; + +/** Java class implementation of {@link TItemMultiPart}, and the canonical home of its behavior. */ +public abstract class JItemMultiPart extends Item implements TItemMultiPart { + + @Override + public double getHitDepth(Vector3 vhit, int side) { + return hitDepth(vhit, side); + } + + @Override + public boolean onItemUse(ItemStack item, EntityPlayer player, World world, int x, int y, int z, int side, + float hitX, float hitY, float hitZ) { + return onItemUse(this, item, player, world, x, y, z, side, hitX, hitY, hitZ); + } + + public static double hitDepth(Vector3 vhit, int side) { + return vhit.copy().scalarProject(Rotation.axes[side]) + (side % 2 ^ 1); + } + + public static boolean onItemUse(TItemMultiPart part, ItemStack item, EntityPlayer player, World world, int x, int y, + int z, int side, float hitX, float hitY, float hitZ) { + BlockCoord pos = new BlockCoord(x, y, z); + Vector3 vhit = new Vector3(hitX, hitY, hitZ); + + if (part.getHitDepth(vhit, side) < 1 && place(part, item, player, world, pos, side, vhit)) { + return true; + } + + pos.offset(side); + return place(part, item, player, world, pos, side, vhit); + } + + private static boolean place(TItemMultiPart part, ItemStack item, EntityPlayer player, World world, BlockCoord pos, + int side, Vector3 vhit) { + TMultiPart newPart = part.newPart(item, player, world, pos, side, vhit); + if (newPart == null || !TileMultipart.canPlacePart(world, pos, newPart)) { + return false; + } + + if (!world.isRemote) { + TileMultipart.addPart(world, pos, newPart); + } + if (!player.capabilities.isCreativeMode) { + item.stackSize -= 1; + } + return true; + } +} diff --git a/src/main/scala/codechicken/multipart/TItemMultiPart$class.java b/src/main/scala/codechicken/multipart/TItemMultiPart$class.java new file mode 100644 index 0000000..a8133fd --- /dev/null +++ b/src/main/scala/codechicken/multipart/TItemMultiPart$class.java @@ -0,0 +1,28 @@ +package codechicken.multipart; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import codechicken.lib.vec.Vector3; + +/** + * Binary bridge for Scala implementations compiled against the original trait. Their forwarders call these statics + * directly, so the bodies cannot delegate back to the instance methods without recursing. + */ +@Deprecated +public abstract class TItemMultiPart$class { + + private TItemMultiPart$class() {} + + public static double getHitDepth(TItemMultiPart part, Vector3 vhit, int side) { + return JItemMultiPart.hitDepth(vhit, side); + } + + public static boolean onItemUse(TItemMultiPart part, ItemStack item, EntityPlayer player, World world, int x, int y, + int z, int side, float hitX, float hitY, float hitZ) { + return JItemMultiPart.onItemUse(part, item, player, world, x, y, z, side, hitX, hitY, hitZ); + } + + public static void $init$(TItemMultiPart part) {} +} diff --git a/src/main/scala/codechicken/multipart/TItemMultiPart.java b/src/main/scala/codechicken/multipart/TItemMultiPart.java new file mode 100644 index 0000000..596f5ac --- /dev/null +++ b/src/main/scala/codechicken/multipart/TItemMultiPart.java @@ -0,0 +1,29 @@ +package codechicken.multipart; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import codechicken.lib.vec.BlockCoord; +import codechicken.lib.vec.Vector3; + +/** + * Simple multipart item interface for easy placement. Override newPart and the part will be added to the block space if + * it passes the occlusion tests. + *
+ * Implementors must extend {@link net.minecraft.item.Item} and override onItemUse themselves, delegating to + * {@link JItemMultiPart#onItemUse}, as {@link JItemMultiPart} does. Item declares onItemUse and a superclass method + * beats an interface default on the JVM, so declaring it default here would silently lose. + */ +public interface TItemMultiPart { + + default double getHitDepth(Vector3 vhit, int side) { + return JItemMultiPart.hitDepth(vhit, side); + } + + boolean onItemUse(ItemStack item, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, + float hitY, float hitZ); + + /** Create a new part based on the placement information parameters. */ + TMultiPart newPart(ItemStack item, EntityPlayer player, World world, BlockCoord pos, int side, Vector3 vhit); +}