加载中...

「RO 笔记」添加自定义宠物


0x00 前言

继上篇《添加自定义魔物》之后,现在把当时添加的「皮卡丘」定制为新的宠物。

添加宠物涉及到宠物本身、宠物蛋、捕捉道具、宠物食物、宠物装备、宠物图档等配置,因此步骤还是相对繁琐的,需要仔细看下文的一些要点,不然查错就很麻烦了。

过程中建议使用 SDE 进行辅助配置,会更高效。

0x10 服务端设定

0x11 添加宠物蛋

对于口袋妖怪而言,宠物蛋其实就是捕捉了精灵的精灵球。

我们在道具库 item_db.yml 中添加「皮卡丘精灵球」:

  - Id: 9172
    AegisName: PIKACHU_BALL
    Name: "[皮卡丘]精灵球"
    Type: Petegg
    Buy: 20

注意要点:

  • 宠物蛋的道具类型固定为 Petegg
  • 记住 AegisNamePIKACHU_BALL,后面要用
  • 宠物蛋 Id 需要注意要在 (9000, 9500) 范围内(范围是开区间,官方的 ID 是从 9001 开始的),否则孵化时会出现问题。

正常情况下,若宠物蛋 Id 官方设定的范围内,孵化后宠物蛋会从背包中消失。

但若宠物蛋 Id 不在官方设定的范围内:

  • 孵蛋后,宠物蛋依然存在背包,但是名字变成 “红字损坏状态”,且不可丢弃交易(相当于被强制锁定了)
  • 把宠物恢复蛋状态后,宠物蛋恢复正常,此时可丢弃或交易

导致这个异常情况是因为客户端硬编码了宠物蛋 Id 范围,且不可更改(目前 Nemo 并没有相关选项)。

当然如果你不在意宠物蛋在孵化后保留在背包,你完全可以使用范围外的 Id。

在本文的例子中,我设置皮卡丘的宠物蛋 Id 为 9172,其实也没什么特别含义:

  • 9172 在 (9000, 9500) 范围中、且是闲置的 Id
  • 172 是皮卡丘的全国图鉴编号

0x12 添加捕捉道具

对于口袋妖怪而言,捕获道具就是精灵球了。

我们在道具库 item_db.yml 中添加普通的「精灵球」:

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    Type: Usable
    Buy: 1000
    Weight: 50
    Flags:
      BuyingStore: true
    NoUse:
      Sitting: true
    Script: |
      pet 20172;

注意要点:

  • 捕捉宠物的道具类型固定为 Usable
  • 记住 AegisNamePOKEMON_BALL,后面要用
  • Script: 为抓宠脚本,查 doc/script_commands.txt 可以找到抓宠命令 pet <pet id>;。 这里的 20172 就是之前在魔物库中定义的「皮卡丘」ID。

RO 从来都是一个捕捉道具只能抓一种宠物的,但是口袋妖怪的精灵球是可以捕捉多种宠物的。如果希望支持一个道具可以捕获多种宠物,文末会有如何设置的讲解,这里先不展开。

0x13 添加宠物食物

如果想偷懒的话,可以直接用官方的「宠物饲料」即可。

但是这里在道具库 item_db.yml 中添加了树果作为口袋妖怪的专用食物,也算是致敬原著吧:

  - Id: 1540000
    AegisName: POKEMON_FOOD
    Name: 橙橙果
    Type: Healing
    Buy: 100
    Weight: 10
    Flags:
      BuyingStore: true
    Script: |
      itemheal rand(10,20),0;

这里记住 AegisNamePOKEMON_FOOD 即可,后面要用。

0x14 添加宠物装备(可选)

在口袋妖怪中,精灵也是可以携带一些特殊装备的,例如皮卡丘就可以装备小智的帽子转换为「搭档皮卡丘」形态。

这里在道具库 item_db.yml 中添加「小智帽子」:

  - Id: 1520172
    AegisName: PIKACHU_EQUIP
    Name: 小智帽子
    Type: Petarmor
    Buy: 20

注意要点:

  • 宠物装备的类型固定为 Petarmor
  • 记住 AegisNamePIKACHU_EQUIP,后面要用

如果希望偷懒的话,这步不是必须的。

但是在默认情况下,宠物若要使用技能进行攻击、需要穿戴装备。假如跳过了这步,就需要在 conf/battle/pet.conf 修改 pet_equip_required

// 宠物是否需要装备饰品才能使用技能?
pet_equip_required: no

但是经测试,新增的宠物装备无法穿戴

0x15 添加宠物信息

现在可以在宠物库 pet_db.yml 添加「皮卡丘」信息了:

  - Mob: PIKACHU
    TameItem: POKEMON_BALL
    EggItem: PIKACHU_BALL
    EquipItem: PIKACHU_EQUIP
    FoodItem: POKEMON_FOOD
    Fullness: 3
    IntimacyFed: 50
    CaptureRate: 3000
    AttackRate: 8000
    RetaliateRate: 8000
    ChangeTargetRate: 800
    # 10% 概率使用【雷鸣术(电球)】Lv10,当亲密度最大时追加 40% 使用概率
    # 5% 概率使用【怒雷强击(十万伏特)】Lv10,当亲密度最大时追加 30% 使用概率
    SupportScript: >
      petskillattack "WZ_JUPITEL", 10, 10, 40;
      petskillattack "WZ_VERMILION", 10, 5, 30;
    Script: >
      .@i = getpetinfo(PETINFO_INTIMATE);
      if (.@i >= PET_INTIMATE_LOYAL) {
        bonus bAgi,3;
        bonus2 bMagicAtkEle,Ele_Wind,10;
      } else if (.@i >= PET_INTIMATE_CORDIAL) {
        bonus bAgi,2;
        bonus2 bMagicAtkEle,Ele_Wind,5;
      }

关键字段说明如下:

  • Mob: PIKACHU: 魔物库「皮卡丘」的 AegisName
  • TameItem: POKEMON_BALL: 道具库捕捉道具「精灵球」的 AegisName
  • EggItem: PIKACHU_BALL: 道具库宠物蛋「皮卡丘精灵球」的 AegisName
  • EquipItem: PIKACHU_EQUIP: 道具库宠物装备「小智帽子」的 AegisName
  • FoodItem: POKEMON_FOOD: 道具库宠物食物「橙橙果」的 AegisName
  • CaptureRate: 3000: 捕捉成功率,这里设置为 30%

其他字段的含义看 pet_db.yml 中的注释即可。

至此服务端部分就配置完成了。

0x20 客户端设定

0x21 添加宠物蛋

对应服务端,在客户端道具池 itemInfo.lub 添加「皮卡丘精灵球」:

[9172] = {
  unidentifiedDisplayName = "[皮卡丘]精灵球",
  unidentifiedResourceName = "PIKACHU_BALL",
  unidentifiedDescriptionName = { 
    "[皮卡丘]栖息的精灵球, 可使用 [洛托姆图鉴] 将其召唤出来。",
    "种类: ^777777精灵球^000000"
  },
  identifiedDisplayName = "[皮卡丘]精灵球",
  identifiedResourceName = "PIKACHU_BALL",
  identifiedDescriptionName = { 
    "[皮卡丘]栖息的精灵球, 可使用 [洛托姆图鉴] 将其召唤出来。",
    "种类: ^777777精灵球^000000"
  },
  slotCount = 0,
  ClassNum = 0
}

需要对应自制这些名为 PIKACHU_BALL 的道具图档:

  • 在地上/拖拽时的图档: data/sprite/酒捞袍/PIKACHU_BALL.act|apr
  • 道具栏的小图图档: data/texture/蜡历牢磐其捞胶/item/PIKACHU_BALL.bmp
  • 详情的大图图档: data/texture/蜡历牢磐其捞胶/collection/PIKACHU_BALL.bmp

0x22 添加捕捉道具

对应服务端,在客户端道具池 itemInfo.lub 添加「精灵球」:

[1500001] = {
  unidentifiedDisplayName = "精灵球",
  unidentifiedResourceName = "POKEMON_BALL",
  unidentifiedDescriptionName = { 
    "用于投向野生口袋妖怪并将其捕捉的球,它是胶囊样式的。",
    "种类: ^777777捕捉口袋妖怪道具^000000",
    "重量: ^7777775^000000"
  },
  identifiedDisplayName = "精灵球",
  identifiedResourceName = "POKEMON_BALL",
  identifiedDescriptionName = { 
    "用于投向野生口袋妖怪并将其捕捉的球,它是胶囊样式的。",
    "种类: ^777777捕捉口袋妖怪道具^000000",
    "重量: ^7777775^000000"
  },
  slotCount = 0,
  ClassNum = 0
}

需要对应自制这些名为 POKEMON_BALL 的道具图档:

  • 在地上/拖拽时的图档: data/sprite/酒捞袍/POKEMON_BALL.act|apr
  • 道具栏的小图图档: data/texture/蜡历牢磐其捞胶/item/POKEMON_BALL.bmp
  • 详情的大图图档: data/texture/蜡历牢磐其捞胶/collection/POKEMON_BALL.bmp

0x23 添加宠物食物

对应服务端,在客户端道具池 itemInfo.lub 添加「橙橙果」:

[1540000] = {
  unidentifiedDisplayName = "橙橙果",
  unidentifiedResourceName = "坷坊瘤",
  unidentifiedDescriptionName = { 
    "口袋妖怪都喜欢吃的树果,吃下后^000088可以恢复少量的 HP^000000。",
    "重量: ^7777771^000000"
  },
  identifiedDisplayName = "橙橙果",
  identifiedResourceName = "坷坊瘤",
  identifiedDescriptionName = { 
    "口袋妖怪都喜欢吃的树果,吃下后^000088可以恢复少量的 HP^000000。",
    "重量: ^7777771^000000"
  },
  slotCount = 0,
  ClassNum = 0
}

需要对应自制这些名为 坷坊瘤 的道具图档:

  • 在地上/拖拽时的图档: data/sprite/酒捞袍/坷坊瘤.act|apr
  • 道具栏的小图图档: data/texture/蜡历牢磐其捞胶/item/坷坊瘤.bmp
  • 详情的大图图档: data/texture/蜡历牢磐其捞胶/collection/坷坊瘤.bmp

0x24 添加宠物装备(可选)

对应服务端,在客户端道具池 itemInfo.lub 添加「小智帽子」:

[1520172] = {
  unidentifiedDisplayName = "头具",
  unidentifiedResourceName = "PIKACHU_EQUIP",
  unidentifiedDescriptionName = { 
    "尚未鉴定, 可使用^990099[放大镜]^000000进行鉴定"
  },
  identifiedDisplayName = "小智帽子",
  identifiedResourceName = "PIKACHU_EQUIP",
  identifiedDescriptionName = { 
    "小智的帽子,含有羁绊之力,由皮卡丘穿戴可转变为 [拍档皮卡丘] 形态。",
    "种类: ^777777口袋妖怪装备^000000",
    "装载: ^777777皮卡丘^000000"
  },
  slotCount = 0,
  ClassNum = 0
}

需要对应自制这些名为 PIKACHU_EQUIP 的道具图档:

  • 在地上/拖拽时的图档: data/sprite/酒捞袍/PIKACHU_EQUIP.act|apr
  • 道具栏的小图图档: data/texture/蜡历牢磐其捞胶/item/PIKACHU_EQUIP.bmp
  • 详情的大图图档: data/texture/蜡历牢磐其捞胶/collection/PIKACHU_EQUIP.bmp

0x25 添加宠物到宠物池

类似《添加自定义魔物》中的魔物池,宠物也存在宠物池,不过更复杂:

定义「宠物唯一代码」和「魔物 ID」的关系: data/luafiles514/lua files/datainfo/jobidentity.lub

在最后末尾新增一行即可(建议和此前在魔物池设置 npcidentity.lub 的代码和 ID 保持一致):

JTtbl = {
  ... ...,
  JT_POKEMON_PIKACHU = 20172
}

定义「宠物唯一代码」和「宠物名称」「宠物图档名称」「宠物装备」「宠物蛋」「宠物食物」的关系: data/luafiles514/lua files/datainfo/petinfo.lub

-- 宠物唯一代码 -> 魔物库的 AegisName
PetNameTable = {
  ... ...,
    [jobtbl.JT_POKEMON_PIKACHU] = "PIKACHU",
}

-- 宠物唯一代码 -> 宠物图档名称
PetIllustNameTable = {
  ... ...,
    [jobtbl.JT_POKEMON_PIKACHU] = "PIKACHU.bmp",
}
PetIllustNameTable_Eng = {
  ... ...,
    [jobtbl.JT_POKEMON_PIKACHU] = "PIKACHU.bmp",
}

-- 定义宠物装备唯一代码 -> 宠物装备的道具 ID
PetAccIDs = {
  ... ...,
    ACC_PIKACHU_EQUIP = 1520172
}

-- 宠物装备唯一代码 -> 宠物穿着装备后的动作图档名称
PetAccActNameTable = {
  ... ...,
    [PetAccIDs.ACC_PIKACHU_EQUIP] = "PIKACHU_EQUIP.act"
}
PetAccActNameTable_Eng = {
  ... ...,
    [PetAccIDs.ACC_PIKACHU_EQUIP] = "PIKACHU_EQUIP.act"
}

-- 宠物唯一代码 -> 在游戏的宠物界面中显示的名字
PetStringTable = {
  ... ...,
    [jobtbl.JT_POKEMON_PIKACHU] = "皮卡丘"
}

-- 宠物蛋 ID -> 宠物唯一代码
PetEggItemID_PetJobID = {
  ... ...,
    [9172] = jobtbl.JT_POKEMON_PIKACHU
}

-- 宠物唯一代码 -> 宠物食物的道具 ID
PetFoodTable = {
  ... ...,
    [jobtbl.JT_POKEMON_PIKACHU] = 1540000
}

上述配置涉及两个图档:

  • 宠物图档名称: PIKACHU.bmp
  • 宠物穿着装备后的动作图档名称: PIKACHU_EQUIP.act

它们所在的目录分别为:

  • 宠物图档目录: data/texture/userinterface/illust
  • 宠物穿着装备后的动作图档目录: data/sprite/阁胶磐

其中自定义的宠物装备无论如何也无法穿着,不加也罢。

而宠物图档尺寸要求为 90x134,把 bmp 放置在对应位置即可生效:

0x26 添加宠物对话

宠物对话的配置文件在 data/pettalktable.xml 中。

这个文件结构很简单,每种宠物一个节点,节点名称就是宠物在魔物库 mob_db.yml 中对应的 AegisName

每个宠物下面有 5 种状态的预设对话:

  • hungry: 饥饿
  • bit_hungry: 稍微饥饿
  • noting: 普通
  • full: 吃饱
  • so_full: 非常饱

0x30 测试

此时进入游戏即可捕捉一只皮卡丘进行测试:

只有在亲密度在 有点亲密 以上时才会有几率触发对话和技能,可以使用 GM 命令 @petfriendly 1000 直接满亲密。

需要注意的是,有些同学测试时使用 @itemgetitem 直接生成一个宠物蛋,但是用孵蛋器却无论如何都孵化不了。

其实 rAthena 在 doc/script_commands.txt 中已经说明过这问题:

If you try to give a pet egg with 'getitem', pet data will not be created by the char
server and the egg will disappear when anyone tries to hatch it.

大意就是说 getitem 不会在数据库创建对应的宠物数据,得到的只是一只空心蛋,所以无法孵化出宠物。

正确的做法是使用 makepet,或使用捕捉道具生成宠物蛋。

0x40 如何使用同一个道具捕捉多种宠物?

上文留了一个尾巴: RO 从来都是一个捕捉道具只能抓一种宠物的,但是口袋妖怪的精灵球是可以捕捉多种精灵的。是否可能在 RO 设置一个道具可以捕获多种宠物

首先给一个结论: 这是可行的

关键其实在捕捉道具的 Script 代码:

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    Type: Usable
    Buy: 1000
    Weight: 50
    Flags:
      BuyingStore: true
    NoUse:
      Sitting: true
    Script: |
      pet 20172;

查阅 doc/script_commands.txt 文档,Script 默认都是 pet <pet id>catchpet <pet id> 格式,即此道具只允许捕捉指定 ID 的一种魔物作宠物。

如果希望该道具可以捕捉多种宠物,首先需要在宠物库 pet_db.yml 的每种宠物的 TameItem 修改为此捕获道具,例如 TameItem: POKEMON_BALL

然后需要修改捕获道具的 Script 代码:

0x41 方法一:重复命令(不可行)

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    ... ...
    Script: |
      pet 20172;
      pet 20173;
      pet 20174;

有同学尝试过使用重复的 pet <pet id> 命令使其捕获多种宠物,事实上只有最后一条 pet 20174; 会生效,因此不可行。

0x42 方法二:条件判断(不可行)

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    ... ...
    Script: |
      if (mobid == 20172) {
        pet 20172;
      } else if (mobid == 20172) {
        pet 20173;
      } else if (mobid == 20174) {
        pet 20174;
      }

于是又有同学在前面基础上增加 modid 的条件判断,试图遇到哪只捉哪只。

事实上也是不可行的,因为在使用道具的时候,魔物 ID 没有绑定到道具脚本中,因此 mobid 永远为空。

0x43 方法三:贤者技能(已失效)

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    ... ...
    Script: |
      itemskill "SA_TAMINGMONSTER", 10;

后来又有同学想到利用贤者的随机技能中,偶尔会出现的「认养宠物」技能 SA_TAMINGMONSTER

此方法在早期版本是可行的,但是现在已经用不了了。

0x44 方法四:源码修改(rAthena,可行)

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    ... ...
    Script: |
      pet 0;

因为 pet 函数本身的限制,实际上在 NPC 侧的脚本已然无解。

有同学就想出当 <pet id> 为 0 时,可以捕捉任意宠物,但是需要修改源码的判断逻辑。

src/map/pet.cpp 中找到捕捉的实现方法 pet_catch_process2,定位到其中的:

//catch_target_class == PET_CATCH_UNIVERSAL is used for universal lures (except bosses for now). [Skotlex]
if (sd->catch_target_class == PET_CATCH_UNIVERSAL && !status_has_mode(&md->status,MD_STATUSIMMUNE)){
  sd->catch_target_class = md->mob_id;

把判断条件改为:

//catch_target_class == 0 is used for universal lures (except bosses for now). [Skotlex]
if (sd->catch_target_class == 0 && !status_has_mode(&md->status,MD_STATUSIMMUNE))
  sd->catch_target_class = md->mob_id;

0x45 方法五:源码修改(Pandas,可行)

  - Id: 1500001
    AegisName: POKEMON_BALL
    Name: 精灵球
    ... ...
    Script: |
      mpet 20172, 20173, 20174;

而在 Pandas 中,是通过添加 mpet <pet id>multicatchpet <pet id> 命令实现多种宠物捕捉的。

它把入参改为了宠物 ID 的列表,比 pet 0 控制更精准。

当然这也涉及到了源码的修改:

#ifdef Pandas_Struct_Map_Session_Data_MultiCatchTargetClass
  else if (sd->catch_target_class == PET_CATCH_MULTI_TARGET) {
    for (auto it : sd->pandas.multi_catch_target_class) {
      if (it != md->mob_id) continue;
      sd->catch_target_class = md->mob_id;
    }
  }
#endif // Pandas_Struct_Map_Session_Data_MultiCatchTargetClass

0xF0 参考文档


文章作者: EXP
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 EXP !
  目录