工具类 Utilities
基本信息
| 属性 | 值 |
|---|---|
| 文件 | src/main/java/net/darkhax/wawla/util/Utilities.java(338 行,全 mod 最长的文件) |
| 静态方法数 | 15 |
| 实例成员 | 无(纯静态工具类,无构造器实例化) |
| 第三方依赖 | org.apache.commons.lang3.SystemUtils / WordUtils、net.minecraftforge.common.UsernameCache |
@SideOnly |
只有 getBlockDamage()(Utilities.java:318) |
15 个方法一览
| 方法 | 签名要点 | 用途 | 调用方 |
|---|---|---|---|
prepareStackCompound |
(ItemStack) → ItemStack |
没有 tag 就补一个,便于后续读写 NBT | 内部 getEnchantmentsFromStack |
wrapStringToList |
(String, int, boolean, List) → List |
按行宽折行后追加进 tooltip | 物品悬浮提示、未接线的 addon |
getEnchantmentsFromStack |
(ItemStack, boolean) → Enchantment[] |
stored=true 读 StoredEnchantments(附魔书),否则读 ench |
物品悬浮提示 |
round |
(double, int) → double |
BigDecimal + HALF_UP 四舍五入 |
7 处 |
generateElementArray |
(Class) → String[] |
把枚举常量转成名字数组 | Pixelmon |
getInventoryStacks |
(NBTTagCompound, int) → ItemStack[] |
从 "Items" 列表还原背包 |
4 个 addon |
compareByClass |
(Class, Class) → boolean |
忽略大小写比类名,两边都判 null | 5 个 addon |
compareTileEntityByClass |
(TileEntity, Class) → boolean |
compareByClass(entity.getClass(), teClass) |
3 个 addon |
getProgression |
(float, float) → float |
cur / max × 100 |
Tinkers、Pixelmon |
upperCase |
(String) → String |
首字母大写 | 神秘时代 |
getBlockLightLevel |
(World, int, int, int, boolean) → int |
读上方一格的方块光照;day=true 传遮蔽值 0,false 传 16(忽略日光) |
通用方块 |
getClass |
(String) → Class |
Class.forName 包装,失败打 WARN + 返回 null |
全 mod 无调用点 |
getUsernameByUUID |
(String) → String |
查 UsernameCache,查不到显示 tooltip.wawla.unknownplayer |
通用实体 |
canPlayerSleep |
(EntityPlayer) → boolean |
未在睡 + 存活 + 世界时间 ∈ (12541, 23458) | 通用方块 |
getDeepTagCompound |
(NBTTagCompound, String[]) → NBTTagCompound |
逐层下钻 NBT,遇不到就停在当前层 | 未接线的 addon |
getBlockDamage |
() → float(@SideOnly(CLIENT)) |
反射读 PlayerControllerMP 的挖掘进度 |
通用方块 |
另有 1 个静态字段 currentBlockDamage(Field,Utilities.java:308),在 ProxyClient.preInit 里用 ReflectionHelper.findField(PlayerControllerMP.class, "g", "field_78770_f", "curBlockDamageMP") 初始化。
⚠️ 源码缺陷
缺陷 1:round(value, 0) 是空操作
Utilities.java:105 的条件是 if (value >= 0 && places > 0),places == 0 时直接落到 return value 原样返回。Javadoc 却说"round to a certain amount of places"。
AddonPixelmonTiles.java:113 正是这样调的:
Utilities.round(Utilities.getProgression(meta, 5), 0) + "%"
即栗子树成长进度完全不取整,HUD 上会显示类似 83.333336%(float 精度误差)的浮点数。AddonGenericTiles.java:143 调的是 round(..., 2),正常。
⚠️ 附带问题:value >= 0 意味着负数永远不取整(round(-1.23456, 2) 返回 -1.23456)。当前 7 个调用点的值都是非负,暂未触发。
缺陷 2:getInventoryStacks 可能返回 null,但 5 个调用点全部直接下标访问
Utilities.java:145 声明 ItemStack[] inventory = null;,只有在 tag.hasKey("Items") 为真时才 new ItemStack[invSize](:150),否则保持 null 返回。
但所有调用点都假设它非 null:
| 位置 | 代码 |
|---|---|
AddonVanillaTiles.java:79 |
ItemStack[] furnaceStacks = Utilities.getInventoryStacks(data.getNBTData(), 3); 随后 furnaceStacks[0] |
AddonThaumcraftTiles.java:125 |
ItemStack pedestalStack = Utilities.getInventoryStacks(data.getNBTData(), 1)[0]; |
AddonThaumcraftTiles.java:138 |
ItemStack pedestalStack = Utilities.getInventoryStacks(data.getNBTData(), 1)[0]; |
AddonTinkersTiles.java:69 |
ItemStack cover = Utilities.getInventoryStacks(data.getNBTData(), 4)[3]; |
AddonTinkersTiles.java:96 |
ItemStack item = Utilities.getInventoryStacks(data.getNBTData(), 1)[0]; |
AddonTinkersTiles.java:133 |
ItemStack[] furnaceStacks = Utilities.getInventoryStacks(data.getNBTData(), 3); |
⚠️ 熔炉/基座/晾干架在刚放置、还没放入任何物品时 NBT 里可能没有 Items 键(TileEntityFurnace.writeToNBT 只在 isSpaceInInventory(inventory) 为假时才写),此时 getInventoryStacks 返回 null,[0] 立即 NPE。这是一个可复现的 HUD 崩溃点。
缺陷 3:getInventoryStacks 的越界保护写错了对象
Utilities.java:154:
if (!(i > list.tagCount())) {
- 意图显然是
i < inventory.length(防inventory[Slot]越界); - 写成
!(i > tagCount())即i <= tagCount(),而i的循环上界就是tagCount(),恒为真,是死代码; - 就算改成
i < inventory.length也不解决问题——真正越界的是currentTag.getByte("Slot")这个值(Utilities.java:157),不是循环下标i。当invSize小于 NBT 里实际的槽位数时(例如按 1 读取却读到Slot: 0..2的基座)就会 AIOOBE。
AddonThaumcraftTiles.java:125 / :138 用 getInventoryStacks(tag, 1) 读基座与法杖基座,恰好是这种"小 invSize 读大背包"的高危组合。
缺陷 4:getUsernameByUUID 对非法 UUID 字符串抛异常
Utilities.java:264 直接 UUID.fromString(uuid)。AddonGenericEntities.java:64-65 只校验了 hasKey("OwnerUUID") 与 length() > 0,不校验格式。被破坏的存档或被刻意写入的 NBT 会让 UUID.fromString 抛 IllegalArgumentException,而 getWailaBody 里没有任何 try/catch——一次刷怪笼、一次宠物 HUD 刷新就能炸掉 tooltip 渲染。
缺陷 5:upperCase("") 抛 StringIndexOutOfBoundsException
Utilities.java:209 的 string.charAt(0) 对空串直接越界。当前两处调用(AddonThaumcraftTiles.java:78 与 :117)恰好都有 !aspect.isEmpty() / hasKey("Aspect") 守卫,暂未触发,但该方法本身不安全。
缺陷 6:getBlockDamage 吞掉异常后返回 0
Utilities.java:328-334 两个空 catch 块吞掉 IllegalArgumentException 与 IllegalAccessException。结果是反射失效时挖掘进度静默变成 0%(AddonGenericTiles.java:145 的 progress > 0 判断会直接跳过该行),无任何日志。
无调用点的方法
Utilities.getClass(String)(Utilities.java:239)全 mod 零调用——13 个 addon 都直接用 Class.forName 加 try/catch,而不是走这个已经封装了 WARN 日志的包装。它失败时打的那句 "A class could not be found! This will cause issues, please report to Darkhax!" 因此永远不会出现。
相关条目
- 架构与 WAILA 接入方式 —
currentBlockDamage的反射初始化位置 - 通用方块 addon —
getBlockLightLevel/canPlayerSleep/getBlockDamage的唯一调用方