Skip to content

Commit c9d87ca

Browse files
committed
fix: stop calling removed World.spawn Consumer overload on Paper 1.21+
The 1.1.0 jar was unusable on Paper 1.21.x: every render tick threw NoSuchMethodError pointing at World.spawn(Location, Class, org.bukkit.util.Consumer). The 1.20.1 paper-api dev bundle still declares that deprecated org.bukkit.util.Consumer overload, so our lambdas were resolved against it at compile time. Paper 1.21 actually honoured the forRemoval marker and deleted the method, leaving the shipped class file pointing at a method that no longer exists. The Paper 1.21+ replacement uses java.util.function.Consumer, but we cannot rely on either type being present everywhere in the supported 1.20.1 -> 26.x range. The smallest portable fix is to drop the Consumer overload entirely and use the long-stable two-argument World.spawn(Location, Class), then configure the entity immediately afterwards. - Convert all four spawn helpers (item display, text label, interaction hitbox, block display) to a two-step spawn: World.spawn(Location, Class) followed by the same property assignments that previously lived inside the consumer. - Set setVisibleByDefault first so the entity tracker tick that picks the spawn up cannot broadcast a one-frame public-visibility window before the plugin narrows viewers. - Bump the project version to 1.1.1 and add a CHANGELOG entry that describes the hotfix in both English and Simplified Chinese. Verified: ./gradlew test (full suite) passes against the 1.20.1 baseline; the resulting jar no longer references the removed Consumer overload, so deployment on Paper 1.21.x stops crashing per render tick.
1 parent 016383c commit c9d87ca

3 files changed

Lines changed: 102 additions & 80 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## 1.1.1 - 2026-06-16
4+
5+
Hotfix release for Paper 1.21+ servers. 1.1.0 was unusable on Paper 1.21.x because every entity render path tripped a `NoSuchMethodError`.
6+
7+
中文更新日志:
8+
9+
- Paper 1.21+ 渲染崩溃修复: 在 1.21 上每次刷新桌面渲染都会抛 `NoSuchMethodError: org.bukkit.World.spawn(Location, Class, org.bukkit.util.Consumer)`,因为 Paper 1.21 已经移除了已弃用的 `org.bukkit.util.Consumer` 重载,而 1.20 的开发包让 lambda 在编译期被解析到这个 forRemoval 的方法上。改成两步 spawn(先 `World.spawn(Location, Class)`,再立即配置实体)后,麻将牌、文本标签、交互盒和方块展示在 1.20.1-26.x 全版本上都能正常生成。
10+
- 可见性次序加固: spawn 之后第一时间设置 `setVisibleByDefault`,防止 entity tracker 在某些版本上抢先广播一帧默认可见状态。
11+
12+
English Release Notes:
13+
14+
- Paper 1.21+ render crash fix: every render tick threw `NoSuchMethodError: org.bukkit.World.spawn(Location, Class, org.bukkit.util.Consumer)` on Paper 1.21.x because Paper removed the deprecated `org.bukkit.util.Consumer` overload while the 1.20 dev bundle had bound our lambdas to it at compile time. The four entity factories (tile/label/interaction/block) now use the long-stable two-argument `World.spawn(Location, Class)` and configure the entity immediately afterwards, restoring rendering across the full 1.20.1-26.x range.
15+
- Visibility ordering hardening: `setVisibleByDefault` is now called first so the entity tracker cannot publish a one-frame window of default visibility before the plugin restricts viewers.
16+
317
## 1.1.0 - 2026-06-16
418

519
Compatibility and architecture release for the post-1.0.0 Paper/Folia line.

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ plugins {
1414
}
1515

1616
group = "top.ellan"
17-
version = "1.1.0"
17+
version = "1.1.1"
1818

1919
val minimumPaperDevBundleVersion = "1.20.1-R0.1-SNAPSHOT"
2020
val latestPaperDevBundleVersion = "26.2-rc-2.build.9-alpha"

src/main/java/top/ellan/mahjong/render/display/DisplayEntities.java

Lines changed: 87 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -654,37 +654,44 @@ private static ItemDisplay spawnTileDisplayInternal(
654654
throw new IllegalArgumentException("Location world is null");
655655
}
656656

657-
ItemDisplay display = world.spawn(location, ItemDisplay.class, spawned -> {
658-
boolean restrictedVisibility = privateViewers != null;
659-
spawned.setPersistent(false);
660-
markManagedEntity(runtime.bukkitPlugin(), spawned);
661-
spawned.setItemDisplayTransform(ItemDisplay.ItemDisplayTransform.HEAD);
662-
spawned.setInterpolationDuration(smoothMovement ? 1 : 0);
663-
spawned.setInterpolationDelay(0);
664-
PaperCompatibility.setTeleportDuration(spawned, smoothMovement ? 1 : 0);
665-
spawned.setViewRange(32.0F);
666-
spawned.setShadowRadius(0.0F);
667-
spawned.setShadowStrength(0.0F);
668-
spawned.setDisplayWidth(0.4F * scale);
669-
spawned.setDisplayHeight(0.6F * scale);
670-
if (billboard != null) {
671-
spawned.setBillboard(billboard);
672-
}
673-
spawned.setRotation(yaw, 0.0F);
674-
spawned.setVisibleByDefault(!restrictedVisibility && visibleByDefault);
675-
if (glowColor != null) {
676-
spawned.setGlowing(true);
677-
spawned.setGlowColorOverride(glowColor);
678-
spawned.setBrightness(new Display.Brightness(15, 15));
679-
}
680-
spawned.setTransformation(new Transformation(
681-
new Vector3f(),
682-
new AxisAngle4f((float) Math.toRadians(pose.xRotationDegrees()), 1.0F, 0.0F, 0.0F),
683-
new Vector3f(scale, scale, scale),
684-
new AxisAngle4f()
685-
));
686-
spawned.setItemStack(tileItem(runtime, variant, tile, pose.faceDown()));
687-
});
657+
// World.spawn(Location, Class, Consumer) was source-compatible across 1.20.x but the
658+
// Consumer parameter type changed from org.bukkit.util.Consumer (forRemoval) to
659+
// java.util.function.Consumer in 1.21+. To keep one jar working on both we use the
660+
// pre-1.20 World.spawn(Location, Class) entry point, which has been stable since
661+
// Bukkit's earliest releases, and configure the entity immediately after spawning.
662+
ItemDisplay display = world.spawn(location, ItemDisplay.class);
663+
boolean restrictedVisibility = privateViewers != null;
664+
// Set visibility before any other property so the entity tracker that
665+
// wakes up on the next broadcast tick already sees the final state and
666+
// never publishes a one-frame window of default visibility to clients.
667+
display.setVisibleByDefault(!restrictedVisibility && visibleByDefault);
668+
display.setPersistent(false);
669+
markManagedEntity(runtime.bukkitPlugin(), display);
670+
display.setItemDisplayTransform(ItemDisplay.ItemDisplayTransform.HEAD);
671+
display.setInterpolationDuration(smoothMovement ? 1 : 0);
672+
display.setInterpolationDelay(0);
673+
PaperCompatibility.setTeleportDuration(display, smoothMovement ? 1 : 0);
674+
display.setViewRange(32.0F);
675+
display.setShadowRadius(0.0F);
676+
display.setShadowStrength(0.0F);
677+
display.setDisplayWidth(0.4F * scale);
678+
display.setDisplayHeight(0.6F * scale);
679+
if (billboard != null) {
680+
display.setBillboard(billboard);
681+
}
682+
display.setRotation(yaw, 0.0F);
683+
if (glowColor != null) {
684+
display.setGlowing(true);
685+
display.setGlowColorOverride(glowColor);
686+
display.setBrightness(new Display.Brightness(15, 15));
687+
}
688+
display.setTransformation(new Transformation(
689+
new Vector3f(),
690+
new AxisAngle4f((float) Math.toRadians(pose.xRotationDegrees()), 1.0F, 0.0F, 0.0F),
691+
new Vector3f(scale, scale, scale),
692+
new AxisAngle4f()
693+
));
694+
display.setItemStack(tileItem(runtime, variant, tile, pose.faceDown()));
688695

689696
if (clickAction != null) {
690697
TableDisplayRegistry.register(display.getEntityId(), clickAction);
@@ -755,22 +762,23 @@ private static TextDisplay spawnLabel(
755762
throw new IllegalArgumentException("Location world is null");
756763
}
757764

758-
TextDisplay display = world.spawn(location, TextDisplay.class, spawned -> {
759-
boolean privateOnly = privateViewers != null && !privateViewers.isEmpty();
760-
spawned.setPersistent(false);
761-
markManagedEntity(runtime.bukkitPlugin(), spawned);
762-
spawned.text(text);
763-
spawned.setSeeThrough(false);
764-
spawned.setShadowed(shadowed);
765-
spawned.setDefaultBackground(false);
766-
spawned.setBillboard(billboard);
767-
spawned.setRotation(yaw, pitch);
768-
spawned.setLineWidth(160);
769-
spawned.setViewRange(LABEL_VIEW_RANGE);
770-
spawned.setBrightness(new Display.Brightness(15, 15));
771-
spawned.setBackgroundColor(color);
772-
spawned.setVisibleByDefault(!privateOnly);
773-
});
765+
TextDisplay display = world.spawn(location, TextDisplay.class);
766+
boolean privateOnly = privateViewers != null && !privateViewers.isEmpty();
767+
// See ItemDisplay spawn comment above: ensure the visibility flag is
768+
// resolved before any tracker tick observes the entity.
769+
display.setVisibleByDefault(!privateOnly);
770+
display.setPersistent(false);
771+
markManagedEntity(runtime.bukkitPlugin(), display);
772+
display.text(text);
773+
display.setSeeThrough(false);
774+
display.setShadowed(shadowed);
775+
display.setDefaultBackground(false);
776+
display.setBillboard(billboard);
777+
display.setRotation(yaw, pitch);
778+
display.setLineWidth(160);
779+
display.setViewRange(LABEL_VIEW_RANGE);
780+
display.setBrightness(new Display.Brightness(15, 15));
781+
display.setBackgroundColor(color);
774782
if (privateViewers != null && !privateViewers.isEmpty()) {
775783
DisplayVisibilityRegistry.registerPrivate(display.getEntityId(), privateViewers);
776784
syncPrivateVisibility(runtime, display, privateViewers);
@@ -812,15 +820,15 @@ private static Interaction spawnInteraction(
812820
throw new IllegalArgumentException("Location world is null");
813821
}
814822

815-
Interaction interaction = world.spawn(location, Interaction.class, spawned -> {
816-
boolean privateOnly = privateViewers != null && !privateViewers.isEmpty();
817-
spawned.setPersistent(false);
818-
markManagedEntity(runtime.bukkitPlugin(), spawned);
819-
spawned.setResponsive(true);
820-
spawned.setInteractionWidth(width);
821-
spawned.setInteractionHeight(height);
822-
spawned.setVisibleByDefault(!privateOnly);
823-
});
823+
Interaction interaction = world.spawn(location, Interaction.class);
824+
boolean interactionPrivateOnly = privateViewers != null && !privateViewers.isEmpty();
825+
// See ItemDisplay spawn comment above.
826+
interaction.setVisibleByDefault(!interactionPrivateOnly);
827+
interaction.setPersistent(false);
828+
markManagedEntity(runtime.bukkitPlugin(), interaction);
829+
interaction.setResponsive(true);
830+
interaction.setInteractionWidth(width);
831+
interaction.setInteractionHeight(height);
824832
if (clickAction != null) {
825833
TableDisplayRegistry.register(interaction.getEntityId(), clickAction);
826834
}
@@ -866,29 +874,29 @@ public static BlockDisplay spawnBlockDisplay(
866874
throw new IllegalArgumentException("Location world is null");
867875
}
868876

869-
BlockDisplay display = world.spawn(location, BlockDisplay.class, spawned -> {
870-
boolean privateOnly = privateViewers != null && !privateViewers.isEmpty();
871-
spawned.setPersistent(false);
872-
markManagedEntity(plugin, spawned);
873-
spawned.setInterpolationDuration(1);
874-
spawned.setInterpolationDelay(0);
875-
PaperCompatibility.setTeleportDuration(spawned, 1);
876-
spawned.setViewRange(48.0F);
877-
spawned.setShadowRadius(0.0F);
878-
spawned.setShadowStrength(0.0F);
879-
spawned.setVisibleByDefault(!privateOnly && visibleByDefault);
880-
spawned.setRotation(0.0F, 0.0F);
881-
spawned.setBlock(material.createBlockData());
882-
// Keep display hit volume aligned with visual scale so custom ray hit-testing is stable.
883-
spawned.setDisplayWidth(scaleX);
884-
spawned.setDisplayHeight(scaleY);
885-
spawned.setTransformation(new Transformation(
886-
new Vector3f(),
887-
new AxisAngle4f(),
888-
new Vector3f(scaleX, scaleY, scaleZ),
889-
new AxisAngle4f()
890-
));
891-
});
877+
BlockDisplay display = world.spawn(location, BlockDisplay.class);
878+
boolean blockPrivateOnly = privateViewers != null && !privateViewers.isEmpty();
879+
// See ItemDisplay spawn comment above.
880+
display.setVisibleByDefault(!blockPrivateOnly && visibleByDefault);
881+
display.setPersistent(false);
882+
markManagedEntity(plugin, display);
883+
display.setInterpolationDuration(1);
884+
display.setInterpolationDelay(0);
885+
PaperCompatibility.setTeleportDuration(display, 1);
886+
display.setViewRange(48.0F);
887+
display.setShadowRadius(0.0F);
888+
display.setShadowStrength(0.0F);
889+
display.setRotation(0.0F, 0.0F);
890+
display.setBlock(material.createBlockData());
891+
// Keep display hit volume aligned with visual scale so custom ray hit-testing is stable.
892+
display.setDisplayWidth(scaleX);
893+
display.setDisplayHeight(scaleY);
894+
display.setTransformation(new Transformation(
895+
new Vector3f(),
896+
new AxisAngle4f(),
897+
new Vector3f(scaleX, scaleY, scaleZ),
898+
new AxisAngle4f()
899+
));
892900
if (clickAction != null) {
893901
TableDisplayRegistry.register(display.getEntityId(), clickAction);
894902
}

0 commit comments

Comments
 (0)