预言(Predictions)权重重写

基本信息

属性 值
实现类 core/entities/PredictionHandler.java
调用时机 postInit 末尾,WitcheryExtras.java:89 PredictionHandler.adjustPredictions()
作用对象 Witchery 本体的 PredictionManager 注册表
本 mod 注册的预言数 0(只改权重,不新增)
数据结构 反射拿到的 Hashtable<Integer, Prediction>

Witchery 的预言系统(com.emoniph.witchery.predictions.*)不在本 wiki 收录范围 (见 分类页)。本页只记本 mod 如何覆写既有预言的权重。

注册表访问靠反射

core/types/Witchery_Predictions.java:13-25:

Object f = ReflectionUtils.getField(PredictionManager.instance(), "predictions");
Hashtable<Integer, Prediction> registry;
try {
    if (f != null) {
        registry = (Hashtable<Integer, Prediction>) f;
        if (registry != null) {
            return registry;
        }
    }
} catch (Throwable t) {}
return new Hashtable<Integer, Prediction>();
  • 反射字段名:PredictionManager 实例的 predictions
  • catch (Throwable t) {} 是空 catch(Witchery_Predictions.java:22)——反射失败被静默吞掉, 返回一个空 Hashtable,调用方无法区分「没有预言」和「反射失败」
  • 对外 API:RecipeManager.Predictions.getPrediction(int aID) / .getPredictions() / .add() / .remove() (api/RecipeManager.java:431-466)

主流程

PredictionHandler.java:79-98:

public static void adjustPredictions() {
    int[] aNewValues = new int[] { AsmConfig.chancePredictionSpawnZombie, /* ... */ };
    for (int i = 0; i < 18; i++) {
        Prediction aOld = RecipeManager.Predictions.getPrediction(i);
        if (aOld != null) {
            if (aOld.itemWeight != aNewValues[i - 1]) {
                RecipeManager.Predictions.remove(aOld);
                RecipeManager.Predictions.add(generateNewPrediction(aOld, aNewValues[i - 1]));
            }
        }
    }
}

只在新旧权重不同时才重建(PredictionHandler.java:92): remove(aOld) 后 add(generateNewPrediction(aOld, newWeight))。

⚠️ 源码缺陷:数组越界 + 下标错位

实测(脚本解析 PredictionHandler.java:80-88):数组字面量共 17 个元素。

下标 配置项
0 chancePredictionSpawnZombie
1 chancePredictionSpawnSkeleton
2 chancePredictionSpawnEnt
3 chancePredictionStumbleAndFall
4 chancePredictionExtraIron
5 chancePredictionFindShinies
6 chancePredictionFindShinies(重复)
7 chancePredictionSpawnBuriedTreasure
8 chancePredictionVillagerLove
9 chancePredictionSpawnBabaYagaBad
10 chancePredictionSpawnBabaYagaGood
11 chancePredictionSpawnFriendlyWolf
12 chancePredictionSpawnProtectiveAnimal
13 chancePredictionSpawnProtectiveAnimal(重复)
14 chancePredictionGetWet
15 chancePredictionTeleportNether
16 chancePredictionExtraCoal

三重不一致:

问题 位置 说明
数组越界 PredictionHandler.java:89-92 循环 i = 0..17(18 次),但下标取 aNewValues[i - 1]。i = 0 时取 aNewValues[-1] → ArrayIndexOutOfBoundsException
下标错位 PredictionHandler.java:92、94 作者显然想让「预言 ID i」对应「数组第 i-1 项」,但循环却从 i = 0 起。正确写法应为 for (int i = 1; i < 18; i++) 或改用 aNewValues[i]
末尾漏配 同上 实际只有 17 个权重,没有任何配置项能影响下标 17。而循环覆盖预言 ID 0..17 共 18 个 ID

触发条件:越界只在 getPrediction(0) != null 时发生——i = 0 分支被 if (aOld != null) 包裹(PredictionHandler.java:91)。 即取决于 Witchery 是否注册了 ID 为 0 的预言; Witchery 不在本 wiki 范围,无法从本仓库确认该 ID 是否存在。 若 ID 0 不存在,则退化为「预言 ID 17 的权重永远无法配置」。

另注:chancePredictionFindShinies 与 chancePredictionSpawnProtectiveAnimal 各占 2 个数组槽(15 个配置项 → 17 个槽位)。这可能是有意的 (两个不同预言共用一个权重开关),源码无注释说明,不要当作笔误断言。

重建逻辑:按类型反射复制字段

generateNewPrediction()(PredictionHandler.java:100-193)在 static 块 (PredictionHandler.java:49-77)预先反射了 12 个私有字段:

字段 所属类
selfFulfillmentProbabilityPerSec Prediction
regularFulfillmentDurationInTicks PredictionAlwaysForced
regularFulfillmentProbability PredictionAlwaysForced
entityClass PredictionRescue
chestGenHook PredictionBuriedTreasure
block / itemPrototype / minExtra / maxExtra PredictionMultiMine
entityClass / bindTameable PredictionFight

按 instanceof 分支重建,共处理 9 种具体类型: PredictionMultiMine、PredictionBuriedTreasure、PredictionFallInLove、 PredictionRescue(以上 4 种嵌在 PredictionAlwaysForced 分支内)、 PredictionFight、PredictionArrow、PredictionFall、PredictionWet、 PredictionNetherTrip。

⚠️ 未覆盖的类型:PredictionMultiMine 之外的 PredictionAlwaysForced 子类若不是上面 4 种之一,会走到 PredictionHandler.java:107-109 读取字段后 aNewPrediction 始终保持 null(PredictionHandler.java:101 初始化), 而 remove(aOld) 已在 PredictionHandler.java:93 执行—— 该预言会被静默删除。新增 Witchery 版本若引入新的 AlwaysForced 子类即触发。 源码没有任何兜底分支处理 aNewPrediction == null。

关联

  • 15 个权重开关的完整默认值见 配置项