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 独立一份)。 每一份子表又分成两段:

文件层面上,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 的格式解读:

举两个例子便于理解:

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

Section ID 是怎么进来的

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

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

怪物稀有 vs 箱子稀有

怪物稀有(Monster Rares)

箱子稀有(Box Rares)

稀有掉率修正

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

数据结构

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 解读:

举例:

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_specs map⟨Enemy, ExpandedDrop[]⟩ 每种怪物对应的稀有池。
box_specs ExpandedDrop[][] 按 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 即 武器/铠甲/盾/插件

// ④ 按大类生成具体物品(权重表、磨石、bonus、special…)
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 作者在注释里说明了这点。

相关页面