mojira.dev
MC-313171

No longer possible to resolve text components when placing signs, due to results being overwritten

How to reproduce

Give yourself a sign with:

/give @s minecraft:oak_sign[minecraft:block_entity_data={id:"minecraft:sign",front_text:{messages:["A","B","C","D"]},is_waxed:true}]

When you place it (ensure you are op to let block_entity_data work), the resulting sign will have no text and will not be waxed. This is not because front_text is no longer loaded; it is loaded, but it is then immediately overwritten. To demonstrate this, give yourself a sign with:

/give @s minecraft:oak_sign[minecraft:block_entity_data={id:"minecraft:sign",front_text:{messages:["A","B","C","D"]},is_waxed:true,allow_op_features:true}]

When you place it, the resulting sign will still have no text and will not be waxed, but if you use /data get to inspect the block data, you will see that it does have allow_op_features:true.

Okay, block_entity_data doesn’t work. You can just use the dedicated sign_text_front and sign_text_back components added in 26.3. But that still doesn’t support everything we had before 26.3:

/give @s minecraft:oak_sign[minecraft:block_entity_data={id:"minecraft:sign",allow_op_features:true},minecraft:sign_text_front={messages:["",{selector:"@p"},"",""]}]

When placed, the sign will display @p instead of the name of the nearest player, indicating the selector component wasn’t resolved. Setting sign NBT data supports resolving server-side text components when the allow_op_features field is set to true, and this does still “work” with block_entity_data in 26.3, but the result is hidden due to an oversight.

Code analysis

The issue is that the dedicated sign_text_front, sign_text_back, and waxed components added in 26.3 always overwrite the values set in block_entity_data, even if the components are not present. In the function that loads sign components, sign text and waxed state are always set, regardless of whether the components are present:

protected void applyImplicitComponents(final DataComponentGetter components) {
    super.applyImplicitComponents(components);
    this.frontText = components.getOrDefault(DataComponents.SIGN_TEXT_FRONT, SignText.EMPTY);
    this.backText = components.getOrDefault(DataComponents.SIGN_TEXT_BACK, SignText.EMPTY);
    this.isWaxed = components.get(DataComponents.WAXED) != null;
}

These components are loaded after block_entity_data, so it isn’t possible to set those fields through block_entity_data anymore.

This wouldn’t be so bad, but server-side text components like score and nbt are currently only resolved when they’re loaded from NBT, and not from sign_text_front or sign_text_back:

@Override
protected void loadAdditional(final ValueInput input) {
    super.loadAdditional(input);
    this.allowOpFeatures = input.getBooleanOr("allow_op_features", false);
    this.frontText = (SignText)input.read("front_text", SignText.CODEC).map(this::resolveLines).orElse(SignText.EMPTY);
    this.backText = (SignText)input.read("back_text", SignText.CODEC).map(this::resolveLines).orElse(SignText.EMPTY);
    this.isWaxed = input.getBooleanOr("is_waxed", false);
}

private SignText resolveLines(final SignText data) {
    return !this.allowOpFeatures ? data : data.asMutable().modifyLines(this::resolveLine).asImmutable();
}

resolveLines() is only called from loadAdditional(), but the results of loading front_text and back_text are always overwritten when placing a sign.

Fixes

applyImplicitComponents() should only overwrite this.frontText, this.backText, and this.isWaxed if the components are present, and not reset them to default values if the components are missing. It should probably also apply resolveLines(), since there doesn’t seem to be any reason for component resolution to be locked behind block_entity_data.

Comments 0

No comments.

AjaxGb

(Unassigned)

Unconfirmed

26.3

Retrieved