Repository navigation
Conversation
… builds The blocks making up a wither or golem are consumed by CarvedPumpkinBlock #clearPatternBlocks, which removes them with a direct Level#setBlock call. That fires no block event at all, so nothing tells the cache those blocks are gone and the counts drift upwards until the chunk unloads. The chest a copper golem leaves behind is placed the same way and drifts the other way. Getting the exact pattern positions out of CreatureSpawnEvent is not possible, so instead snapshot the area around the spawn (the widest pattern reaches two blocks out in any orientation) and reconcile whatever actually changed a tick later, once the pattern has been cleared. This needs no knowledge of the pattern shapes and picks up the copper golem's chest for free. The spawn reasons are derived from SpawnReason#values() at runtime so that reasons added by later Minecraft versions are covered without a code change. The listener is opt-out via settings.disabled-listeners, which meant moving the entity listeners into the disable-capable list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes #182.
That issue is marked
wontfixon the grounds that the pattern block locations can't be recovered fromCreatureSpawnEvent:That's true, and this PR doesn't try to recover them. It reconciles the area instead, which sidesteps the problem entirely.
Why nothing sees these blocks today
CarvedPumpkinBlock#clearPatternBlocks(shared byWitherSkullBlock#checkSpawn) removes each pattern block with a bareLevel#setBlock(pos, AIR, 2)— notdestroyBlock, and noCraftEventFactorycall anywhere on the path. So no Bukkit block event fires at all, and none ofBlockListener,PaperBlockListener(BlockDestroyEvent),EntityListener,PistonListenerorWorldListenercan observe it. The counts drift upward until the chunk unloads andChunkListener#onChunkUnloaddrops the cache entry.Approach
CreatureSpawnEventfires before the pattern is cleared —addFreshEntity(wither, BUILD_WITHER)returns first, andclearPatternBlocksruns after it — so atMONITORevery pattern block is still in place. This snapshots the area around the spawn and reconciles whatever actually changed one tick later, viahandleModification(location, from, to, 1).Three things fall out of doing it this way rather than decrementing known pattern positions:
handleModificationresolves each block's own chunk (and addon region) independently.BUILD_COPPERGOLEM, andCarvedPumpkinBlock#replaceCopperBlockWithChestturns the copper block into a copper chest in the same tick — a block entity appearing with no event, i.e. the same bug in the opposite direction. A diff picks it up; decrementing pattern blocks would not.BUILD_SPAWN_REASONSis derived fromSpawnReason#values()filtered on theBUILD_prefix at class-init, so a new built-mob reason needs no code change here. The hot path stays a singleEnumSet#contains.A radius of 2 is sufficient for every current pattern. The widest (wither, iron golem) spawns the mob at
match.getBlock(1, 2, 0), so relative to that position the other blocks sit within 1 along the pattern's right axis and 2 along its up axis, whatever orientation it matched in. Snow and copper golems are well inside that.The delayed re-check follows the existing idiom in
BlockListener#handlePistonEvent(snapshot materials, re-read after agetRegionScheduler().runDelayed). Chunks are never loaded for the snapshot — an unloaded chunk has no cached storage to correct anyway.Listener registration
The handler is annotated
@AllowDisablingsoCreatureSpawnEventcan be turned off insettings.disabled-listeners, since it's a hot event and some servers won't want the listener at all. That required addingEntityListenerto theAllowDisablingscan and moving the entity listeners intodisableListenersinListenerManager.EntityListenerhas no other@AllowDisablingmethods, so nothing else becomes disableable as a result.Testing
Built and running on a live 26.1.2 Paper server (~10 players). Verified in game: the tile count rises by 3 as the skulls go down and returns to its pre-build value the moment the wither spawns, where previously it stayed high until the chunk unloaded. The four configured
disabled-listenersstill unregister correctly after theListenerManagerchange, and the plugin loads with no errors.One note unrelated to this change:
maindoesn't currently compile againstminecraft = "26.2.build.+", becauseCommandSourceStack#getEntityOrThrowis abstract in every published 26.2 build andCommandSenderMapper's anonymous implementation doesn't override it. I built against26.1.2.build.74-stableto get around it. Happy to open a separate issue if that's news.🤖 Generated with Claude Code