Skip to content

Implemented Procedural Process Recipe 实现了方块流程处理配方#3589

Open
CgAu3 wants to merge 17 commits into
Anvil-Dev:dev/1.21/1.6from
CgAu3:procedural
Open

Implemented Procedural Process Recipe 实现了方块流程处理配方#3589
CgAu3 wants to merge 17 commits into
Anvil-Dev:dev/1.21/1.6from
CgAu3:procedural

Conversation

@CgAu3
Copy link
Copy Markdown
Contributor

@CgAu3 CgAu3 commented May 29, 2026

Resolved #3361
写了#3360 的一部分:注册了红石计算机及其配方,并让其放置朝向玩家,不过没有实现任何功能

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Reasoning Details 我们被问到:请 review 以下代码更改。然后给出了一个很长的diff文件列表。作为代码审查助理,我们需要识别潜在问题,如代码风格违规、逻辑错误、安全漏洞等,并提供改进建议。需要以简洁明了的方式列出这些问题及相应的改进建议。

我们分析这些变更。这是一个Minecraft mod的代码更改,主要涉及新的“Procedural Process”配方系统,添加了redstone_computer相关的方块、物品、配方、语言文件等。

注意:review主要是针对代码更改本身,而不是整个项目。我们要从diff中看出有没有明显的问题。

让我们逐部分分析:

  1. 生成的资源文件:语言文件、模型、advancement、recipe json等。这些看起来是自动生成的,没有明显问题(除了注意是否有拼写错误)。但作为review,我们应该检查资源文件的正确性。例如,'en_ud.json'是翻转字母的英文,似乎是恶搞语言,但redstone_computer的条目"ɹǝʇndɯoƆ ǝuoʇspǝᴚ"看起来像是对"Redstone Computer"的翻转(小写字母翻转后变成这样?但实际上应该是翻转大写字母?需要确认。但既然是生成的不必细究)。注意wip_block的条目:"ʞɔoꞁᗺ dᴉM"对应"Wip Block"?应该是"WIP Block"?这里是小写的"Wip",可能不一致。但文件是生成的,可能是故意的。

  2. 代码文件(Java):

    • ProceduralProcessRecipeLoader: 初始化了一些配方,看起来合理。
    • RecipeHandler: 添加了init调用。
    • AnvilEventListener: 修改了AnvilOnLand事件处理,先检查ProceduralProcessStepManager.checkAnyMatches(event),如果匹配则不再执行原有的配方。但这里有一个潜在逻辑问题:如果checkAnyMatches返回false,它还会执行handleNeoAnvilRecipe。但是checkAnyMatches可能返回false不是因为不匹配,而是因为错误?但看起来是正常的。但也要注意,如果checkAnyMatches返回true,它已经执行了配方并可能改变了世界状态,然后事件处理就返回了,这没问题。但是事件监听器中的handleNeoAnvilRecipe可能因为checkAnyMatches提前返回而被跳过。不过从代码看,checkAnyMatches内部会设置return true,从而让AnvilEventListener的onLand方法直接return,跳过后续处理。所以逻辑正确。
    • RecipeManagerMixin: 在afterBuildRecipe中调用了ProceduralProcessStepManager.initialize(byName)。这很好,但需要注意:ProceduralProcessStepManager中使用了静态Map和Set,这些在reload时需要清空。代码中确实在initialize中清空了它们。但是存在并发问题吗?Minecraft的reload是单线程的,应该没问题。
    • ProceduralProcessRecipe: 定义了匹配逻辑。有一个潜在问题:getWipBlockFromContext中,它检测被铁砧砸的方块位置pos.below()和再下面一个位置pos.below().below()。这限制了只有铁砧正下方两格可以放置WIP方块。这是故意的吗?maybe need to note。
    • ProceduralProcessStepManager: 核心管理类。有很多逻辑。值得注意的点:
      • addStep方法接收couldBeFirstStep参数,但调用处有时传true,有时传false(多圈配方的multiLoopFirstStep)。多圈第一步不应该作为第一步触发?为什么?因为多圈配方后续循环的第一步是重复的步骤,但第一次循环的第一步已经在steps列表里了,multiLoopFirstStep是给后续循环用的。所以addStep(step, false)是正确的。
      • 在checkAnyMatches中,当wip==null时,它遍历possibleSteps进行匹配。但是possibleSteps是从PROCEDURAL_PROCESS_FIRST_STEP_INQUIRY获取的,而这个Map的键是状态方块,而不是BlockState。这里获取时用state.getBlock(),但如果一个方块有多种状态,如不同的BlockState,那没有问题,因为PROCEDURAL_PROCESS_FIRST_STEP_INQUIRY只按Block区分。但是AbstractProcessRecipe的getFirstInputBlock返回的是BlockStatePredicate,它能匹配多种状态,但这里只通过Block来判断是否是第一步,可能造成误匹配?不,因为后面还会调用apr.matches进行详细匹配。所以没问题。
      • 在wip存在的情况下,它根据wip的stepCount计算当前应该在哪个步骤。注意它用了一个q和r,但忽略了一个细节:wip.getStepCount()记录的是已经完成的步骤数(初始为0?在代码中第一次执行后设为1)。然后在checkAnyMatches中,它用stepCount除以oneLoopSize得到q(已完成的完整循环数),余数r是当前循环中的步骤索引(从0开始)。但是注意,当stepCount=0时(即还没执行任何步骤),应该触发第一步(索引0)。然而在checkAnyMatches中,如果wip!=null且getRecipeId!=null,那么只有当q<loopMax且r对应的步骤存在时才会执行。但stepCount为0时,r=0,符合条件,可以执行第一步。但stepCount什么时候变成0?wip创建时stepCount默认是多少?查看WipBlockEntity(未在此diff中给出),我们需要假设它初始为0。另外,在wip首次创建时(从checkAnyMatches中创建wip0),它设置stepCount=1。所以第一步执行后stepCount变成1,那么下一次触发时,stepCount=1,oneLoopSize假设是3,则r=1,即执行第二步。所以逻辑正确。
      • 注意当wip存在但recipeId不匹配或配方不在manager里时,会发生什么?checkAnyMatches会跳过,然后返回false,事件会继续处理neoanvilrecipe。这可能导致意想不到的副作用(如果wip块被当作普通方块被neoanvil配方处理)。但这是合理的,因为如果wip不是由ProceduralProcessStep创建的,它可能只是个普通的WIP方块。但是,WipBlockEntity是什么?可能还有其他用途。这里没有详细检查。
      • 在wip存在且配方正确时,它执行apr.assemble和context.accept(),然后查找新的wip。注意:apr.assemble可能会改变世界状态(例如将wip方块替换为新的wip块),所以需要重新获取wip2。然后根据是否最后一圈最后一个步骤来决定是否替换为结果方块或更新wip数据。但是有一个问题:当q==loopMax-1且r==oneLoopSize-1时,是最后一圈的最后一步,它应该将wip替换为结果块。代码中确实做了。但注意:在替换之前,它已经执行了apr.assemble,而apr.assemble可能已经修改了世界(比如它自己做了方块替换)。例如,一个ItemInjectRecipe本身会将输入方块替换为结果方块。那么在这个点上,之前的wip可能已经被替换了,所以wip2可能是null。但代码中检查了wip2!=null才进行替换。所以没问题。但如果在apr.assemble中,将方块替换成了结果块,而不是wip,那么wip2就是null,也就不会再次替换。这取决于apr的具体实现。对于ItemInjectRecipe,它一般会根据配置将方块替换为指定的结果块。如果结果块是WIP_BLOCK,那么wip2就会存在。所以对于多圈配方,每步的结果块应该是WIP_BLOCK。这是预期的。
      • 另外,在构建wip2后,设置stepCount的公式是q * oneLoopSize + step.stepIndex + 1。注意:step.stepIndex是从步骤列表获取的,对于第一圈,step是从steps列表获取的,stepIndex等于其索引。但对于多圈配方的第一步(multiLoopFirstStep),stepIndex是0,但步骤来自multiLoopFirstStep的step,并且它的stepIndex被设置为0。所以计算qoneLoopSize+step.stepIndex+1,其中q是当前已完成的循环数?等一下,在wip存在的情况下,q是通过wip.getStepCount()/oneLoopSize得到的。那么当执行完当前步骤后,新的stepCount应该是 (qoneLoopSize + step.stepIndex + 1)?但实际上,当前步骤执行之前,wip的stepCount记录的是已完成的步骤数。例如,假设oneLoopSize=3,已完成步骤数为1(即执行了第一步),此时q=0, r=1。执行第二步(stepIndex=1),执行后应将stepCount设置为2。计算:qoneLoopSize + step.stepIndex + 1 = 03 + 1 + 1 = 2,正确。如果执行的是多圈的第一步,假设第一圈已执行完毕(stepCount=3),此时q=1, r=0,执行多圈第一步(multiLoopFirstStep的stepIndex=0),执行后stepCount应为4:qoneLoopSize + step.stepIndex+1 = 13+0+1=4,正确。所以公式正确。
      • 但有一个边界情况:当执行的是最后一圈的最后一步时,它会将wip替换为结果块,所以不需要更新wip。所以代码中在else分支里更新wip。但是代码中在替换结果块后并没有return,而是继续往下执行了else分支?不,它是在if分支中直接替换了,然后返回true。没有执行else。所以没问题。
      • 还有一个小问题:在wip为null的首次匹配中,它找到了可能的步骤,执行apr.assemble后,获取wip0,并设置stepCount=1。然后返回true。但是,如果配方只有一步?比如oneLoopSize为1,且loop为1,那么第一步执行后已经完成了,应该立即替换为结果块。但这里的代码并没有处理这种情况。它仍然创建了wip0并设置stepCount=1,然后返回true。之后wip会存在,但recipeId还是那个ppRecipeId。下一次铁砧砸到这个wip时,会进入存在wip的分支,检查stepCount=1,r=0(因为oneLoopSize=1),q=1,而loopMax=1,q >= loopMax不满足,因此不会继续执行。那么wip将永远不会被替换为结果块。这是bug!所以需要处理这种情况。应该在wip为null且配方只有一步时,直接应用结果块而不是创建wip。或者至少,当执行第一步后,如果oneLoopSize==1且loop==1,则应该直接替换。但目前没有。所以这会导致单步ProceduralProcess配方无法完成。让我检查实际的配方:redstone_computer_from_procedural有三个步骤(oneLoopSize=3,loop=1),所以没问题。但另一个配方spacetime_supercomputer_from_redstone_computer有多个步骤,且loop=3,所以也是多步多圈,不会出现单步情况。但理论上是可能的,所以这是一个潜在问题。
      • 另外,注意在wip为null时,代码会获取initialBlock。它先检查pos.below()和pos.below().below()是否匹配initialBlock谓词,如果都不匹配则使用配方中的默认初始方块。这合理,但为什么只检查下面两个位置?可能因为wip方块只可能在这两个位置?但也许应该检查更多。不过这可能是设计决定。
    • ReversedSmearAlikeRecipe: 新的配方类型,反向涂抹。看起来逻辑复制了SmearRecipe但交换了输入输出位置?实际上它的构造是List inputs 和 ChanceBlockState result,与SmearRecipe类似但可能处理方式不同。注意它继承自AbstractProcessRecipe,而AbstractProcessRecipe有Property包含blockInputOffset等。在构造函数中,它调用了super(new Property()...),设置了blockInputOffset为(0,-1,0)和blockOutputOffset为(0,-1,0)。这意味着输入和输出的方块都在铁砧下方一格。这与涂抹类似?但SmearRecipe的偏移是什么?这里需要确认。但是注意到注释说“序列装配配方中的“闪炼、时移、中子辐照、砧子辐照”实际上都是反向涂抹,因为并不是物品-炼药锅-方块操作,而是方块-方块操作”。所以ReversedSmearAlikeRecipe是用于处理这些操作的。看起来合理。
  3. 其他:ModRecipeTypes中注册了新的序列化器。注意:ReversedSmearAlikeRecipe不是ProceduralProcessRecipe的一部分吗?它是作为一个独立的配方类型注册的,因为步骤中的内容可以是任何AbstractProcessRecipe的子类。所以注册它没问题。但注意:ProceduralProcessStep的CODEC使用了AbstractProcessRecipe.CODEC,这需要AbstractProcessRecipe.CODEC存在。我们检查AbstractProcessRecipe(未在diff中给出),它应该有一个CODEC和STREAM_CODEC。所以没问题。

  4. WipBlockEntity没有在此diff中,但被引用。我们需要知道它是否有必要。看起来WipBlockEntity是存在的,并且有getStepCount, setStepCount, getRecipeId, setRecipeId, getInitialBlock, setInitialBlock等方法。这些字段需要在BlockEntity中存储NBT数据。没有看到这些数据被保存和加载,但可能在另一个文件中。作为review,我们可以指出这些数据持久化的重要性,但可能超出此diff范围。

  5. 安全漏洞:没有明显的安全漏洞。主要是一些逻辑边界情况。

改进建议:

  • 处理单步ProceduralProcess配方的情况,确保结果块被正确应用。
  • 可能需要对WipBlockEntity的stepCount初始化进行确认(应为0)。
  • 建议添加日志记录以便调试,尤其是在匹配失败或异常时。
  • 确保ProceduralProcessStepManager中的静态Map/Set在reload时线程安全(目前single-threaded,ok)。
  • 检查ReversedSmearAlikeRecipe的CODEC和STREAM_CODEC是否正确:它直接使用inputs和result构造,但AbstractProcessRecipe的构造函数期望Property?实际上它的构造函数是ReversedSmearAlikeRecipe(List inputs, ChanceBlockState result),内部调用super(new Property()...)。所以解析时需要按照inputs和result字段。这是正确的。
  • 但注意:AbstractProcessRecipe.CODEC是什么?未在此diff中,但应该能处理多种子类。如果ReversedSmearAlikeRecipe继承自AbstractProcessRecipe,那么它的CODEC应该覆盖父类的?不,ProceduralProcessStep中直接使用AbstractProcessRecipe.CODEC进行序列化,而AbstractProcessRecipe.CODEC应该是多态的分发器(类多态Codec)。需要确保它包含ReversedSmearAlikeRecipe的序列化逻辑。可能已经在另外的代码中注册了。从上下文看,AbstractProcessRecipe是一个抽象基类,它的CODEC采用Type-based Dispatch,而ReversedSmearAlikeRecipe有自己的Serializer继承RecipeSerializer<?>,同时它需要被包含在AbstractProcessRecipe的分发中。但ProceduralProcessStep.CODEC使用AbstractProcessRecipe.CODEC,这要求AbstractProcessRecipe.CODEC能够解码任何AbstractProcessRecipe的子类。如果未注册ReversedSmearAlikeRecipe的分发器,会导致加载失败。但我们在diff中看到ReversedSmearAlikeRecipe有一个内部类Serializer,并注册到了ModRecipeTypes。但AbstractProcessRecipe.CODEC可能是一个独立的Codec,而不是通过RecipeSerializer来分发。需要检查是否有其他地方注册了这些子类的codec。作为一个reviewer,我们可以指出这一点:需要确保AbstractProcessRecipe的CODEC能够处理所有子类,包括新添加的ReversedSmearAlikeRecipe。否则加载配方时会出错。但因为是新代码,可能需要补充注册。

由于这是代码审查,我们应该给出具体的建议。

我们还要检查代码风格:使用lombok的@Getter,似乎可以。但注意ProceduralProcessStep中有@Setter。在加载时修改stepIndex和ppRecipeId,使用setter是可以的。但应该考虑使用final字段并在构造时设置?因为它们在后续可能会被修改?实际上它们是用于保存从数据包加载后的信息,加载后应该就固定了。但这里在initialize中修改了step的stepIndex和ppRecipeId,这意味着step对象是可变的。另一种做法是在构建配方时就设置好这些字段。但当前做法也可以工作。

在checkAnyMatches中,当wip为null且possibleSteps为空时,代码会返回false,没问题。但需要确保possibleSteps不为null(虽然Map.get返回null,但后面遍历会NullPointerException)。代码中直接for (ProceduralProcessStep step : possibleSteps),如果possibleSteps为null会抛NullPointerException。这是一个bug!在获取Map时没有做null检查。应该先检查possibleSteps是否为null。这个必须修复。

另外,在wip存在时,获取recipeHolder后,如果没有找到,那么后面的代码会尝试访问recipeHolder.get(),但recipeHolder是Optional,需要检查isPresent()。代码中已经检查了recipeHolder.isPresent(),所以没问题。但在获取recipeHolder.get().value()之后,需要确认它是ProceduralProcessRecipe。代码中也做了instanceof检查。所以安全。

还有一个性能问题:每次铁砧落地时都会调用checkAnyMatches,即使没有ProceduralProcess配方,也会检查PROCEDURAL_PROCESS_EXIST_STEP_INQUIRY。这个Set可能很大但只是contains操作,影响不大。

现在,列出问题和改进建议。

应该用中文回答,简洁明了。

潜在问题与改进建议

1. 单步 Procedural Process 配方无法完成

问题:当配方只有一个步骤(steps.size() == 1)且 loop == 1 时,首次执行后代码会创建 WIP 方块并设置 stepCount = 1,但后续铁砧再次落下时因 q >= loopMaxq=1, loopMax=1)不会触发任何操作,导致结果方块永远无法被放置。
改进建议:在 ProceduralProcessStepManager.checkAnyMatches 中处理 wip == null 的情况时,检测到该配方为单步且一圈结束,应直接执行 ppr.getResultBlock() 并替换方块,而不是创建 WIP 方块。


2. 空指针风险:PROCEDURAL_PROCESS_FIRST_STEP_INQUIRY.get(state.getBlock()) 可能返回 null

问题checkAnyMatches 方法中直接从 Map 获取列表并直接遍历,未判空。若该方块不在任何配方的第一步中,返回 null 会导致 NullPointerException
改进建议:在遍历前增加 null 检查:

List<ProceduralProcessStep> possibleSteps = PROCEDURAL_PROCESS_FIRST_STEP_INQUIRY.get(state.getBlock());
if (possibleSteps != null) {
    for (ProceduralProcessStep step : possibleSteps) { ... }
}

3. AbstractProcessRecipe 多态序列化的兼容性

问题ProceduralProcessStep.CODEC 使用 AbstractProcessRecipe.CODEC 进行多态反序列化,但新增的 ReversedSmearAlikeRecipe 可能未在该 CODEC 的分发映射中注册,导致加载数据包配方时失败。
改进建议:确认 AbstractProcessRecipe.CODEC 已通过某种机制(如 RecipeSerializer 的注册或自定义 Codec 分发器)包含了所有子类型,若未包含,需将 ReversedSmearAlikeRecipe 的序列化逻辑注册进去。


4. WIP 方块数据持久化未在此变更中体现

问题WipBlockEntity 中存储了 stepCountrecipeIdinitialBlock 等关键数据,但当前 diff 未包含其 NBT 读写逻辑。若未实现,服务器重启后这些数据会丢失,导致配方进度丢失。
改进建议:确保 WipBlockEntityload()save() 方法正确处理上述字段,并在 setChanged() 后同步数据。


5. 多圈配方第一步注册时 couldBeFirstStep 为 false,但实际含义可能混淆

问题:在 initialize 中,多圈配方的 multiLoopFirstStep 调用 addStep(step, false),注释说明“不将该步骤作为第一步触发”。但该步骤的 stepIndex 被设为 0,且仍被加入 PROCEDURAL_PROCESS_EXIST_STEP_INQUIRY(用于快速判断)。逻辑正确,但语义不够清晰,建议在 addStep 方法或注释中明确说明 couldBeFirstStep 的作用边界。
改进建议:增加文档注释,说明该参数仅控制是否将步骤加入 PROCEDURAL_PROCESS_FIRST_STEP_INQUIRY


6. 多次匹配尝试时上下文未完全重建

问题:在 wip == null 的循环中,每次匹配前创建新的 InWorldRecipeContext,但 contextOfStep 的构造只重置了 levelpos,未重置可能受前一步影响的物品数量(若物品被消耗但 matches 返回 false,计数器可能未正确回滚)。
改进建议:检查 InWorldRecipeContext 的内部实现,确保每次 contextOfStep 是全新的独立实例,或使用 contextOfStep.clone() 等方式保持状态干净。


7. 结果方块替换后未发送方块更新

问题:在配方最后一步替换为结果方块时,代码调用了 sl.setBlock(pos, entry.getKey(), 3) 并更新了 BlockEntity,但未发送方块更新包给客户端(sendBlockUpdated 只发送了旧的方块实体更新)。客户端可能无法立即看到方块变化。
改进建议:在设置方块后额外调用 sl.sendBlockUpdated(pos, oldState, newState, 3),其中 oldState 可以设为 newState 或更新前的状态。


8. 性能:每次铁砧落地都查询 Set,但该 Set 可能很大

问题:每次铁砧落地都会检查 PROCEDURAL_PROCESS_EXIST_STEP_INQUIRY.contains(state.getBlock()),如果该 Set 包含大量方块,影响不大(HashSet O(1))。但可以通过在事件监听器早期增加更多快速过滤条件(如只检测特定方块种类)来降低开销。
改进建议:可考虑在 AnvilEventListener 中增加一个可配置的过滤列表,或使用更精确的 TagKey 判断。


9. 代码风格:Magic Number

建议pos.below()pos.below().below() 中硬编码的偏移量可抽取为常量或配置值,便于后续调整(如增加检测范围)。


10. 注释中的 TODO 未实现

  • ProceduralProcessRecipe// TODO: JEI(查看配方)和Jade(查看方块实体内容:哪个配方第几步)支持
  • ReversedSmearAlikeRecipe.Builder// TODO: 砧子辐照还没人写,有人写了记得调整这里
    建议:及时补充或移除 TODO,避免遗留未完成的功能。

CgAu3 added 3 commits June 2, 2026 21:45
# Conflicts:
#	src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java
Copy link
Copy Markdown
Contributor

@ZhuRuoLing ZhuRuoLing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hermes Review — PR #3589

Implemented Procedural Process Recipe 实现了方块流程处理配方

元数据
Author CgAu3 (Abslb)
Base dev/1.21/1.6procedural
Files Changed 38

概述

该 PR 实现了一个完整的 序列装配配方(Procedural Process Recipe)系统,允许玩家通过多步骤铁砧处理流程来制作方块。主要变更包括:

  • 新配方系统ProceduralProcessRecipe + ProceduralProcessStep + ProceduralProcessStepManager — 支持多步骤、多循环(loop)的方块处理流程
  • 新方块RedstoneComputerBlock(红石计算机,#3360 的一部分)和 WipBlock(Work In Progress 方块,用于中间态显示)
  • 新配方类型ReversedSmearAlikeRecipe — 序列装配中使用的"反向涂抹"配方(伪闪炼、时移、中子辐照)
  • 配方加载与注册:ProceduralProcessRecipeLoader、ModRecipeTypes、RecipeManagerMixin 集成
  • 数据生成:redstone_computer 和 spacetime_supercomputer 的序列装配配方

🔴 严重问题

1. RedstoneComputerBlock 不发出红石信号

RedstoneComputerBlock.java 设置了 isSignalSource() → true 并注册了 POWERED 属性,但 没有重写 getSignal()getDirectSignal() 方法。这意味着即使 POWERED=true,它也不会向相邻方块输出红石信号。实际上这个方块目前只有装饰效果。

// 缺少以下方法:
@Override
protected int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) {
    return state.getValue(POWERED) ? 15 : 0;
}

建议: 添加上面的 getSignal() 方法,或者如果该方块不应发出信号,则移除 isSignalSource()POWERED 属性。

2. WipBlockEntity.loadAdditional 空值风险

WipBlockEntity.java:58:

recipeId = ResourceLocation.parse(tag.getString("Recipe"));

如果 NBT 中不存在 "Recipe" 标签(如初始放置或损坏存档),tag.getString("Recipe") 返回空字符串 "",然后 ResourceLocation.parse("") 会抛出 ResourceLocationSyntaxException

建议: 加上 tag.contains("Recipe") 判断:

if (tag.contains("Recipe")) {
    recipeId = ResourceLocation.parse(tag.getString("Recipe"));
}

3. ProceduralProcessRecipe.matches() 不验证 recipeId

ProceduralProcessRecipe.java:106-107:

public boolean matches(...) {
    WipBlockEntity wip = getWipBlockFromContext(ctx);
    if (wip == null) return false;
    return wip.getStepCount() >= steps.size();
}

该方法只检查 WIP 方块的 stepCount 是否达到步骤总数,但 不验证该 WIP 方块是否正在执行同一个 recipe。虽然在实际执行中 checkAnyMatches 会根据 recipeId 找到正确的配方,但这里的 matches() 本身是不安全的。


⚠️ 警告

4. getResultItem 对 BlockState 的处理有误

ProceduralProcessRecipe.java:124:

BlockState state = resultBlock.state();
if (state.isEmpty() || state.isAir()) return Items.ANVIL.getDefaultInstance();

ChanceBlockState.state() 返回的是 BlockState 而非 Optional<BlockState>,所以 state.isEmpty() 在这里无效(BlockState 没有 isEmpty() 方法——它用的是 isAir())。需要确认 ChanceBlockState 的 API。

5. WipBlockEntityRenderer 使用了错误的渲染类型

WipBlockEntityRenderer.java:49:

buffer.getBuffer(RenderTypeHelper.getMovingBlockRenderType(renderType)),

getMovingBlockRenderType 是给 正在移动的方块(如活塞推动)使用的,不是给静态 BlockEntity 渲染的。对于静态渲染,应该直接使用原始 renderType

buffer.getBuffer(renderType),

这可能导致半透明或 cutout 类型的方块出现渲染异常。

6. ModBlocks 中 RedstoneComputer 的 datagen 引用了 PulseGeneratorBlock 的属性

ModBlocks.java:3747-3777:

.with(PulseGeneratorBlock.FACING, Direction.SOUTH)  // 应该用 RedstoneComputerBlock 的
.with(PulseGeneratorBlock.POWERED, false)            // 应该用 RedstoneComputerBlock 的

虽然 PulseGeneratorBlock.FACINGPulseGeneratorBlock.POWERED(都来自 BlockStateProperties)功能上不会出错,但这是一个代码异味(code smell)——应该引用自己类的属性。

7. 缺少 JEI/Jade 集成

PR 代码中已标注 // TODO: JEI(查看配方)和Jade(查看方块实体内容:哪个配方第几步)支持。没有这些集成,玩家在游戏中无法发现或查看序列装配配方的流程,这将严重影响可用性。建议创建 issue 跟踪。

8. checkAnyMatches 中多余的 sendBlockUpdated

ProceduralProcessStepManager.java 中多处使用了 sl.setBlock(pos, entry.getKey(), Block.UPDATE_ALL) 后又立即调用 sl.sendBlockUpdated(pos, ...)UPDATE_ALL flag 已经包含了客户端更新,所以 sendBlockUpdated 是多余的,会触发不必要的网络流量。


💡 建议

9. 性能:第一步查询时重复创建 context

checkAnyMatches 中处理第一步(无 WIP 方块时),代码为每个可能的 step 创建了一个新的 InWorldRecipeContext,并在循环中重复从 level 获取信息。如果 PROCEDURAL_PROCESS_FIRST_STEP_INQUIRY 中有大量第一步选项,这会带来不必要的开销。

10. ReversedSmearAlikeRecipe 的 codec 嵌套

ReversedSmearAlikeRecipe.java:74:

ChanceBlockState.CODEC.codec()
    .fieldOf("result")

ChanceBlockState.CODEC 已经是 MapCodec<ChanceBlockState> 了,这里又调用了 .codec() 再包装成 FieldDecoder,可简化为直接用 ChanceBlockState.CODEC.fieldOf("result")

11. Grass Block 配方修改的副作用

PR 修改了 grass_block.jsonblock_compress 配方和 BlockSmearRecipeLoader 中的一条调用(将苔藓+土=草方块的配方从涂抹改回压合,同时新增了反向涂抹配方)。这个语义变更应该在 PR 描述中明确说明。


✅ 做得好的地方

  • 架构设计清晰:将步骤(Step)和配方(Recipe)分离,支持 multi-loop,设计灵活
  • /reload 的支持:每次数据包重载时清空并重建查询表
  • 错误处理:当 step 不是 AbstractProcessRecipe 时报警告并跳过
  • 校验完善ProceduralProcessRecipeBuilder.validate() 对 loop 次数、空步骤进行了校验
  • WipBlock 的掉落物处理getDrops() 根据 initialBlock 返回对应掉落,设计合理
  • 比较器输出:WipBlock 实现了 getAnalogOutputSignal,让红石比较器可以读取进度

审核结论

类别 数量
🔴 严重 3
⚠️ 警告 5
💡 建议 3

建议: 至少需要修复第 1、2 项(红石信号输出、NPE 风险)再合并。第 4、5 项也建议在合并前处理。JEI 集成(第 7 项)可推迟到后续 PR,建议创建 issue 跟踪。

Reviewed by Hermes Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TODO] 方块流程处理

2 participants