TrinityCore 任务系统深度解析:quest_template 字段、条件链与奖励机制 原创
温馨提示:
本文最后更新于 2026-10-22,已超过 0 天没有更新。
若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我。
任务是 TrinityCore 中最复杂的内容系统:quest_template 有 100+ 个字段、条件链可以无限嵌套、奖励涉及经验/金钱/物品/声望/法术。这篇把任务从数据定义到运行时判定的完整链路讲清楚,涵盖 90% 的自定义任务开发场景。
一、quest_template:字段太多,先看核心分组
quest_template 的字段可以分成 6 组:
| 分组 | 关键字段 | 作用 |
|---|---|---|
| 基础信息 | ID, LogTitle, LogDescription, QuestDescription | 任务日志显示 |
| 接取条件 | MinLevel, MaxLevel, RequiredRaces, RequiredClasses, PrevQuestId | 谁能接 |
| 完成条件 | RequiredItemId1-6, RequiredNpcOrGo1-4, RequiredSpellCast | 怎么完成 |
| 奖励 | RewardItem1-4, RewardMoney, RewardXP, RewardFactionId1-5 | 给什么 |
| 任务链 | NextQuestId, ExclusiveGroup, BreadcrumbForQuestId | 前后关联 |
| 特殊 | Flags, Type, TimeAllowed, SpecialFlags | 日常/周常/限时 |
二、接取条件:MinLevel 只是冰山一角
最常见的误解是「任务只有等级要求」。实际上接取判定是多层过滤:
// Player::CanTakeQuest 的简化逻辑
bool Player::CanTakeQuest(Quest const* quest, bool msg)
{
// 1. 等级
if (GetLevel() < quest->GetMinLevel())
return false;
// 2. 种族/职业
if (!(quest->GetAllowableRaces() & (1 << GetRace())))
return false;
// 3. 前置任务
if (quest->GetPrevQuestId() && !GetQuestRewardStatus(quest->GetPrevQuestId()))
return false;
// 4. 互斥组(ExclusiveGroup 里只能接一个)
if (quest->GetExclusiveGroup() && HasQuestInExclusiveGroup(quest))
return false;
// 5. 职业/技能要求
if (quest->GetRequiredSkill() && GetSkillValue(quest->GetRequiredSkill()) < quest->GetRequiredSkillValue())
return false;
// 6. 阵营要求
if (quest->GetRequiredMinRepFaction() &&
GetReputation(quest->GetRequiredMinRepFaction()) < quest->GetRequiredMinRepValue())
return false;
return true;
}
三、完成条件:不只是「杀 10 个怪」
TrinityCore 支持 5 种完成条件,可以组合使用:
3.1 击杀类(最常用)
RequiredNpcOrGo1 = 12345 -- 生物 entry
RequiredNpcOrGoCount1 = 10 -- 数量
3.2 收集类
RequiredItemId1 = 23456 -- 物品 entry
RequiredItemCount1 = 5
3.3 探索类
-- 配合 areatrigger_involvedrelation 表
-- 玩家进入指定区域自动完成
3.4 法术类
RequiredSpellCast1 = 34567 -- 在目标上施放指定法术
RequiredSpellCastTarget1 = 12345
3.5 事件类(最复杂)
-- 需要 SmartAI 或 C++ 脚本触发
-- 例如:护送 NPC 到目的地、保护 NPC 不死
四、任务链:NextQuestId vs ExclusiveGroup
两个字段都管「任务关联」,但逻辑完全不同:
| 字段 | 作用 | 典型场景 |
|---|---|---|
| PrevQuestId | 必须先完成 X 才能接当前 | 线性任务链 |
| NextQuestId | 完成后自动接到 X | 自动推进剧情 |
| ExclusiveGroup | 同组任务只能接一个 | 分支选择(选了 A 就不能选 B) |
| BreadcrumbForQuestId | 完成后提示去接 X | 引导玩家去新区域 |
实战:做一个「三选一」分支任务
-- 三个任务都设 ExclusiveGroup = 100
-- 玩家接了 A,B 和 C 就不可接
INSERT INTO quest_template (ID, LogTitle, ExclusiveGroup, ...) VALUES
(90001, '帮助铁炉堡', 100, ...),
(90002, '帮助暴风城', 100, ...),
(90003, '帮助达纳苏斯', 100, ...);
五、奖励系统:不只是给经验
任务奖励的完整清单:
-- 物品奖励(最多 4 个固定 + 2 个可选)
RewardItem1 = 12345, RewardItemCount1 = 1
RewardChoiceItemId1 = 23456 -- 玩家可选
-- 金钱
RewardMoney = 10000 -- 铜币
-- 经验
RewardXP = 5000
-- 声望(最多 5 个阵营)
RewardFactionId1 = 72, RewardFactionValue1 = 250
-- 法术(学会/获得 buff)
RewardSpell = 12345
RewardDisplaySpell = 23456 -- 只是显示效果
-- 称号
RewardTitle = 50
-- 天赋点
RewardTalents = 1
奖励在 Player::RewardQuest 里发放,关键逻辑:
void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, bool announce)
{
// 1. 经验(受等级差衰减)
uint32 XP = quest->GetRewXP() * GetQuestXPRate();
GiveXP(XP, nullptr);
// 2. 金钱(满级后经验转金钱)
if (GetLevel() >= GetMaxLevel())
Money += quest->GetRewMoneyMaxLevel();
// 3. 物品
for (int i = 0; i < QUEST_REWARDS_COUNT; ++i)
if (quest->RewardItemId[i])
AddItem(quest->RewardItemId[i], quest->RewardItemCount[i]);
// 4. 声望
for (int i = 0; i < QUEST_REPUTATIONS_COUNT; ++i)
if (quest->RewardFactionId[i])
ModifyFactionReputation(quest->RewardFactionId[i], quest->RewardFactionValue[i]);
}
六、实战:做一个「每日副本任务」
结合前面知识,做一个「每天可重复、要求击杀指定副本 Boss」的任务:
-- 1. 基础设置
INSERT INTO quest_template (
ID, Type, Flags, SpecialFlags,
MinLevel, QuestSortID,
LogTitle, LogDescription, QuestDescription,
RequiredNpcOrGo1, RequiredNpcOrGoCount1,
RewardXP, RewardMoney, RewardItem1, RewardItemCount1
) VALUES (
90010, 0, 4096, 1, -- Flags=4096 表示日常, SpecialFlags=1 表示可重复
80, -373, -- QuestSortID=-373 表示副本任务
'每日:净化斯坦索姆', '击败瑞文戴尔男爵', '为了银色黎明,清除斯坦索姆的亡灵!',
10440, 1, -- 瑞文戴尔男爵
10000, 200000, -- 1W 经验, 20G
47241, 1 -- 奖励一个牌子
);
-- 2. 设置每日重置(world.quest_daily 表)
INSERT INTO quest_daily (QuestId) VALUES (90010);
结语
任务系统的复杂度不在单个任务,而在条件链的嵌套和奖励的组合。理解了 quest_template 的字段分组、接取/完成判定的多层过滤、以及奖励发放的时机,做日常、周常、职业任务、阵营任务都只是参数组合的问题。下一篇讲怎么用 SmartAI 做「护送」「保护」这类事件型任务。