Skip to content

WIP: port to Minecraft 26.2 (ground-up rewrite for the record-Enchantment + DataComponent changes) - #63

Closed
Nitjsefnie wants to merge 3 commits into
RubixDev:mainfrom
Nitjsefnie:port-26.2
Closed

WIP: port to Minecraft 26.2 (ground-up rewrite for the record-Enchantment + DataComponent changes)#63
Nitjsefnie wants to merge 3 commits into
RubixDev:mainfrom
Nitjsefnie:port-26.2

Conversation

@Nitjsefnie

Copy link
Copy Markdown
Contributor

WIP / draft — porting EnchantedShulkers to Minecraft 26.2.

Opened as a draft so the direction is visible while work is in progress. Per discussion this is a ground-up 26.2 rewrite rather than building on the 1.20.6 PR #62, because two of 26.x's breaking changes hit the mod's core and can't be mechanically ported:

  • Enchantment is now a final record — the six custom enchantments (Siphon/Refill/Vacuum/Void/Augment/Container) can no longer subclass it, so they're being reimplemented as datapack JSON enchantments with item-tag predicates, looked up via Holder<Enchantment>. This also retires the Fabric-ASM enum-injection machinery.
  • ItemStack NBT is gone (DataComponents now) — the portable/augmentable container storage (a nested inventory inside a stack) is being moved onto the ItemContainerContents component.

Toolchain is already on 26.2 (Loom 1.17-SNAPSHOT, JDK 25, official unobfuscated names, a standalone versions/26.2 project) and builds; the gameplay rewrite is in progress. Compat integrations for mods without a 26.2 build yet (owo-lib / trinkets → "Things" / "Shulker Box Slot", plus a few others) are dropped for the 26.2 target.

Will un-draft once the core enchantment + container behavior compiles and is functional. Feedback on the datapack-enchantment / component approach is welcome.


Generated by Claude Opus 4.8 (rewrite orchestration, review), Claude Sonnet 5 (implementation)

Nitjsefnie and others added 3 commits July 19, 2026 10:54
…changes

STATUS: TOOLCHAIN-VERIFIED, CORE-BLOCKED. Adds a working, building `versions/26.2`
Fabric mod skeleton (JDK 25 + fabric-loom 1.17.16 + official Mojang mappings +
fabric-language-kotlin), but the actual gameplay source has NOT been ported: two
independent, verified MC-side architectural changes block a mechanical port of
the mod's core (no compat mixins yet attempted).

## Why 26.2 is a standalone project, not a `com.replaymod.preprocess` node

Two independent, empirically-verified reasons `versions/26.2` cannot be
`include()`d into the existing multi-project build (tried, both fail):

1. Yarn mappings do not exist for 26.x (Fabric meta confirms
   `/v2/versions/yarn/26.2` = `[]`). The preprocessor's cross-version linking
   (`Node.link()`, see PreprocessPlugin.kt) can only bridge mapping *types*
   across a version gap via a hand-authored intermediary crosswalk file (as
   used for 1.19.4-1.20.4), or bridge yarn<->official for the *same* MC
   version via notch mappings - neither applies to 1.20.4(yarn)->26.2(official).
2. Gradle cannot apply two versions of the same plugin ID across subprojects
   in one build. `include(":26.2")` + `./gradlew :26.2:build` fails with:
   `Error resolving plugin [id: 'fabric-loom', version: '1.17-SNAPSHOT'] > ...
   already on the classpath with a different version (1.5-SNAPSHOT)`.

So `versions/26.2` is its own Gradle project (own `settings.gradle.kts`, own
`gradlew` wrapper) inside the monorepo, not wired into the root build.

## Toolchain debugging (3 non-obvious Loom 1.17 pitfalls found, all fixed)

Reference: FabricMC/fabric-example-mod branch `26.2` (minecraft_version=26.2,
loader_version=0.19.3, loom_version=1.17-SNAPSHOT, fabric_version=0.155.2+26.2).

1. Plugin ID must be fully-qualified: `id("net.fabricmc.fabric-loom")`, NOT
   the short `id("fabric-loom")`. The short form resolves to something that
   loads and even logs "Fabric Loom: 1.17.16" but never wires up Minecraft/
   mappings setup, failing later with a confusing
   `Configuration 'mappings' has no dependencies`.
2. No explicit `mappings(...)` call - Loom defaults to official Mojang
   mappings automatically once the plugin ID is correct. An explicit
   `mappings(loom.officialMojangMappings())` workaround instead fails with
   `Failed to find official mojang mappings for 26.2`.
3. Plain `implementation(...)`, not `modImplementation(...)`, for
   fabric-loader/fabric-api/fabric-language-kotlin: official-mappings-only
   projects have no intermediary remap layer, so Loom 1.17 doesn't register
   the `modXxx` configuration variants at all.
4. (minor) accesswidener header must be `accessWidener v2 official`, not
   `named` - `Expected official namespace for access widener entry, found:
   named`.

Confirmed reproducible in a genuinely clean environment: isolated `-g`
Gradle home, `--no-daemon`, from scratch, no shared-cache involvement.
`versions/26.2:build` now succeeds and produces:
  versions/26.2/build/libs/enchantedshulkers-mc26.2-1.2.4-26.2-dev.jar
containing a real compiled Kotlin entrypoint (Mod.class), expanded
fabric.mod.json, and LICENSE. JDK confirmed: 25.0.3 Temurin
(~/.gradle/jdks/eclipse_adoptium-25-amd64-linux.2).

## The two verified core-architecture blockers (decompiled from the actual
## MC 26.2 client jar, not guessed)

1. `net.minecraft.world.item.enchantment.Enchantment` is now
   `public final class Enchantment extends java.lang.Record` - it can no
   longer be subclassed. This mod's six custom enchantments
   (Siphon/Refill/Vacuum/Void/Augment/ContainerEnchantment, in
   src/main/kotlin/.../enchantment/) are all `class X : Enchantment(...)`
   subclasses overriding `isAcceptableItem`/`getMaxLevel`/etc. - none of
   this compiles against 26.2's Enchantment. Custom enchantments must now be
   defined as datapack JSON (`data/<ns>/enchantment/<name>.json`) with
   `supported_items`/`primary_items` item-tag predicates, looked up via
   `Holder<Enchantment>`/`ResourceKey<Enchantment>`. This also obsoletes the
   Fabric-ASM `ClassTinkerers` enum-injection machinery (Mod.kt,
   EnumInjector.kt, asm/PortableContainerTarget.kt,
   asm/AugmentableContainerTarget.kt, EnchantmentTargetMixin.java) used to
   add PORTABLE_CONTAINER/AUGMENTABLE_CONTAINER constants to the old
   `EnchantmentTarget` enum: 26.2's `EnchantmentTarget` is a different, much
   smaller (3-constant) enum for combat-effect targeting only, unrelated to
   item applicability.

2. `ItemStack` has zero NBT API in 26.2 (`javap` on the real class: no
   `getSubNbt`/`getOrCreateSubNbt`/`getNbt`/`setNbt`; `BlockItem.
   BLOCK_ENTITY_TAG_KEY` does not exist at all) - fully `DataComponent`-based
   (`PatchedDataComponentMap`). `net.minecraft.world.item.component.
   ItemContainerContents` is the confirmed replacement. This mod stores an
   entire nested inventory inside a single ItemStack's NBT sub-tag
   (Utils.kt: getContainerInventory/setContainerInventory, both centrally
   used) - the core mechanism behind portable/augmentable containers.
   Directly confirmed touching this API in: ChestBlockEntityMixin (readNbt/
   writeNbt overrides), EnchantmentHelperMixin (`stack.getOrCreateSubNbt(
   "BlockEntityTag")`), GrindstoneScreenHandlerMixin (`@At(target=
   "...ItemStack;removeSubNbt...")`), AbstractBlockMixin (getNbt/setNbt/
   getSubNbt/removeSubNbt), and Utils.kt itself.

Both require a genuine redesign (not a Yarn->Mojmap rename) of the mod's
enchantment-registration layer and its container-storage layer, touching
most of the ~28 core (non-compat) mixins and several central Kotlin files.
This is a design decision (which datapack/tag/component shape to use) that
should get the user's sign-off before a subagent grinds through a large,
behavior-changing rewrite - not something to invent silently mid-port.

## Compat-mixin dependency availability audit (Modrinth API + target mavens,
## checked 2026-07-19, not yet wired since core isn't built)

Available for 26.2 (compat could be re-enabled once core exists):
  cloth-config (26.2.155+fabric), modmenu (20.0.1), polymer-core
  (0.17.3+26.2), server-translations-api (3.1.0+26.2), sgui (2.1.0+26.2),
  architectury-api (21.0.4+fabric), cardinal-components-api (8.0.1),
  shulkerboxtooltip (5.4.0+26.2-fabric), peek (fabric-1.5.1+26.2),
  enderite-mod (1.9.0).
NOT available for 26.2 (compat to drop): owo-lib, trinkets (both per brief),
  and additionally: reinforced-shulker-boxes/reinfcore, shulker+,
  split-shulker-boxes, quickshulker/shulkerutils/kyrptconfig,
  shulker-box-slot, clickopener. This drops: Reinforced Shulker Boxes,
  Shulker+, Split Shulker Boxes, QuickShulker, Shulker Box Slot ("Things"
  already dropped per owo-lib), Click Opener. Keeps viable once core exists:
  ShulkerBoxTooltip, Peek, Enderite Mod compat.

## Renderer/core mixins inspected (not yet ported, noted for scope)

ShulkerBoxBlockEntityRendererMixin, ChestBlockEntityRendererMixin (3x
ordinal-matched @WrapOperation against a private render() overload -
control-flow-fragile across a renderer rewrite), BarrelBlockEntity_
ViewerCountManagerMixin (mixes into an anonymous inner class `BarrelBlockEntity$1`
by an old intermediary-derived field name `field_27208` - the `$1` class
still exists in 26.2's jar but the field needs re-discovery),
ServerPlayerEntityMixin, PlayerInventoryMixin, GrindstoneScreenHandlerMixin,
EnchantmentHelperMixin, ChestBlockEntityMixin, AbstractBlockMixin - all
reviewed, all need real rework once the two blockers above are resolved.

## Left in place for review

- Clone: EnchantedShulkers/ (this repo, branch port-26.2)
- Toolchain proof/debug trail: ../example-262 (untouched FabricMC/
  fabric-example-mod 26.2 branch, builds clean - the reference used to
  root-cause the 3 Loom pitfalls above), ../groovy-262-test,
  ../standalone-262-test (intermediate debugging copies), ../isolated-gradle-home
  (clean-room `-g` Gradle home used to rule out shared-cache contention)
- Built jar: versions/26.2/build/libs/enchantedshulkers-mc26.2-1.2.4-26.2-dev.jar

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…CORE BUILDS)

STATUS: CORE BUILDS. `./gradlew build` in versions/26.2 succeeds and produces
versions/26.2/build/libs/enchantedshulkers-mc26.2-1.2.4-26.2-dev.jar containing real,
compiled gameplay logic (not just the toolchain scaffold from the previous commit).

## Enchantments -> datapack JSON (removes ASM injection)

`Enchantment` is a non-subclassable `final record` in 26.2 (verified via javap on the
real 26.2 jar - see the previous commit). The five custom enchantments are now:
  - data/enchantedshulkers/enchantment/{siphon,refill,vacuum,void,augment}.json, each a
    minimal Enchantment.EnchantmentDefinition (description/supported_items/weight/
    max_level/min_cost/max_cost/anvil_cost/slots, all field names confirmed via javap on
    Enchantment$EnchantmentDefinition/$Cost) with empty "effects" (all the actual
    siphon/refill/vacuum/void/augment behavior is mixin-driven, not a vanilla effect).
  - Mod.SIPHON_KEY/REFILL_KEY/VACUUM_KEY/VOID_KEY/AUGMENT_KEY: ResourceKey<Enchantment>,
    resolved to a Holder<Enchantment> at point of use via
    Utils.enchantmentHolder(registryAccess, key) = registryAccess.lookupOrThrow(
    Registries.ENCHANTMENT).getOrThrow(key).
  - data/enchantedshulkers/tags/item/{portable_container,augmentable_container}.json
    (note: singular `tags/item/`, not `tags/items/` - the 1.21+ datapack path convention;
    unverified against a runtime datapack load, flagging as an assumption) referenced by
    "supported_items": "#enchantedshulkers:portable_container" etc., replacing the old
    Fabric-ASM ClassTinkerers enum-injection into EnchantmentTarget entirely.
  - Removed (not ported, obsolete): EnumInjector.kt, asm/PortableContainerTarget.kt,
    asm/AugmentableContainerTarget.kt, asm/EnchantmentTargetMixin.java,
    ContainerEnchantment.kt, AugmentEnchantment.kt (Augment has no runtime logic of its
    own - its effect is entirely "this level feeds Utils.getInvRows elsewhere").

SiphonEnchantment/RefillEnchantment/VacuumEnchantment/VoidEnchantment are now plain
Kotlin `object`s (their real content was always the companion-object statics; only the
Enchantment-subclass part is gone). TODO(26.2) in each: the WorldConfig runtime toggles
(creativeSiphon, strongerSiphon, refillNonStackables, weakerVacuum, etc.) are hardcoded
to their original defaults since config/ isn't ported yet.

## Container storage -> ItemContainerContents component

ItemStack has zero NBT API in 26.2 (verified via javap: no getSubNbt/getOrCreateSubNbt/
getNbt/setNbt; BlockItem.BLOCK_ENTITY_TAG_KEY doesn't exist - see the previous commit).
Utils.getContainerInventory/setContainerInventory rewritten onto
net.minecraft.world.item.component.ItemContainerContents (DataComponents.CONTAINER).
ItemContainerContents.fromItems(List<ItemStack>) only remembers up to the last
non-empty slot (no fixed-size concept) - same as the original, which always recomputed
size from the current Augment level rather than persisting it, the port always
recreates a NonNullList of the *computed* size and copies stored contents into it via
ItemContainerContents.copyInto(NonNullList), leaving the rest empty. This turned out to
map cleanly onto the existing design.

## Yarn -> Mojmap migration, done for the touched surface

Confirmed via javap on the real 26.2 jars (not guessed): AbstractBlock -> BlockBehaviour,
PlayerInventory -> Inventory (player.getInventory()), ServerPlayerEntity -> ServerPlayer,
GrindstoneScreenHandler -> GrindstoneMenu, DefaultedList -> NonNullList,
Identifier stays Identifier but moves package (net.minecraft.util -> net.minecraft.
resources), RegistryKey -> ResourceKey, TagKey.of -> TagKey.create, DataComponentTypes
(1.20.5-1.21.x Mojmap) -> DataComponents (renamed again in 26.2), EnchantmentHelper.
getLevel -> getItemEnchantmentLevel(Holder<Enchantment>, ItemInstance),
insertStack -> add, increment/decrement -> grow/shrink, ItemStack.canCombine ->
isSameItemSameComponents, DynamicRegistryManager -> RegistryAccess (via
Entity.registryAccess()). BlockEntity NBT read/write is ALSO redesigned in 26.2:
readNbt/writeNbt(NbtCompound) -> loadAdditional/saveAdditional(ValueInput/ValueOutput),
a genuine codec-based typed-read/write API replacing raw NBT compound manipulation, not
just a rename (confirmed via javap on BlockEntity/ChestBlockEntity/ValueInput/
ValueOutput) - a third architectural shift beyond the two found in the previous commit.

## Mixins ported this pass (5)

- AbstractBlockMixin (getDroppedStacks -> getDrops on BlockBehaviour): rewrites the
  drop-time enchantment mirroring from raw NBT onto EnchantmentHelper.updateEnchantments
  + DataComponents.ENCHANTMENTS.
- ChestBlockEntityMixin: enchantment persistence + inventory-size updates, ported onto
  ItemEnchantments + ValueInput/ValueOutput (using ItemEnchantments.CODEC directly - a
  clean fit). TODO(26.2): the display-name-tinting and AugmentedScreenHandler overrides
  are dropped (need screen/, not ported), as is the toInitialChunkDataNbt/toUpdatePacket
  client-sync override (deferred to the renderer pass).
- GrindstoneMenuMixin (renamed from GrindstoneScreenHandlerMixin): re-anchored on
  computeResult's return value via @ModifyReturnValue instead of the original's
  local-capture at an ItemStack;removeSubNbt call site inside grind() - that method and
  the vanilla NBT call it hooked don't exist anymore; 26.2's own grindstone
  curse-removal already uses ItemEnchantments.Mutable natively. TODO(26.2): the
  shulker-stacking-prevention half of the original mixin (unrelated to container
  storage) is dropped, not yet re-verified against 26.2's actual slotsChanged/
  createResult shape.
- PlayerInventoryMixin (insertStack -> add) and ServerPlayerEntityMixin (mixed into
  Player.tick() at TAIL instead of the original's ServerPlayerEntity.playerTick() inner
  PlayerEntity;tick() call site, which doesn't have an obvious 26.2 equivalent under that
  name - TODO(26.2): re-verify this still fires at the intended point): wire
  Siphon/Vacuum/Void into item pickup and Refill into the player tick, same structure as
  the original.
- Dropped, obsolete: EnchantmentHelperMixin - it targeted EnchantmentHelper.set(Map
  <Enchantment,Integer>, ItemStack), which no longer exists in 26.2 in any shape
  (confirmed via full javap listing of EnchantmentHelper's public methods); its purpose
  (mirroring enchantment NBT into a container's "BlockEntityTag" so the anvil UI stays in
  sync) is moot now that enchantments are DataComponents.ENCHANTMENTS directly on the
  stack, not a separately-tracked NBT shadow copy.

## Not yet in this pass

Renderer mixins, the remaining ~17 core mixins (BarrelBlockEntity*, EnderChestBlock*,
CrossbowItem/BowItem, EnchantBookFactory, ItemStack, PlayerEntity, ShulkerBoxBlock*,
ChestBlock_NamedScreenHandlerFactory), config/screen/networking subsystems, ClientMod,
and all compat mixins (owo-lib/trinkets-dependent ones dropped per the availability
audit in the previous commit; the ones with 26.2 builds - ShulkerBoxTooltip/Peek/
Enderite - not yet attempted).

## Toolchain

Same as the previous commit: JDK 25.0.3 Temurin, fabric-loom 1.17.16 (fully-qualified
`net.fabricmc.fabric-loom` plugin id), Gradle wrapper 9.5.1, official Mojang mappings
(no explicit mappings() call), plain `implementation` not `modImplementation`.
MixinExtras (@ModifyReturnValue, used by GrindstoneMenuMixin) resolved without an
explicit dependency - bundled transitively via fabric-loader 0.19.3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tityMixin)

BarrelBlockEntity's `items` field / loadAdditional/saveAdditional/getContainerSize
shape is identical to ChestBlockEntity's (confirmed via javap), so this is a direct
parallel port: enchantment persistence (ItemEnchantments + ValueInput/ValueOutput) +
inventory-size updates. Same TODO(26.2) deferrals as ChestBlockEntityMixin
(display-name tinting, AugmentedScreenHandler, client-sync packet override - all need
subsystems not yet ported). `./gradlew build` still succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Nitjsefnie Nitjsefnie closed this Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant