Skip to content

Update 1.21 - #5301

Open
TarLaboratories wants to merge 3 commits into
1.21from
tar/update-1.21
Open

Update 1.21#5301
TarLaboratories wants to merge 3 commits into
1.21from
tar/update-1.21

Conversation

@TarLaboratories

@TarLaboratories TarLaboratories commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What

I cherry-picked 4 commits into 1.21:

Implementation Details

Uhh spoilage uses data components now i guess. Also eliminated some spoilage-related mixins since they weren't needed anymore

AI Usage

no

Outcome

1.21.1 is up to date and i get to work on modular armor yaaaaaay

How Was This Tested

logged into the game, ran gametests

@TarLaboratories
TarLaboratories requested a review from a team as a code owner August 16, 2026 20:30
@TarLaboratories TarLaboratories added type: feature New feature or request bundled for a 0.X.0 Update 1.21.1 ignore changelog PR should not be added to the changelog. Release: Major - 0.X.0 Releases focused on Content, changes to gameplay; While maintaining mostly API stability. Ignore Version Sync Do not append this issue to the version sync issue tracker. labels Aug 16, 2026
@github-actions github-actions Bot added 1.21 Tests: Passed Game Tests have passed on this PR labels Aug 16, 2026
Comment thread src/main/java/com/gregtechceu/gtceu/api/item/component/SpoilContext.java Outdated

public static final Codec<SpoilContext> CODEC = CompoundTag.CODEC.xmap(SpoilContext::deserializeNBT,
SpoilContext::serializeNBT);
public static final StreamCodec<ByteBuf, SpoilContext> STREAM_CODEC = ByteBufCodecs.COMPOUND_TAG

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here for the stream codec

Comment thread src/main/java/com/gregtechceu/gtceu/api/item/component/SpoilContext.java Outdated
Comment thread src/main/java/com/gregtechceu/gtceu/api/item/component/SpoilUtils.java Outdated
Comment thread src/main/java/com/gregtechceu/gtceu/core/mixins/ItemStackMixin.java
Comment thread src/main/java/com/gregtechceu/gtceu/core/mixins/ItemStackMixin.java Outdated
Comment thread src/main/java/com/gregtechceu/gtceu/core/mixins/ItemStackMixin.java Outdated
Comment on lines +74 to +80
private static SpoilContext build(ResourceKey<Level> levelKey, BlockPos pos, Integer entityId,
ResourceLocation itemHandlerSourceId, CompoundTag itemHandlerData, int slot) {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
Level level = server == null ? null : server.getLevel(levelKey);
return new SpoilContext(level, pos, level != null && entityId != null ? level.getEntity(entityId) : null,
ItemHandlerSource.getById(itemHandlerSourceId), itemHandlerData, slot);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static SpoilContext build(ResourceKey<Level> levelKey, BlockPos pos, Integer entityId,
ResourceLocation itemHandlerSourceId, CompoundTag itemHandlerData, int slot) {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
Level level = server == null ? null : server.getLevel(levelKey);
return new SpoilContext(level, pos, level != null && entityId != null ? level.getEntity(entityId) : null,
ItemHandlerSource.getById(itemHandlerSourceId), itemHandlerData, slot);
}
private static SpoilContext build(Optional<ResourceKey<Level>> dimension, Optional<BlockPos> pos, Optional<UUID> entityId,
Optional<ResourceLocation> itemHandlerSourceId, Optional<CompoundTag> itemHandlerData, int slot) {
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
Level level = server == null || dimension.isEmpty() ? null : server.getLevel(dimension.get());
return new SpoilContext(level, pos, level != null && entityId.isPresent() ? level.getEntities().get(entityId.get()) : null,
itemHandlerSourceId.isPresent() ? ItemHandlerSource.getById(itemHandlerSourceId.get()) : null, itemHandlerData.orElse(null), slot);
}

Comment on lines +50 to +58
public static final Codec<SpoilContext> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Level.RESOURCE_KEY_CODEC.optionalFieldOf("level", null)
.forGetter(ctx -> ctx.level == null ? null : ctx.level.dimension()),
BlockPos.CODEC.optionalFieldOf("pos", null).forGetter(SpoilContext::pos),
Codec.INT.optionalFieldOf("entity", null).forGetter(ctx -> ctx.entity == null ? null : ctx.entity.getId()),
ResourceLocation.CODEC.optionalFieldOf("item_handler", null)
.forGetter(ctx -> ctx.itemHandlerSource == null ? null : ctx.itemHandlerSource.getId()),
CompoundTag.CODEC.optionalFieldOf("item_handler_data", null).forGetter(SpoilContext::itemHandlerData),
Codec.INT.optionalFieldOf("slot", -1).forGetter(SpoilContext::slot)).apply(instance, SpoilContext::build));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DFU actually really hates null default values, so do this instead:

Suggested change
public static final Codec<SpoilContext> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Level.RESOURCE_KEY_CODEC.optionalFieldOf("level", null)
.forGetter(ctx -> ctx.level == null ? null : ctx.level.dimension()),
BlockPos.CODEC.optionalFieldOf("pos", null).forGetter(SpoilContext::pos),
Codec.INT.optionalFieldOf("entity", null).forGetter(ctx -> ctx.entity == null ? null : ctx.entity.getId()),
ResourceLocation.CODEC.optionalFieldOf("item_handler", null)
.forGetter(ctx -> ctx.itemHandlerSource == null ? null : ctx.itemHandlerSource.getId()),
CompoundTag.CODEC.optionalFieldOf("item_handler_data", null).forGetter(SpoilContext::itemHandlerData),
Codec.INT.optionalFieldOf("slot", -1).forGetter(SpoilContext::slot)).apply(instance, SpoilContext::build));
// spotless:off
public static final Codec<SpoilContext> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Level.RESOURCE_KEY_CODEC.optionalFieldOf("dimension").forGetter(ctx -> ctx.level == null ? Optional.empty() : Optional.of(ctx.level.dimension())),
BlockPos.CODEC.optionalFieldOf("pos").forGetter(ctx -> Optional.ofNullable(ctx.pos)),
UUIDUtil.CODEC.optionalFieldOf("entity").forGetter(ctx -> ctx.entity == null ? Optional.empty() : Optional.of(ctx.entity.getUUID())),
ResourceLocation.CODEC.optionalFieldOf("item_handler").forGetter(ctx -> ctx.itemHandlerSource == null ? Optional.empty() : Optional.of(ctx.itemHandlerSource.getId())),
CompoundTag.CODEC.optionalFieldOf("item_handler_data").forGetter(ctx -> Optional.ofNullable(ctx.itemHandlerData)),
Codec.INT.optionalFieldOf("slot", -1).forGetter(SpoilContext::slot)
).apply(instance, SpoilContext::build));
// spotless:on

Another suggestion will follow for the builder function.

P.S. I also changed the type of the entity field because that ID isn't consistent across world loads.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add an 'empty' context as a default value:

public static final SpoilContext EMPTY = new SpoilContext();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this to injected_interfaces/interfaces.json.


public interface ISpoilableItemStackExtension {

void gtceu$setStack(ItemStack newStack);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this method to gtceu$forceMyContentTo.
this should really be implemented as swapping the ItemStack instance to another one, though.

private ContentModifier outputModifier = ContentModifier.IDENTITY;
private ContentModifier tickInputModifier = ContentModifier.IDENTITY;
private ContentModifier tickOutputModifier = ContentModifier.IDENTITY;
private BiConsumer<GTRecipe, Object> actualOutputModifier = (recipe, object) -> {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this also include the (optional) KJS output modifiers from GTRecipe?

return new CustomItemStackHandler(player.getInventory().items);
} else return null;
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a 3rd default type: generic entity inventory.

/**
 * Represents getting an item handler as an entity's inventory (used if {@link SpoilContext#entity} is NOT a
 * {@link Player})
 */
public static final ItemHandlerSource ENTITY_CAPABILITY = new ItemHandlerSource(GTCEu.id("entity_capability")) {

    @Override
    protected @Nullable IItemHandler getHandler(SpoilContext ctx) {
        if (ctx.entity != null) {
            return ctx.entity.getCapability(Capabilities.ItemHandler.ENTITY);
        } else {
            return null;
        }
    }
};

Comment on lines +94 to +95
public SpoilContext(@NotNull Entity entity) {
this(entity.level(), entity.blockPosition(), entity, null, null, -1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for the "generic entity" item handler source)

Suggested change
public SpoilContext(@NotNull Entity entity) {
this(entity.level(), entity.blockPosition(), entity, null, null, -1);
public SpoilContext(@NotNull Entity entity, int slot) {
this(entity.level(), entity.blockPosition(), entity, ItemHandlerSource.ENTITY_INVENTORY, null, slot);

Comment on lines +122 to +124
if (side == null) return this.withItemHandlerSource(ItemHandlerSource.BLOCK_CAPABILITY);
return this.withItemHandlerSource(ItemHandlerSource.BLOCK_CAPABILITY)
.withItemHandlerData("side", StringTag.valueOf(side.getSerializedName()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (side == null) return this.withItemHandlerSource(ItemHandlerSource.BLOCK_CAPABILITY);
return this.withItemHandlerSource(ItemHandlerSource.BLOCK_CAPABILITY)
.withItemHandlerData("side", StringTag.valueOf(side.getSerializedName()));
SpoilContext self = this.withItemHandlerSource(ItemHandlerSource.BLOCK_CAPABILITY);
if (side != null) {
self = self.withItemHandlerData("side", StringTag.valueOf(side.getSerializedName()));
}
return self;

@Nullable BlockPos pos,
@Nullable Entity entity,
@Nullable ItemHandlerSource itemHandlerSource,
@Nullable CompoundTag itemHandlerData,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field should be made private if at all possible so people can't mistakenly modify it.

private void gtceu$tickFreshness(Level level, Entity entity, int inventorySlot, boolean isCurrentItem,
CallbackInfo ci) {
if (entity instanceof Player player) gtceu$updateFreshness(new SpoilContext(player, inventorySlot), true);
else gtceu$updateFreshness(new SpoilContext(entity), true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for the "generic entity" item handler source)

Suggested change
else gtceu$updateFreshness(new SpoilContext(entity), true);
else gtceu$updateFreshness(new SpoilContext(entity, inventorySlot), true);

@Ghostipedia Ghostipedia added the Do Not Merge DO NOT MERGE THIS PR YET! label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.21 1.21.1 Do Not Merge DO NOT MERGE THIS PR YET! ignore changelog PR should not be added to the changelog. Ignore Version Sync Do not append this issue to the version sync issue tracker. Release: Major - 0.X.0 Releases focused on Content, changes to gameplay; While maintaining mostly API stability. Tests: Passed Game Tests have passed on this PR type: feature New feature or request bundled for a 0.X.0 Update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants