AzerothCore 任务系统开发指南:从 quest_template 到任务链的完整链路 原创
引言
任务(Quest)是 MMO 内容的骨架。AzerothCore 的任务系统横跨数据库表结构、客户端数据文件和服务端脚本三层,任何一层不匹配都会导致任务不显示、无法完成或奖励错误。本文拆解任务模板、目标条件、任务链、自定义脚本和奖励发放的完整开发链路。
一、任务数据的三层结构
一个任务需要三层数据协同:
- quest_template(world 库):任务元数据 — 标题、说明、目标、奖励、接取条件
- quest_template_addon(world 库):扩展属性 — 前置任务、下一个任务、特殊标志
- 客户端 questcache.wdb / questlist:客户端本地缓存,决定 NPC 头顶的感叹号/问号显示
关键认知:服务端决定任务逻辑,客户端决定任务显示。即使数据库配置完美,如果客户端缓存没更新,玩家仍看不到任务感叹号。
二、quest_template 核心字段
SELECT ID, LogTitle, QuestType, QuestLevel, RequiredRacesOrClasses,
RewardMoney, RewardXPDifficulty, Flags, SpecialFlags
FROM quest_template WHERE ID=12345;
| 字段 | 作用 |
|---|---|
| ID | 任务唯一 ID,必须避开客户端已占用范围 |
| LogTitle | 任务标题 |
| LogDescription | 任务文本(日志里的详细描述) |
| QuestType | 0 普通任务,1 组队,2 团队(生命),3 PvP |
| QuestLevel | 任务等级(影响感叹号颜色和 XP) |
| MinLevel | 可接取的最低角色等级 |
| Flags | 任务标志位(自动完成、不可放弃、日常等) |
| RewardXPDifficulty | XP 奖励档位(0-10,由 QuestLevel 查表换算) |
三、任务目标:四种完成条件
quest_template 用 4 组字段描述”做什么才能完成任务”:
1. 击杀怪物(RequiredNpcOrGo + NpcOrGoCount)
-- RequiredNpcOrGo1..4: NPC entry(正数)或 GameObject entry(负数)
-- NpcOrGoCount1..4: 所需数量
RequiredNpcOrGo1=3887, NpcOrGoCount1=5 -- 击杀5只 NPC 3887
RequiredNpcOrGo2=-12345, NpcOrGoCount2=1 -- 使用1个 GO 12345(负数表示 GO)
2. 收集物品(RequiredItemId + RequiredItemCount)
RequiredItemId1=60000, RequiredItemCount1=10 -- 收集10个物品
RequiredItemId2=60001, RequiredItemCount2=3
3. 探索区域(RequiredAreaTriggerId)
RequiredAreaTriggerId=4567 -- 到达指定 AreaTrigger 区域
到达后客户端自动触发完成,无需与 NPC 交互。
4. 击杀指定生物的进阶(RequiredSpellCast / 特殊)
某些复杂条件(如对怪物使用物品再击杀)需要脚本配合,见后文。
四、任务链与前置关系
任务链通过 quest_template_addon 表的 PrevQuestID 和 NextQuestID 串联:
-- 任务链 A -> B -> C
-- quest_template_addon:
INSERT INTO quest_template_addon (ID, PrevQuestID, NextQuestID, ExclusiveGroup, RewardMailDelay)
VALUES
(12001, 0, 12002, 0, 0), -- A 无前置,下一个 B
(12002, 12001, 12003, 0, 0), -- B 前置 A
(12003, 12002, 0, 0, 0); -- C 前置 B
关键机制:
- PrevQuestID 为负数:表示”该前置任务未完成时才能接”(互斥分支)
- ExclusiveGroup:同一组内多个任务只能完成一个(典型的二选一支线)
- BreadCrumbs:AllowableClasses 字段配合可做职业限定
NPC 接取和交付关系由 creature_queststarter / creature_questender 表配置(一个 NPC 发任务,另一个收任务的场景):
-- NPC 90001 发放任务 12001
INSERT INTO creature_queststarter (id, quest) VALUES (90001, 12001);
-- NPC 90002 收取任务 12001
INSERT INTO creature_questender (id, quest) VALUES (90002, 12001);
五、奖励配置
物品奖励(二选一或全给)
-- RewardChoiceItemID1..6: 玩家从这些中选一个
-- RewardItemID1..4: 全部发放
-- RewardChoiceItemCount / RewardItemCount: 数量
RewardChoiceItemID1=60100, RewardChoiceItemCount1=1,
RewardChoiceItemID2=60101, RewardChoiceItemCount2=1,
RewardItemID1=60200, RewardItemCount1=2
金钱、经验、声望
RewardMoney=15000 -- 铜币(1金50银)
RewardXPDifficulty=5 -- XP 档位
-- RewardFactionID1..5 + RewardFactionValue1..5 + RewardFactionOverride
RewardFactionID1=72, RewardFactionValue1=250, RewardFactionOverride1=0
六、自定义任务脚本
数据库覆盖不了的逻辑(护送、剧情、动态目标)用 QuestScript 实现:
class quest_custom_escort : public QuestScript
{
public:
quest_custom_escort() : QuestScript("quest_custom_escort") {}
void OnQuestStart(Player* player, Creature* creature) override
{
// 任务开始:生成护送 NPC
if (Creature* escort = creature->SummonCreature(
NPC_ESCORTEE, creature->GetPosition(),
TEMPSUMMON_TIMED_DESPAWN, 300000))
{
escort->SetWalk(true);
escort->GetMotionMaster()->MovePath(PATH_ESCORT, false);
}
}
void OnQuestComplete(Player* player, Creature* creature) override
{
// 完成时触发剧情:喊话、生成敌人波次
creature->AI()->Talk(SAY_THANKS, player);
// ... 波次逻辑
}
void OnQuestFail(Player* player) override
{
// 护送目标死亡等失败处理
player->FailQuest(12001);
}
};
void AddSC_quest_custom_escort()
{
new quest_custom_escort();
}
复杂目标(如用道具削弱后再击杀)用 PlayerScript 的 OnCreatureKill 钩子配合物品使用事件处理,并手动调用 player->CompleteQuest()。
七、自定义任务完整创建示例
-- 1. 任务模板:杀怪收集型
INSERT INTO quest_template
(ID, QuestType, QuestLevel, MinLevel, LogTitle, LogDescription,
RequiredNpcOrGo1, NpcOrGoCount1,
RequiredItemId1, RequiredItemCount1,
RewardMoney, RewardXPDifficulty, Flags)
VALUES
(12001, 0, 30, 28, '清除狼群', '银松森林的狼群威胁着旅人,去消灭它们并取回狼爪。',
3875, 8,
60000, 8,
12000, 4, 0);
-- 2. 任务链关系
INSERT INTO quest_template_addon (ID, PrevQuestID, NextQuestID)
VALUES (12001, 0, 12002);
-- 3. 发放/收取 NPC
INSERT INTO creature_queststarter (id, quest) VALUES (90001, 12001);
INSERT INTO creature_questender (id, quest) VALUES (90001, 12001);
-- 4. 物品奖励
UPDATE quest_template SET
RewardChoiceItemID1=60100, RewardChoiceItemCount1=1,
RewardChoiceItemID2=60101, RewardChoiceItemCount2=1
WHERE ID=12001;
八、客户端缓存与测试
# GM 命令
.quest add 12001 # 直接接取任务
.quest complete 12001 # 完成(测试奖励发放)
.quest remove 12001 # 移除
.lookup quest 清除狼群 # 查找任务 ID
.reload quest_template # 重载任务表(无需重启)
# NPC 相关
.npc add 90001 # 放置发放 NPC
若 NPC 头顶不显示感叹号:这是客户端缓存问题。删除客户端 WDB/cache 目录下的 questcache.wdb,或让玩家与 NPC 实际对话触发服务器下发任务数据。自定义客户端通常需要同步修改任务本地化文件。
九、常见坑
- 任务 ID 冲突:自定义 ID 必须避开官方和模组占用段,建议用 12000 起的私有段
- RequiredNpcOrGo 正负号:正数是 NPC,负数是 GameObject,写反会找不到目标
- 物品不存在:RequiredItemId 指向的物品必须在 item_template 中存在,否则无法计数
- Flags 误用:FLAGS_AUTOCOMPLETE 会跳过交付,FLAGS_DAILY 任务需配合重置时间
- 忘记重载:改库后必须 .reload quest_template,否则服务器还用旧数据
- 声望溢出:RewardFactionValue 是实际值乘以 100,250 声望写 25000
总结
任务开发的核心是先判断需求落在哪一层:简单的杀怪/收集/传话纯靠 quest_template 配置即可,无需一行代码;涉及剧情、护送、动态目标才引入 QuestScript。任务链用 addon 表的 Prev/Next 串联,NPC 关系用 starter/ender 表绑定。掌握”数据库优先、脚本兜底”这个原则,能避免把简单任务过度复杂化。