Skip to content

Refactor Amulet system 重构护符系统 - #5042

Draft
QiuShui1012 wants to merge 4 commits into
Anvil-Dev:dev/1.21/1.6from
QiuShui1012:amulet/1.21/1.6
Draft

QiuShui1012 wants to merge 4 commits into
Anvil-Dev:dev/1.21/1.6from
QiuShui1012:amulet/1.21/1.6

Conversation

@QiuShui1012

@QiuShui1012 QiuShui1012 commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

@Gugle2308

Copy link
Copy Markdown
Collaborator

⏳ Retrying in 2.2s (attempt 1/3)...

@Gugle2308

Copy link
Copy Markdown
Collaborator

⏳ Retrying in 5.7s (attempt 2/3)...

@Gugle2308

Copy link
Copy Markdown
Collaborator

❌ API failed after 3 retries — HTTP 503: Service temporarily unavailable

@Gugle2308

Copy link
Copy Markdown
Collaborator

API call failed after 3 retries: HTTP 503: Service temporarily unavailable

@QiuShui1012
QiuShui1012 marked this pull request as draft September 26, 2026 08:45
@Gugle2308

Copy link
Copy Markdown
Collaborator

⏳ Retrying in 2.3s (attempt 1/3)...

@Gugle2308

Copy link
Copy Markdown
Collaborator

⏳ Retrying in 5.0s (attempt 2/3)...

@Gugle2308

Copy link
Copy Markdown
Collaborator

❌ API failed after 3 retries — HTTP 503: Service temporarily unavailable

@Gugle2308

Copy link
Copy Markdown
Collaborator

API call failed after 3 retries: HTTP 503: Service temporarily unavailable

@Gugle2308

Copy link
Copy Markdown
Collaborator

验证工作已完成。以下是审查结论。

代码审查摘要 — PR #5042(Draft)

操作: edited | 当前状态:draft(草稿),mergeable_state=clean
范围: 70 个文件(28 新增 / 13 删除 / 1 重命名)· +1749 / −1342 · 4 个 commit
基线: dev/1.21/1.6 @ 989bf7b3 → head amulet/1.21/1.6 @ a16ee01f6(与 API 元数据一致,我审查的就是这个 revision)
环境: MC 1.21.1 / NeoForge 21.1.238 / anvillib 2.0.0+snapshot.534
diff 完整性: patch-diff 的 70 文件、+1818/−1411(含 +/- 头行 → 1748/1341)与 API 的 changed_files:70 / +1749 / −1342 对得上,md5 bf645a2b,无截断。

✅ 已独立核对的项(不阻塞)

核对项 结论
悬空引用(head 树全量 grep) hasAmuletInInventory / AmuletAbilities(旧类) / ModAmuletTypes / AMULET_TYPE / amulet_type / consumeFood / canActLike / ModDataAttachments.DISCOUNT_RATE / isGolemProtected 在 src 内 零残留;SpiderMixin 已从 anvilcraft.mixins.json 摘除 ✅
LivingEntityMixin.letAmuletProcess 的 mixin 目标 用 Mojang 1.21.1 client_mappings + 混淆客户端字节码核对:LivingEntity.addEatEffect(FoodProperties) 存在(btn.a(Lcpr;)V),且其内部对 addEffect(MobEffectInstance)Z(btn.b(Lbrz;)Z)只有 1 处调用 → @WrapOperation 目标可解析、单点命中 ✅
其它 MC API EntityEvent.TAMING_SUCCEEDED、TagPredicate.is(TagKey)(映射 line 19)、EntityType.is(HolderSet)(旧 AnvilAmulet 已有同用法)、Math.clamp(int,int,int)(base 多处 int x = Math.clamp(...))均存在 ✅
依赖库 API(下载 anvillib jar 解嵌套 jarjar 常量池核对) IExpression.of(double)/ref(String)/CODEC/STREAM_CODEC/evaluate(double)/evaluateInt(Arguments)、Arguments.of(double...)、withAll(List,List)、Arguments$Value$Single、LibBuiltInFunctions.ADD.call(IExpression...) 全部存在;且 base 仓库 RepairMaterialFrostMaterialPredicate 已有 Arguments.of(int).withAll(List.of(name), List.of(new Arguments.Value.Single(int))) 同写法 ✅
旧语义等价性 逐值对比删除的 GiveEffectAmulet/DiscountAmulet/ImmuneDamageAmulet/ImmuneEntityAmulet/WrappedOthersAmulet/AnvilAmulet/ComradeAmulet:旧 inventoryTick 是「谓词命中就 return」→ 旧 inLava/inWater 实际含义就是 不在岩浆/水里才生效,新命名 notInLava/notInWater 才是对的,行为保留 ✅
顺带修掉的 NPE 旧 AnvilAmulet 的 Objects.requireNonNull(getDirectEntity()) / getWeaponItem() → 新 ImmuneAnvilDamageAmuletEffect 已判空 ✅
数据兼容 ModComponents.AMULET 仍存 ResourceKey<Amulet>、注册表 id(anvilcraft:amulet)与条目 id(emerald/topaz/…)不变 → 旧物品 NBT 可读;无新增物品/配方,不需要跑 runData ✅
删除的 cogwheel_amulet.png 本 mod 内无 item/model 引用(只有 ItemTagLoader 的 addOptional,那是外部 mod 的物品)→ 属孤儿资源清理 ✅

⚠️ 建议修改

  1. AmuletAbilitiesEventListener.onInventoryTick 缺侧向守卫,且判定范围从 ServerPlayer 扩到全部 LivingEntity。
    该类自己的文档(AmuletManager.shouldEvaluate)写明「效果判定全部由服务端负责,只有重力预测与交互例外」,但这条每 tick 的生命周期 pass(enabled/disabled 两次遍历)没有调用 shouldEvaluate,同时调用点从旧的 Inventory.tick(仅 ServerPlayer)换成了 EntityTickEvent.Post(双端 × 所有生物)。于是每个生物每 tick 都要:post 一次 AmuletEvent.Find(Curios 还会做一次能力查询)→ 分配 LinkedHashMap + IdentityHashMap → 遍历全部 16 个注册护符在 disabled pass 里逐个 trigger(其中 AttributeAmuletEffect 会对没戴护符的生物每 tick 尝试移除一次修饰符)。建议加 if (!AmuletManager.shouldEvaluate(entity)) return; 并/或对「身上没有护符」的实体早退(或按 tick 缓存 getActiveEffects)。

  2. GravityManager.ignoresCelestialGravity 由 Player 放宽到 LivingEntity(同类问题的热路径版本)。
    它被 Entity.getGravity()(RETURN 注入)与 Entity.tick TAIL、以及 calculateSweptGravity 调用——即每个实体每 tick 多次、双端。旧代码 entity instanceof Player && (...) 会短路,现在每个怪物都要跑一遍完整护符求值(事件派发 + 分配 + flatten 遍历)。只有玩家可能戴护符,建议保留 Player 早退或用每 tick 记忆化,别让「客户端预测例外」变成所有生物的开销。

  3. NearestAttackableTargetGoalMixin / PhantomGoalMixin 的 selector 没有缓存。
    shouldIgnoreTarget 每次都重建整张 active-effect 表,而 selector 会对范围内每个候选实体求值;本 PR 自己给 AvoidEntityGoalMixin 加了 IdentityHashMap 每调用缓存,这里建议照做(同一个 canUse/目标选择周期内按玩家缓存)。

  4. DiscountAmuletEffect 的累加初值(1F)与 ModAmuletEffectContextKeys.DISCOUNT_RATE 的 javadoc(「为 0 时表示无折扣」)互相矛盾,且加法组合会踩坑。
    消费端 VillagerMixin 是 k = floor(rate * 基础价); addToSpecialPriceDiff(-max(k,1)),即把该值当「减价比例」。若插件按文档写 x+0.1,从 1F 起步会得到 1.1 → 直接把价格压到 1 个物品(原版 Math.max(1, ...) 兜底)。EMERALD 用的是常量 0.3,现网无 bug,但建议明确「只支持乘法组合」或统一 0/1 约定。

  5. 几处行为变更建议在描述/迁移指南里点名(看起来是有意为之,但都影响手感):

    • IEnchantedGold 不再因 ABNORMAL 护符早退 → 戴 ABNORMAL 护符时附魔金的 LUCK 与「移除虚弱/缓慢/饥饿」不再被抑制,而 IAbnormal(负面效果)仍走 IMMUNE_ABNORMAL_ITEM。与新文档「免疫负面效果」自洽,但属于平衡改动,建议写明。
    • ABNORMAL 的「进食免疫」覆盖面收窄:旧的 ThreadLocal + MobEffectEvent.Applicable 覆盖 finishUsingItem 期间任意 addEffect,新的只拦 addEatEffect(即 FoodProperties.effects)。自定义 finishUsingItem 里直接给效果的物品不再受保护。
    • SILENCE / FEATHER / ANVIL 的免疫方式由「阻止施加(DO_NOT_APPLY)」改为「每 tick 移除」。差异是效果会先被施加再移除:MobEffectEvent.Added 驱动的行为(成就判定、其它 mod 的“效果添加”回调、施加反馈)仍会触发。
    • GiveMobEffectAmuletEffect.REFRESH_TICKS = 2(旧为 200):刷新型 buff(TOPAZ 急迫、FEATHER 缓降、RUBY 力量、ARMADILLO/SAPPHIRE 抗性)时长恒为 1~2 tick。我核对了 1.21.1 混淆客户端字节码:Gui 的效果渲染方法里硬编码了 200 这个阈值(对应原版「即将失效」的闪烁/计时 0:00 表现)。请确认这正是 [Bug] 护符 #5043 期望的 HUD 结果——现在不再是「计时条卡在 3:20 不动」,而是「图标常闪 + 计时 0:00」。
  6. fixed #5043 无法确认已修复: issue [Bug] 护符 #5043(「宝石/羽毛护符 buff 持续时间每 tick 刷新」,期望「和其他正常的护符刷新一致」)目前仍是 open,且这是 draft PR(合并后才会自动关闭)。结合上面最后一条,建议作者确认新表现与 issue 期望一致后再标 fixed。另:PR 处于 draft,本身不可合并。

💡 建议

  • ImmuneMurdererDamageAmuletEffect(45 行)在 head 树里只有自己引用自己(旧的 ImmuneEntityAmulet 至少还注册成了类型);GiveMobEffectAmuletEffect.Entry.CODEC/STREAM_CODEC 同样不可达(Amulet 本身没有 codec,护符是代码注册的)。要么接上护符,要么在文档里明确它是给附属用的公开 API。
  • Amulet / WrapOtherAmuletEffect 的 flatten 是「惰性缓存 + 无失效」:WrapOtherAmuletEffect.computeFlattenEffects() 通过 ModRegistries.AMULET.get(key) 解析被包覆护符,若在注册表填充完成前被首次调用(校验、附属提前调用),会把不完整结果永久缓存。建议注册冻结时 eager 计算,或在 javadoc 写明「须在注册表加载后调用」。
  • ImmuneFriendlyDamageAmuletEffect 从「提供该效果的物品堆」读 COMRADES 组件;一旦它通过 WrapOtherAmuletEffect(GEM/NATURE 那种)生效,传入的是外层护符堆(没有 COMRADES),免疫会静默失效。当前没有护符包覆 COMRADE,所以是潜在的 API 陷阱,建议在 IAmuletEffect#amulet 参数文档里点明,或把组件来源改为护符自身数据。
  • DummyArmadillo.CACHE(静态 HashMap<UUID, Entity>,抓 Level 引用)已按惯例在登出时清理 ✅,但与 DummyCat/Wolf 一样没在维度切换/存档卸载时清理,可一并处理。
  • 迁移指南目前是 github user-attachments 链接(AMULET_REFACTOR_MIGRATION.md)。既然是删除公开类型(IAmulet、amulet_type 注册表、IAmulet#canActLike)的破坏性重构,建议把文件放仓库内(或写进 PR 描述)以免附件失效。
  • PR 标题偏笼统,没体现「护符改为效果制 + 支持所有生物 + 移除 amulet_type 注册表(API 破坏)+ 修 [Bug] 护符 #5043」。本次未自动改标题:gh auth status 显示 token 失效(Active account: true 但 invalid),而按流程不得 export GITHUB_TOKEN。建议标题:
    Refactor amulet system into effect-based contexts and support all living entities 重构护符系统为效果制并支持所有生物

🟢 做得好的地方

  • context-key 设计(输入键/输出键 + 仅副作用效果关心 ENABLED)干净,flatten + 按引用去重正确解决了「同时佩戴包覆者与被包覆者时效果重复触发」,disabled pass 只回撤护符自身效果的分工也是对的。
  • 文档注释质量高:shouldEvaluate 的例外清单、ImmuneMobEffectAmuletEffect 里「查询上下文没有 ENABLED」的自我说明、REFRESH_DURATION 的语义都写清楚了。
  • 顺带修了两处 NPE;IgnoreGravity 不再限玩家;蜘蛛/铁傀儡/骷髅/苦力怕的目标逻辑从硬编码类判断改为类型谓词(顺带修掉旧 clazz.isAssignableFrom(AbstractSkeleton.class) 方向写反导致部分骷髅漏判的问题);SpiderMixin 删除后由 AvoidEntityGoalMixin 的 Armadillo 伪装统一承接,并加了每调用缓存。

结论: COMMENT(Draft,先不要合并) — 架构与语义核对通过、mixin/依赖库 API 均已验证可编译可注入;阻塞项主要是 ①每 tick 全生物双端的护符求值(含热路径重力)与缺侧向守卫,②#5043 的 HUD 表现需确认,③行为变更需在描述/迁移指南中写明。修掉 ①②后可转 ready for review。

🧪 测试建议

被测目标 推荐场景 优先级
AmuletManager.getActiveEffects / isAmuletActive 同时佩戴 GEM 与 EMERALD(引用去重:效果只触发一次、raffle 概率不重复给);只戴 EMERALD 时 GEM 判为未激活 🔴
AmuletAbilitiesEventListener.onInventoryTick 的 disabled pass 摘下 ANVIL 后击退抗性修饰符必须被移除;客户端侧不应产生残留状态(双端一致性) 🔴
GiveMobEffectAmuletEffect 时长/上下界 RUBY 不在岩浆→时长累加并在 3600 封顶;REFRESH_TICKS=2 的刷新型 buff 在条件持续时永不超时、条件消失后 ≤2 tick 内消失 🔴
LivingEntityMixin.letAmuletProcess ABNORMAL 护符进食负面效果不生效、正面效果仍生效;非进食路径的 addEffect 不受影响 🟡
IgnoreMobTargetAmuletEffect + 各 mixin ARMADILLO↔蜘蛛、DOG↔全部骷髅、CAT↔苦力怕/幻翼、EMERALD↔铁傀儡(含 setLastHurtByMob) 🟡
DiscountAmuletEffect 表达式 x*0.8 叠加与 IExpression.of(0.3);rate>1 时价格兜底不为 0/负 🟡
WrapOtherAmuletEffect.flatten 惰性缓存 注册表加载前调用一次后再调用,验证缓存不残留空集 🟢

由 Hermes Agent 审查(diff: patch-diff.githubusercontent.com;mixin/库 API 用 Mojang 1.21.1 mappings + 混淆客户端字节码、anvillib 2.0.0+snapshot.534 jar 常量池逐项核对)

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'offline-pr-evidence-verification' (1 replacement). · Patched SKILL.md in skill 'registry-driven-refactor-pr-review' (1 replacement).

This branch has not been deployed

No deployments
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.

[Bug] 护符

2 participants