起因
今天在折腾特殊制作,发现亚丁精髓的材料老是打不出来。于是查了一下掉落,好家伙。官方设定中只有少数几个怪掉落,还只掉其中一两样。
这怎么能愉快地玩耍,于是我决定自己加。但是每个怪单独配置,上千个怪估计先死的是我。
于是。。。。。。。一个邪恶的念头悄悄泛起了涟漪。
一句话掉落判定不在一个 XML 里,所有怪、所有死法的掉落最后都要走同一个方法: Attackable.doItemDrop(NpcTemplate npcTemplate, Creature mainDamageDealer)
在它的开头插一段十几行的代码,就能让所有符合条件的怪(哪怕以后再新加自制怪也不用再重复操作了)多掉东西——不用动任何 npc XML,也不用一只只怪手工加。
掉落管线data/stats/npcs/<区间>.xml 原码掉落表写在这里:<dropLists><drop><group chance="…"><item id="…" …> │ 开机时由 NpcData 解析成内存里的掉落表 ▼NpcTemplate.calculateDrops(DropType.DROP, npc, killer) 源码掉落表的「判定」在这里(机率、数量、掉宝倍率都在这层) │ ▼Attackable.doItemDrop(NpcTemplate, Creature) ← 所有怪、所有死法的唯一总出口 │ ├─ player.doAutoLoot(this, drop) 自动拾取 → 直接进背包 └─ dropItem(player, drop) 掉在地上,等人捡
你要找的就是最后那个「总出口」。它同时是每一只怪的出口(正常打死、宝箱怪、离线挂机刷的怪……最后都汇到这里),所以挂在这里 = 一处改、全局生效。
好!开始动手! 第 1 步:确认你版本里的方法签名与插入点grep -n "doItemDrop" "<你的源码>/java/org/l2jmobius/gameserver/entity/actor/Attackable.java"
10.1 上游 master 的方法长这样(// ★ 是注释): public void doItemDrop(NpcTemplate npcTemplate, Creature mainDamageDealer){ if (mainDamageDealer == null) { return; // ★ 额外掉落必须插在【这个 return 之后】 } final Player player = mainDamageDealer.asPlayer(); // 最后一刀不是玩家就不掉(除非是假人) if (player == null) { if (mainDamageDealer.isFakePlayer() && FakePlayersConfig.FAKE_PLAYER_CAN_DROP_ITEMS) { final Collection<ItemHolder> deathItems = npcTemplate.calculateDrops(DropType.DROP, this, mainDamageDealer); if (deathItems != null) { for (ItemHolder drop : deathItems) { // ……自动拾取判断…… dropItem(mainDamageDealer, drop); // 假人/离线挂机:掉在地上 } deathItems.clear(); } } return; // ★ …插在这行【之前】,才能同时覆盖真人 + 假人 } CursedWeaponsManager.getInstance().checkDrop(this, player); // ……以下原厂逻辑(含 isSpoiled() 的 SPOIL 掉落、真人路径的 doAutoLoot / dropItem)……}
插入位置口诀:mainDamageDealer == null 的检查之后、asPlayer() 之前。 插在 null 检查之前 ⇒ killer 为 null 时会 NPE,连原厂掉落一起整批吞掉(而且日志可能什么都不报)。插在方法最后 ⇒ 假人/离线挂机走的是上面那个 return,永远拿不到额外掉落。
第 2 步:贴上这段代码需求示例:所有 Lv.80 以上的普通怪,额外 5% 掉 1~2 个「强化结晶」。9552 只是个占位,换成你自己要掉的道具 id。 // ===== 全局额外掉落(示例:Lv.80+ 普通怪 5% 掉 1~2 个)===== final int bonusItemId = 9552; // ← 换成你要掉的道具 id final boolean bonusTarget = npcTemplate.isType("Monster") // 只对普通怪 && !isRaid() // 排除首领/狩猎首領 && (npcTemplate.getId() < 900000) // 排除高 id 段的自定义 NPC(如果有做自定义) && (getLevel() >= 80); // 等级门槛 if (bonusTarget && (Rnd.get(10000) < 500)) // 500/10000 = 5.00%(0.01% 粒度) { final int bonusCount = 1 + Rnd.get(2); // 1~2 个 final ItemHolder bonus = new ItemHolder(bonusItemId, bonusCount); final Player bonusOwner = mainDamageDealer.asPlayer(); if (bonusOwner != null) { bonusOwner.doAutoLoot(this, bonus); // 真人:走自动拾取流程,直接进背包 } else { dropItem(mainDamageDealer, bonus); // 假人/离线挂机:掉在地上 } } // ===== 示例结束 =====
用的全是这个类里现成的东西,不用加 import: 名称出处作用
npcTemplate方法参数这只怪的模板,类型/id/等级都从它拿
npcTemplate.isType("Monster")NpcTemplate判断是不是普通怪(部分版本写成 "Monster".equals(npcTemplate.getType()))
isRaid()Attackable是不是首领;本版还有 _isRaid 字段
npcTemplate.getId() / getLevel()NpcTemplate / Npc排除 id 段、做等级阶梯
Rnd.get(n)org.l2jmobius.commons.util.Rnd0~n-1 随机数(本文件已 import)
new ItemHolder(id, count)entity/item/holders/ItemHolder掉落物的「id + 数量」载体(本文件已 import)
mainDamageDealer.asPlayer()Creature不是玩家(假人/召唤物)会返回 null
doAutoLoot(this, holder)Player按自动拾取规则发道具
dropItem(who, holder)Npc把道具掉在地上
编译报「找不到符号」时:Rnd / ItemHolder 补 import;Player 与 Attackable 同包,本不需要 import。
第 3 步:编译javac -g -encoding UTF-8 \ -cp "<你的服务端>/libs/GameServer.jar:<你的服务端>/libs/*" \ -d out "<你的源码>/java/org/l2jmobius/gameserver/entity/actor/Attackable.java"
- Windows 的 classpath 分隔符是 ;,Linux/macOS 是 :(写错了就是一堆「找不到符号」)。
- libs/* 要把整个 jar 目录带上,GameServer.jar 只是其中一个。
- -g 不能省:原厂 class 带 LocalVariableTable,你重编时不带,字节码的局部变量信息就对不上(做热替换时尤其容易出问题)。
- 产物:out/org/l2jmobius/gameserver/entity/actor/Attackable.class
让它生效
1) 停服2) 备份3) 不要直接对运行中的 jar 打包(会被锁)——先复制副本改,再整档盖回cp libs/GameServer.jar libs/GameServer.jar.tmpcd outjar uf ../libs/GameServer.jar.tmp org/l2jmobius/gameserver/entity/actor/Attackable.classcd ..cp libs/GameServer.jar.tmp libs/GameServer.jar4) 三条校验jar tf libs/GameServer.jar | wc -l # 条目数应与备份一致cp libs/GameServer.jar /tmp/x.jar && unzip -p /tmp/x.jar org/l2jmobius/gameserver/entity/actor/Attackable.class | wc -cjavap -c -p -cp out org.l2jmobius.gameserver.entity.actor.Attackable | grep -n "9552\|10000"5) 启服,看日志:Server loaded in … seconds,没有 exception
|