Add DragonRod and Devourer's devour priority judgement 吞噬额外判断#3383
Add DragonRod and Devourer's devour priority judgement 吞噬额外判断#3383Chrise2024 wants to merge 2 commits intoAnvil-Dev:dev/1.21/1.6from
Conversation
Chrise2024
commented
Mar 31, 2026
- 修复了 [Feature] 龙杖破坏额外判定 #3382 的问题
- 龙杖,方块吞噬器在破坏依附方块时可以正确破坏而不是直接掉落
There was a problem hiding this comment.
Pull request overview
This PR addresses issue #3382 by adding an attachment-aware “devour ordering” so blocks that would otherwise pop off (torches, redstone dust, etc.) can be handled before their supporting blocks are removed.
Changes:
- Added
DevouringLevelReader(a wrapperLevelReader) to simulate “to-be-devoured blocks are already air” when evaluatingBlockState#canSurvive. - Updated
DragonRodItemdevour logic to split blocks into “primary” vs “secondary” destruction passes using survivability checks. - Updated
BlockDevourerBlockdevour logic to use survivability checks (plus chain-devour handling) to influence devour order.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/main/java/dev/dubhe/anvilcraft/util/DevouringLevelReader.java |
New fake LevelReader used to evaluate survivability as if devoured blocks were already removed. |
src/main/java/dev/dubhe/anvilcraft/item/DragonRodItem.java |
Uses the fake reader to reorder destruction and reduce attachment pop-off drops. |
src/main/java/dev/dubhe/anvilcraft/block/BlockDevourerBlock.java |
Applies survivability-based ordering to block devourer (including chain devouring). |
Comments suppressed due to low confidence (1)
src/main/java/dev/dubhe/anvilcraft/block/BlockDevourerBlock.java:33
import net.minecraft.world.level.block.Blocks;appears unused in this file (noBlocks.references). If your build enables unused-import checks, this will fail compilation; please remove the import.
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.DirectionalBlock;
import net.minecraft.world.level.block.DoublePlantBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.RenderShape;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| @Override | ||
| public @Nullable ChunkAccess getChunk(int i, int i1, ChunkStatus chunkStatus, boolean b) { | ||
| return null; |
There was a problem hiding this comment.
getChunk(...) currently always returns null. Some BlockState#canSurvive implementations call into LevelReader#getChunk/chunk access, which can cause NPEs or incorrect behavior when using this reader. Delegate to parentLevel.getChunk(...) (or implement a safe passthrough) instead of returning null.
| return null; | |
| return parentLevel.getChunk(i, i1, chunkStatus, b); |
| * 用来执行吞噬判断的假世界,仅修改了 {@link LevelReader#getBlockState(BlockPos) LevelReade.getBlockState} 的逻辑. | ||
| * 原则上来说应该重写所有方法以规避意料之外的问题( |
There was a problem hiding this comment.
Class Javadoc has a typo in the link label (LevelReade) and an unclosed parenthesis in the second line, which makes the comment misleading and renders poorly in Javadoc. Please correct the reference text and close the sentence/parenthesis.
| * 用来执行吞噬判断的假世界,仅修改了 {@link LevelReader#getBlockState(BlockPos) LevelReade.getBlockState} 的逻辑. | |
| * 原则上来说应该重写所有方法以规避意料之外的问题( | |
| * 用来执行吞噬判断的假世界,仅修改了 {@link LevelReader#getBlockState(BlockPos) LevelReader.getBlockState} 的逻辑. | |
| * 原则上来说应该重写所有方法以规避意料之外的问题。 |
| public BlockState getBlockState(BlockPos blockPos) { | ||
| BlockState blockState = parentLevel.getBlockState(blockPos); | ||
| if (devouringList.contains(blockPos) && BlockDevourerBlock.canDevour(blockState)) { | ||
| return AIR_STATE; | ||
| } | ||
| return blockState; |
There was a problem hiding this comment.
getBlockState uses devouringList.contains(blockPos) on a List, making lookups O(n). canSurvive checks can invoke many getBlockState calls, and the devour list can be dozens/hundreds of positions, so this can become a hot path. Consider storing devour positions in a Set (e.g., LongSet using BlockPos#asLong) for O(1) lookups.
| List<BlockPos> devouringPosList = Streams.stream(devouringPoses).map(BlockPos::immutable).toList(); | ||
| DevouringLevelReader devouringLevelReader = new DevouringLevelReader(level, devouringPosList); | ||
|
|
||
| for (BlockPos devouringPos : devouringPoses) { | ||
| List<BlockPos> secondaryDevouringPosList = new ArrayList<>(devouringPosList.size()); | ||
| for (BlockPos devouringPos : devouringPosList) { | ||
| BlockState devouringState = level.getBlockState(devouringPos); | ||
| if (devouringState.isAir()) continue; | ||
| if (!BlockDevourerBlock.canDevour(devouringState)) continue; | ||
| if (devouringState.is(ModBlockTags.BLOCK_DEVOURER_PROBABILITY_DROPPING) | ||
| && level.random.nextDouble() > 0.05) { | ||
| level.destroyBlock(devouringPos, false); | ||
| continue; | ||
| } | ||
|
|
||
| devouringPos = MultiPartBlockUtil.getChainableMainPartPos(level, devouringPos); | ||
| devouringState = level.getBlockState(devouringPos); | ||
| boolean shouldDrop = !devouringState.is(ModBlockTags.BLOCK_DEVOURER_PROBABILITY_DROPPING) |
There was a problem hiding this comment.
DevouringLevelReader is built from devouringPosList before positions are normalized via MultiPartBlockUtil.getChainableMainPartPos(...), but the later survivability check uses the normalized devouringPos. If the main-part position differs and isn’t in devouringPosList (e.g., bed foot/head, double blocks at edge of range), the fake world won’t treat it as devoured, which can defeat the attachment-order fix. Build the devour-position collection after normalizing to main parts (and de-duplicate), or ensure both original and normalized positions are included in the reader’s devour set.
| @@ -175,6 +176,16 @@ public static void devourBlock( | |||
| if (!(devouringState.getBlock() instanceof DoublePlantBlock)) { | |||
| devouringState.getBlock().playerWillDestroy(level, devouringPos, devouringState, player); | |||
| } | |||
|
|
|||
| // 通过假世界判断需要先被破坏的方块 | |||
| if (!devouringState.canSurvive(devouringLevelReader, devouringPos)) { | |||
| level.destroyBlock(devouringPos, false); | |||
| } else { | |||
| secondaryDevouringPosList.add(devouringPos); | |||
| } | |||
There was a problem hiding this comment.
The probability-dropping path behavior changed: previously when the random check failed, the block was destroyed immediately and the loop continued (skipping playerWillDestroy and survivability ordering). Now playerWillDestroy is always called and the block is scheduled/destroyed later, even when shouldDrop is false. If those side effects (events/stats/block-specific logic) were intentionally skipped before, this is a behavior change; consider keeping the early-destroy/continue behavior for the no-drop case to preserve prior semantics.
| if (level.getBlockState(devourBlockPos).canSurvive(devouringLevelReader, devourBlockPos)) { | ||
| devourSingleBlockInternalLogic(level, anvil, devourBlockPos, filteredBlockPosList, itemHandlerList, center); | ||
| } else { | ||
| secondaryBlockPosList.add(devourBlockPos); | ||
| } | ||
| } | ||
| DevouringLevelReader chainDevouringLevelReader = new DevouringLevelReader(level, chainDevourBlockPosList); | ||
| for (BlockPos devourBlockPos : chainDevourBlockPosList) { | ||
| if (level.getBlockState(devourBlockPos).canSurvive(chainDevouringLevelReader, devourBlockPos)) { | ||
| devourSingleBlockInternalLogic(level, anvil, devourBlockPos, filteredBlockPosList, itemHandlerList, center); | ||
| } else { | ||
| secondaryBlockPosList.add(devourBlockPos); | ||
| } | ||
| } | ||
| for (BlockPos devourBlockPos : secondaryBlockPosList) { | ||
| devourSingleBlockInternalLogic(level, anvil, devourBlockPos, filteredBlockPosList, itemHandlerList, center); | ||
| } |
There was a problem hiding this comment.
The survivability-based ordering appears inverted. Blocks that cannot survive when other devoured blocks are treated as air are the ones that need to be devoured first (to avoid attachment blocks popping/dropping after their support is removed). Here, canSurvive(...) blocks are processed first and non-survivors are deferred to secondaryBlockPosList, which likely recreates the original drop problem. Swap the branches so non-survivors are devoured before survivors (apply similarly to the chain loop).
| @@ -246,19 +251,31 @@ public void devourBlock( | |||
| chainDevourBlockPosList.add(chainDevourBlockPos.immutable()); | |||
| } | |||
| } | |||
|
|
|||
| devourSingleBlockInternalLogic(level, anvil, devourBlockPos, filteredBlockPosList, itemHandlerList, center); | |||
| if (level.getBlockState(devourBlockPos).canSurvive(devouringLevelReader, devourBlockPos)) { | |||
| devourSingleBlockInternalLogic(level, anvil, devourBlockPos, filteredBlockPosList, itemHandlerList, center); | |||
| } else { | |||
| secondaryBlockPosList.add(devourBlockPos); | |||
| } | |||
| } | |||
| DevouringLevelReader chainDevouringLevelReader = new DevouringLevelReader(level, chainDevourBlockPosList); | |||
| for (BlockPos devourBlockPos : chainDevourBlockPosList) { | |||
| if (level.getBlockState(devourBlockPos).canSurvive(chainDevouringLevelReader, devourBlockPos)) { | |||
| devourSingleBlockInternalLogic(level, anvil, devourBlockPos, filteredBlockPosList, itemHandlerList, center); | |||
| } else { | |||
| secondaryBlockPosList.add(devourBlockPos); | |||
| } | |||
There was a problem hiding this comment.
Survivability checks are done on devourBlockPos/chainDevourBlockPos before devourSingleBlockInternalLogic normalizes positions via MultiPartBlockUtil.getChainableMainPartPos(...). If normalization changes the actual broken position, the ordering decision may be based on the wrong block, and the fake-world “devoured positions” set may not include the normalized position. Consider normalizing (and de-duplicating) positions before performing canSurvive ordering and before constructing the DevouringLevelReader.