Restore paintings and item frames on Folia without teleporting - #1011
Merged
Intelli merged 4 commits intoOct 2, 2026
Merged
Conversation
Rollback spawns a painting or item frame and then teleports it into place, but Folia always throws from Entity#teleport, so paintings are lost and frames never get their recorded facing or item. On Folia the entity is now spawned directly at its recorded block with its facing, art or item set in the pre-spawn consumer. Paper and Spigot are unchanged.
❌ Deploy Preview for coreprotect failed. Why did it fail? →
|
Contributor
|
Thanks -- automated review is requesting the following change:
|
Compiling against the current API binds World#spawn(Location, Class, Consumer) to the java.util.function.Consumer overload, which only exists from Bukkit 1.20.2. Folia 1.19.4 and 1.20.1 only have the org.bukkit.util.Consumer overload, so restoring a painting or item frame there threw NoSuchMethodError. The spawn now goes through BukkitAdapter.spawn, which calls the java.util.function.Consumer overload when World has it and otherwise invokes the org.bukkit.util.Consumer overload by reflection.
…a-hanging-restore
…a-hanging-restore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On Folia,
/co rollbackof a broken painting does not bring it back. The restore spawns the entity and then callsEntity#teleport, which Folia refuses withUnsupportedOperationException: Must use teleportAsync while in region threading. This change spawns paintings and item frames directly at their recorded spot on Folia, with facing and art set before they join the world.The problem
HangingUtil.spawnHangingrestores a hanging entity in two steps:spawnBlockwithWorld#spawn.teleport()it to the recorded block, then callsetFacingDirectionandsetArt(paintings,utility/entity/HangingUtil.java:136-143) orsetFacingDirectionandsetItem(frames,:153-163).On Folia,
CraftEntity.teleport0always throwsUnsupportedOperationException("Must use teleportAsync while in region threading")(checked in the Folia 1.21.11 server jar).HangingUtil.java:141is outside anytry, so the exception escapesspawnHanging,setFacingDirectionandsetArtnever run, and the error is reported from the rollback's region task. In the test below, both paintings were missing after the rollback, and the console logged the exception once per painting.:156is inside atrywhosecatchis empty, so the exception is swallowed andsetFacingDirectionandsetItemnever run. In the test below the frames still came back correctly, because their spawn spot was already their recorded block, the only wall picked the right facing, and the frame's item is restored separately by the container rollback. A frame whose spawn spot is not its recorded block, or where the spawn picks a different face (floors, ceilings, corners), is left as the spawn placed it. I did not build a test for those cases.The fix
On Folia only,
spawnHangingspawns the painting or frame directly at its recorded block withWorld#spawn(location, class, consumer). The consumer callssetFacingDirection(face, true)andsetArt(art, true)for paintings, orsetFacingDirection(face, true)andsetItemfor frames, before the entity is added to the world. No teleport is needed.Paper and Spigot keep the existing spawn-then-teleport path unchanged.
Behaviour change
Risk
Folia only. The consumer uses the same
setFacingDirection(face, true)force flag the existing path uses.Testing
Build:
mvn packagepasses.Live,
HangingChecktest plugin on a fresh server with SQLite: it builds a wall, hangs a 1x1 painting (kebab), a 2x1 painting (pool), a frame holding a diamond and an empty frame, firesHangingBreakEventwith causeEXPLOSIONfor each and removes them, runs/co rollback u:#explosion t:10m r:#global, then compares every hanging entity's block, facing, art and item against the snapshot taken before.UnsupportedOperationExceptionatHangingUtil.java:141twice