DNF 任务表,以及所有类型任务的一键完成方案。
发现了这么一个GM工具:https://github.com/crazytuzi/DNF__GM_Tools
可以一件完成任务,目前主流的方案都仅能完成简单对话或副本任务,而这个工具可以完成所有类型的任务,包括收集材料或者击杀多个怪物等等。
解析源码后发现,它是先查询任务表:new_charac_quest
, 获得角色已接受且未完成的任务, 然后再去PVF文件中读取任务的完成条件。
查询:
select * from new_charac_quest where charac_no={characNo}
事实上不用select *
可以值查询play_{i}
和play_{i}_trigger
, 其中{i}
是1~20(一个角色最多同时接受20个任务)
play_{i}
: 任务IDplay_{i}_trigger
:完成状态(0 = 已完成)。
那么,最终的SQL可以是这样的:
SELECT task_no, task_id
FROM (
SELECT 1 AS task_no, play_1 AS task_id, play_1_trigger AS trigger_status FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 2, play_2, play_2_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 3, play_3, play_3_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 4, play_4, play_4_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 5, play_5, play_5_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 6, play_6, play_6_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 7, play_7, play_7_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 8, play_8, play_8_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 9, play_9, play_9_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 10, play_10, play_10_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 11, play_11, play_11_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 12, play_12, play_12_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 13, play_13, play_13_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 14, play_14, play_14_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 15, play_15, play_15_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 16, play_16, play_16_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 17, play_17, play_17_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 18, play_18, play_18_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 19, play_19, play_19_trigger FROM new_charac_quest WHERE charac_no = 18
UNION ALL
SELECT 20, play_20, play_20_trigger FROM new_charac_quest WHERE charac_no = 18
) t
WHERE t.task_id > 0 AND t.trigger_status != 0;
查询结果:
task_no | task_id |
---|---|
2 | 97 |
4 | 7 |
5 | 1062 |
接下来去PVF中根据taskId
查找指定任务的完成条件。
任务的PVF中,找这个文件: n_quest/quest.lst
, 这是全局的任务导航列表。进去找刚才的taskId
,比如97
,可以导航到对应的任务描述文件中, 这是对应的描述文件内容:
#PVF_File
[grade]
`[epic]`
[difficulty]
`W`
[gold multiple]
150
[npc index]
13
[complete npc index]
-1
[job]
`[all]`
[grow type]
-1
[level]
15 99
[pre required quest]
96
[/pre required quest]
[quest point]
4
[type]
`[hunt monster]`
[sub type]
-1
[int data]
1000 1 212 1
[/int data]
[reward type]
`[item]`
[reward int data]
0 10000 1033 5
[/reward int data]
[reward selection int data]
[/reward selection int data]
[name]
`毀滅的痕跡`
[depend message]
`…
根據你收集的材料和我的研究結果,我終於確立了一個結論。 到現在為止,這個結論還沒有第2個人知道,
記得我之前跟你說過那些僵屍很可能是人類或者類似於人類的物種吧? 我現在可以肯定那個推測是正確的。
因為根據我的研究,暗黑雷鳴廢墟的那些僵屍其實就是生活在格蘭之森並在大火災後莫名失蹤的精靈!
更令人擔憂的是,研究結果表明,暗黑雷鳴廢墟的範圍正在逐日增加。 照這樣下去,整個格蘭之森遲早會被它吞噬。
雖然現在只是推測,但是我相信只有除掉那些僵屍的頭領,才能阻止暗黑雷鳴廢墟擴張。
冒險家,時間緊迫,你快去暗黑雷鳴廢墟除掉那群僵屍的頭領吧, 我要立刻把這些研究結果和推論向貝爾瑪爾公國彙報! `
[condition message]
`前往格蘭之森的暗黑雷鳴廢墟,除掉領主盜屍者骨獄息,要求地下城難度為中等難度(Medium)以上。`
[solve message]
`真不敢相信, 難道公國……不, 帝國早就知道這事了嗎? 我向公國彙報研究結果後, 就有帝國的人突然闖進我的鍊金術師協會, 他們拿走了所有的研究材料, 他們真是一群混蛋。
雖然已經沒有證據能證明我的推論是正確的, 但是我更能確定暗黑雷鳴廢墟裡徘徊的那些僵屍就是阿拉德大陸曾經最優秀的種族--精靈, 而帝國肯定和這些事有牽連!
呼……這事就作為我們兩個人的秘密吧, 畢竟是無法證明的事, 多說無益。 而且帝國方面的強硬態度也很讓人擔心……
不過我還是很高興, 雖然真相無法公佈於眾, 但是我曾站在離真相最近的地方……`
解释:
grade
: 任务类型[epic]
: 主线任务[normaly repeat]
: 重复任务[common unique]
: 普通任务?不确定,没去验证,这不重要[daily]
: 每日任务[achievement]
: 成就任务?
difficulty
: 难度(好像没什么用)gold multiple
: 奖励金币倍数?不确定是什么算法,但会影响到获得的金币。npc index
: 任务NPC的IDcomplete npc index
: 完成任务所需的NPC的ID。job
: 哪个职业可以接受,比如[all]
grow type
: 不知道干嘛的level
: 可接受任务等级区间15 99
: 两个值,表示从15级开始,到99级
pre required quest
: 前置任务IDtype
: 任务类别(到重点了, 主要是读这里,来完成任务)[custom quest]
: 操作类任务,比如解除魔法封印,使用分解机等。[hunt monster]
: 击杀类, 比如击败某某领主。[hunt enemy]
: 击杀类, 比如击败某某怪。[condition under clear]
: 过图,副本等。这类任务可以直接在任务表中更改状态来完成。[seeking]
:收集类任务,比如收集多少个章鱼腿儿。[meet npc]
: 找人类任务,比如与赛利亚交谈。这类人物也可以直接再任务表中更改状态来完成。
int data
: 任务需要的值(偶数位,两个值为一组),比如击杀类任务1000 1 212 1
就表示击杀ID为1000
的怪物1
次和击杀ID为212
的关务1
次, 同理,收集类任务也长这样。reward type
: 奖励类型[item]
: 物品奖励(应该还有其他的,不知道,目前用不到)
reward int data
: 必得奖励,也是两个一组,比如0 10000 1033 5
就表示金币1W和ID为1033
的物品5个reward selection int data
: 自选奖励,比如巨剑短剑光剑啥的,让你选一种。name
: 任务名depend message
: 背景消息,就是巴拉巴拉一大堆故事背景啥的,没用condition message
: 指引信息,就是一句话告诉你要干什么,比如前往某某区域的某某图,除掉某某怪,要求难度为某某级别以上。solve message
:完成的对话消息,巴拉巴拉一大堆没什么用。
现在试着写一个Java代码读取ID为97的任务描述:
public static void main(String[] args) {
PvfCoder.initialize("C:\\Users\\71934\\Desktop\\Script.pvf", Charset.defaultCharset());
Pvf pvf = PvfCoder.getPvf();
String taskId = "97";
JSONObject questList = pvf.getScript("n_quest/quest.lst");
if (!questList.containsKey(taskId)){
System.out.printf("没有找到ID为%s的任务描述文件%n", taskId);
}
System.out.printf("找到ID为%s的任务描述文件:%s%n", taskId, questList.get(taskId));
JSONObject script = pvf.getScript("n_quest/" + questList.getStr("97"));
System.out.println("任务描述信息如下:" + script);
}
输出:
找到ID为97的任务描述文件:handonmyre/sunderlanddark/epic_14_sunderlanddark_gul_6.qst
任务描述信息如下:{"[grade]":["[epic]"],"[difficulty]":["W"],"[gold multiple]":[150],"[npc index]":[13],"[complete npc index]":[-1],"[job]":["[all]"],"[grow type]":[-1],"[level]":[15,99],"[pre required quest]":[96],"[quest point]":[4],"[type]":["[hunt monster]"],"[sub type]":[-1],"[int data]":[1000,1,212,1],"[reward type]":["[item]"],"[reward int data]":[0,10000,1033,5],"[reward selection int data]":[],"[name]":["这里乱码了"],"[depend message]":["这里乱码了"],"[condition message]":["这里乱码了"]}
验证可行(关于任务的一些对话和描述的地方中文乱码,这是编码的问题,我还没研究,不过不影响,乱码的地方正好我用不到)
最终代码:
public class Main {
public static void main(String[] args) {
PvfCoder.initialize("C:\\Users\\71934\\Desktop\\Script.pvf", Charset.defaultCharset());
Pvf pvf = PvfCoder.getPvf();
String taskId = "97";
JSONObject questList = pvf.getScript("n_quest/quest.lst");
if (!questList.containsKey(taskId)){
System.out.printf("没有找到ID为%s的任务描述文件%n", taskId);
}
System.out.printf("找到ID为%s的任务描述文件:%s%n", taskId, questList.get(taskId));
JSONObject script = pvf.getScript("n_quest/" + questList.getStr("97"));
System.out.println("任务描述信息如下:" + script);
String typeStr = script.getJSONArray("[type]").getStr(0);
TypeTag type = TypeTag.forType(typeStr);
if (type.isCatFinish()){
System.out.println(type.getDesc() + ", 可直接完成");
}else {
System.out.println(type.getDesc() + ", 自动发送收集物品邮件给当前角色辅助完成");
List<ItemInfo> items = new LinkedList<>();
JSONArray jsonArray = script.getJSONArray("[int data]");
for (int i = 0; i < jsonArray.size(); i++) {
ItemInfo info = new ItemInfo();
info.setItemId(jsonArray.getInt(i));
info.setCount(jsonArray.getInt(++i));
items.add(info);
}
System.out.println("准备发送邮件:" + items);
}
}
public static enum TypeTag {
condition("[condition under clear]", "过图任务", true),
npc("[meet npc]", "寻人任务", true),
seeking("[seeking]", "收集任务", false),
hunt("[hunt monster]", "击杀任务", true);
/**
* 任务类型
*/
@Getter
private final String type;
/**
* 描述
*/
@Getter
private final String desc;
/**
* 是否可直接完成
*/
@Getter
private final boolean catFinish;
TypeTag(String type, String desc, boolean catFinish) {
this.type = type;
this.desc = desc;
this.catFinish = catFinish;
}
public static TypeTag forType(String type){
for (TypeTag t : TypeTag.values()){
if (t.type.equals(type)){
return t;
}
}
throw new RuntimeException("no t found TypeTag");
}
}
@Getter
@Setter
public static class ItemInfo{
private int itemId;
private int count;
@Override
public String toString() {
return "ItemInfo [itemId=" + itemId + ", count=" + count + "]";
}
}
}