ItemRT 稀有掉落表

← 返回首页

ItemRT(Item Rare Table)是 PSO 的「稀有掉落表」。它决定了每种怪物、每种箱子在掉出稀有物品时, 会掉哪一件、概率多少。本页掉落表里看到的「Dragon Slayer 1/456」「Tyrell's Parasol 1/3660」这些具体数字,就是来自 ItemRT。 配合 ItemPT(掉不掉 / 掉什么大类) 和 ItemPMT(物品本身的属性)三者一起,才构成 PSO 完整的掉落系统。

总览

在 PSO 的「掉落判定」流程里,稀有判定是独立于普通掉落的。一次怪物死亡或箱子破坏时,游戏会先做一次稀有判定:

  1. 查 ItemRT 里这只怪物(或这种箱子)对应的稀有池。
  2. 对池子里每一个候选稀有物品,单独做一次「概率掷骰」。
  3. 如果任何一项骰中了,这次掉落就固定为该稀有物品,绕过 ItemPT 的普通流程
  4. 如果全部没中,再回到 ItemPT 的流程走普通掉落。

这意味着同一只怪,「掉什么普通货」和「掉什么稀有货」是两条独立的路径。稀有掉率再低,也不会因为普通货多而降低。

文件结构

ItemRT 按「难度 × Section ID」为单位存放多份子表,一共 4 × 10 = 40 份(EP4 不参与,因为 EP4 的 Episode 独立一份)。 每一份子表又分成两段:

  • 怪物稀有池(Monster Rares):按怪物类型索引。每只怪最多对应 1 个稀有物品(加 1 个概率)。
  • 箱子稀有池(Box Rares):按区域索引。一个区域的箱子通常对应多个稀有候选(例如 Ruins 3 箱有近十种可能的稀有)。

文件层面上,PSOBB 用两个独立文件:ItemRT.gsl(Ep1 & Ep2)和 ItemRT_Ep4.gsl。 GSL 是 Sega 自己的小型归档格式(GStreamLib),里面每个 entry 对应一份子表。

一次稀有判定的生成流程

  1. 定位子表:按当前难度和本局游戏的 Section ID 选中对应的那份 ItemRT 子表。
  2. 查找候选
    • 怪物稀有:按怪物类型索引,拿到 (物品 ID, 概率值)
    • 箱子稀有:按区域索引,拿到一个候选列表。
  3. 掷骰:对每个候选做一次 32 位整数随机,和候选的概率值比较。命中则此次稀有判定成功。
  4. 命中后:该物品立刻被「构造」出来。如果是武器,还要根据当前区域走一遍 ItemPT 的 bonus/special 生成;如果是铠甲/盾,还要决定槽数;最后交给 ItemPMT 填充名称和模型。

概率的表示:为什么是「压缩格式」

PSOBB 的 ItemRT 文件里,每个稀有物品的概率不是直接存一个分数,而是用一个 uint8(1 字节)压缩存储。 原因是 Sega 想在一个很小的文件里塞下几百条稀有记录。这个 1 字节按 SSSSS VVV 的格式解读:

  • 高 5 位是位移(shift),低 3 位是基值(value)
  • 展开公式大致是 (2 << shift) × (value + 7),得到一个 32 位概率值
  • 最终掉率 = 这个 32 位值 / 0xFFFFFFFF(即 4,294,967,295)

举两个例子便于理解:

  • 0x40000000:最终掉率 = 25%(0x40000000 / 0xFFFFFFFF ≈ 1/4
  • 0x00FFFFFF:最终掉率 ≈ 1/256

因为压缩格式只有 256 个可选值,ItemRT 的概率不是任意精确的 — 所以社区常见的数值如 1/4561/36601/11702 等看起来「不太圆整」。 它们都是压缩格式能精确表示的「合法值」中最接近的那一个。

Section ID 是怎么进来的

每个 PSOBB 角色都有一个固定的 Section ID(10 种颜色 ID),这个 ID 在角色创建时由名字的 hash 决定,一旦确定不可更改。Section ID 的作用是:

  • 决定你这只角色在所有难度下访问的 ItemRT 子表。
  • 不同 ID 的稀有池完全不同 — 某些稀有只在特定 ID 下能掉。
  • 例如 Dark Flow 只在 Oran 玩家杀 Olga Flow 时有机会掉(U 难度)。

这就是为什么 PSO 有「刷 ID」的文化:特定稀有物品必须用特定 ID 刷。可以用 Section ID 计算器 反推能掉某件稀有的名字。

怪物稀有 vs 箱子稀有

怪物稀有(Monster Rares)

  • rt_index(怪物类型枚举)索引,每种怪一条记录
  • 每条记录是 (物品 ID, 压缩概率),固定长度
  • 所以「一只怪最多对应一个稀有」是天花板 — 如果同一怪物在不同 ID 下对应多个稀有(比如 Hildebear 能掉多种剑),是通过选 Section ID 来切换子表达成的,不是一只怪同时判定多个

箱子稀有(Box Rares)

  • 区域索引,每个区域可以有多个稀有候选
  • 通常一个区域有 1–30 个稀有候选,每个都有独立概率
  • 游戏对整个候选列表逐一判定,每个都有机会命中
  • 正因为这个机制,高区的红盒(Ruins 3 / 遗迹3 / CCA / Tower / 地下沙漠3)才这么「富」

稀有掉率修正

ItemRT 的原始概率是 Sega 设定的「基准值」。实际服务器通常会再乘一个全局掉率倍数

  • Ephinea 默认是 1x(基准)。周年活动里程碑会开更高的倍率。
  • 全局倍数作用于所有稀有物品的概率。

数据结构

ItemRT 的原始二进制文件使用固定长度的「压缩概率 + 物品代码」作为最小存储单元。一份子表(对应一个「难度 × ID」组合)的顶部是下面这张偏移头。

子表顶部偏移 Offsets(0x10 字节)

偏移字段类型说明
0x00monster_rares_offsetu32怪物稀有池指针 → PackedDrop[0x65](v1 只有 0x33 项)。按 rt_index 索引。
0x04box_countu32箱子稀有条目总数,通常固定为 30(0x1E)。
0x08box_areas_offsetu32箱子所属区域数组 → u8[box_count]。第 i 项告诉你第 i 个稀有条目位于哪个区域。
0x0Cbox_rares_offsetu32箱子稀有池指针 → PackedDrop[box_count],和 box_areas 一一对应。

压缩概率条目 PackedDrop(4 字节)

每一条稀有物品记录都是 4 字节,这是 ItemRT 的最小单元。怪物稀有池和箱子稀有池都用同一种结构。

偏移字段类型说明
0x00probabilityu8压缩概率值(SSSSS VVV 位格式)。
0x01item_codeu8×3物品代码 data1[0..2],例如 00 01 00 表示 Saber。

概率压缩与展开

1 字节的 probability 被当成 SSSSS VVV 解读:

  • 高 5 位 S 决定 shift:shift = S − 4,范围 [−4, 27],负值按 0 算
  • 低 3 位 V 决定 value:value = V + 7,范围 [7, 14]
  • 展开后的 32 位概率 = (2 << shift) × value,这个值作为「出该稀有物品」的分子,分母是 0xFFFFFFFF(全 32 位)
  • 最终掉率 = 展开值 / 0xFFFFFFFF

举例:

packed(16 进制)shiftvalueexpanded约等于
0xFF27140xE000000087.5%
0xC02070x01C00000约 1/152
0x801270x0001C000约 1/38836
0x00070x0000000E极小 (< 1/3 亿)

因为只有 256 个可选值,掉率不是任意精确的 —— 社区看到的 1/4561/36601/11702 这些「不圆整」的数字都是压缩表能精确表达的有效值中最接近的那一个。不可能写出例如恰好 1/500 这种精确掉率。

展开后结构 ExpandedDrop

newserv 在内存里把每条 PackedDrop 展开成一个便于计算的结构体,交互和序列化都用它:

字段类型说明
probabilityu3232 位展开概率(分子,分母固定 0xFFFFFFFF)
dataItemData完整的物品数据(含 data1/data2,不止 3 字节 ID)

箱子稀有 BoxRare

因为箱子稀有记录不按区域索引,而是把区域编号嵌在记录本身里:

字段类型说明
area_norm_plus_1u8区域编号 +1(0 保留做未使用标记)
dropExpandedDrop概率 + 物品数据

完整子表 SpecCollection

顶层的每一份子表对应一个「难度 × Section ID」键,展开后包含:

字段类型说明
enemy_specsmap⟨Enemy, ExpandedDrop[]⟩每种怪物对应的稀有池。
box_specsExpandedDrop[][]按 area_norm 索引的外层数组,每个区域内是一个稀有候选列表(内层数组里每一项都是独立判定的)。

完整掉落判定流程

一次掉落事件的入口是「怪物死亡」或「箱子破坏」,两条路径独立,都先做稀有判定、失败后再走 ItemPT 的普通流程。

怪物掉落

// ① 前置判定:这只怪到底会不会掉东西
type_drop_prob = pt.enemy_type_drop_probs[enemy_type]   // 0–100
if rand(0..99) >= type_drop_prob:
    return nothing                                       // 什么都不掉

// ② 稀有判定(Stacking 算法 — 一次随机判定所有 spec)
specs = rare_item_set.get_enemy_specs(mode, episode, difficulty, section_id, enemy_type)
det = rand(0, 0x100000000)                               // 32 位 + 1
for spec in specs:
    det -= spec.probability                              // 按顺序减
    if det < 0:
        return create_rare_item(spec.data, area)          // 命中稀有,流程结束

// ③ 走 ItemPT 的普通流程,先决定大类
determinant = allow_meseta ? rand(0..2) : rand(0..1) + 1
switch determinant:
    case 0: item_class = 5 (梅塞塔)
    case 1: item_class = 4 (消耗品)
    case 2: item_class = pt.enemy_type_item_classes[enemy_type]   // 0..3 即 武器/铠甲/盾/插件

// ④ 按大类生成具体物品(权重表、磨数、属性加成、特殊攻击…)
generate_common_item_variances(item, area)

箱子掉落

// ① 稀有判定(和怪物同一套 stacking 算法)
table_index = table_index_for_area(area)
specs = rare_item_set.get_box_specs(mode, episode, difficulty, section_id, table_index)
det = rand(0, 0x100000000)
for spec in specs:
    det -= spec.probability
    if det < 0:
        return create_rare_item(spec.data, area)

// ② 没中稀有 → 从 box_item_class_prob_table 按权重抽一个大类
item_class = weighted_pick(pt.box_item_class_prob_table, table_index)
    // 0=武器 1=铠甲 2=盾 3=插件 4=消耗品 5=梅塞塔 6=空

// ③ 若 item_class < 6 则生成具体物品
if item_class < 6:
    generate_common_item_variances(item, area)

关于 stacking 的一个重要提醒:
stacking 的写法是「初始 det,逐个 spec 从 det 里减去概率,减到负数时命中」,等价于一次随机决定所有 spec 的命中,spec 的顺序不影响最终分布。
而 Sega 原版客户端对每个 spec 独立掷一次 rand(0, 0xFFFFFFFF) 然后和 spec.probability 比较 —— 这会让「后面的 spec 实际稀有度更高」(因为前一个 spec 命中后流程就结束了)。对只有一个 spec 的怪物没影响,但对候选多达十几项的箱子列表,原版的计算结果和 stacking 不完全一致。newserv 作者在注释里说明了这点。

相关页面