TrinityCore 竞技场系统深度解析:Elo 匹配、积分计算与赛季结算 原创
温馨提示:
本文最后更新于 2026-10-26,已超过 0 天没有更新。
若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我。
竞技场是 TrinityCore 中最硬核的 PvP 系统:Elo 匹配算法、积分计算、赛季排名、奖励发放——每个环节都涉及数学模型和实时判定。这篇从 arena_team 表出发,讲清楚匹配机制、积分系统和赛季结算的实现原理。
一、核心表:arena_team 家族
-- 竞技场队伍
CREATE TABLE arena_team (
arenaTeamId INT UNSIGNED PRIMARY KEY,
name VARCHAR(24),
captainGuid INT UNSIGNED,
type TINYINT UNSIGNED, -- 2v2 / 3v3 / 5v5
rating INT UNSIGNED DEFAULT 1500, -- 队伍积分
weekGames INT UNSIGNED, -- 本周场次
weekWins INT UNSIGNED, -- 本周胜场
seasonGames INT UNSIGNED, -- 赛季场次
seasonWins INT UNSIGNED, -- 赛季胜场
rank INT UNSIGNED -- 排名
);
-- 队伍成员
CREATE TABLE arena_team_member (
arenaTeamId INT UNSIGNED,
guid INT UNSIGNED,
weekGames INT UNSIGNED, -- 个人本周场次
weekWins INT UNSIGNED,
seasonGames INT UNSIGNED,
seasonWins INT UNSIGNED,
personalRating INT UNSIGNED DEFAULT 1500, -- 个人积分
played INT UNSIGNED -- 总场次
);
-- 统计(赛季结算用)
CREATE TABLE arena_team_stats (
arenaTeamId INT UNSIGNED,
rating INT UNSIGNED,
rank INT UNSIGNED,
seasonGames INT UNSIGNED,
seasonWins INT UNSIGNED
);
二、Elo 匹配:不只是「找相近积分」
TrinityCore 的匹配算法在 BattlegroundQueue 里实现,核心是 Elo 变体:
// 匹配范围随等待时间扩大
uint32 BattlegroundQueue::GetMatchingRange(uint32 rating, uint32 time)
{
// 基础范围 ±100
uint32 range = 100;
// 每等 30 秒扩大 50
range += (time / 30) * 50;
// 上限 ±500
return std::min(range, 500u);
}
// 匹配质量评分
float BattlegroundQueue::GetMatchQuality(uint32 rating1, uint32 rating2)
{
uint32 diff = std::abs(int32(rating1) - int32(rating2));
// 差距越小质量越高
if (diff < 50) return 1.0f;
if (diff < 100) return 0.8f;
if (diff < 200) return 0.5f;
if (diff < 300) return 0.3f;
return 0.1f;
}
匹配流程:
- 玩家排队,记录 rating 和等待时间
- 每 5 秒扫一次队列,找 rating 在范围内的对手
- 计算匹配质量,选质量最高的配对
- 质量 < 0.3 时,扩大范围或继续等待
三、积分计算:K 值衰减的 Elo
比赛结束后,积分变化用改进的 Elo 公式:
void ArenaTeam::UpdateRating(uint32 opponentRating, bool won)
{
// 1. 计算期望胜率
float expected = 1.0f / (1.0f + pow(10.0f, (opponentRating - rating) / 400.0f));
// 2. K 值(新队伍 K=40,老队伍 K=20)
uint32 k = (seasonGames < 30) ? 40 : 20;
// 3. 实际得分(胜 1,负 0)
float score = won ? 1.0f : 0.0f;
// 4. 计算变化
int32 change = int32(k * (score - expected));
// 5. 应用
rating = std::max(0, int32(rating) + change);
// 6. 记录
seasonGames++;
if (won) seasonWins++;
}
关键设计:K 值随场次衰减,防止新队伍刷分,也防止老队伍掉分太快。
四、赛季结算:不只是发奖励
赛季结束时的完整流程:
void ArenaTeamMgr::EndSeason()
{
// 1. 冻结所有队伍
for (auto& [id, team] : _arenaTeams)
team->SetFrozen(true);
// 2. 计算排名
std::vector sorted;
for (auto& [id, team] : _arenaTeams)
if (team->GetSeasonGames() >= 10) // 至少 10 场才排名
sorted.push_back(team);
std::sort(sorted.begin(), sorted.end(),
[](ArenaTeam* a, ArenaTeam* b) { return a->GetRating() > b->GetRating(); });
// 3. 分配排名
for (size_t i = 0; i < sorted.size(); ++i)
sorted[i]->SetRank(i + 1);
// 4. 发奖励
for (auto* team : sorted)
{
uint32 rank = team->GetRank();
uint32 type = team->GetType();
// 根据排名发称号、坐骑、装备
if (rank == 1)
team->SendReward(ARENA_REWARD_GLADIATOR);
else if (rank <= 10)
team->SendReward(ARENA_REWARD_DUELIST);
else if (rank <= 100)
team->SendReward(ARENA_REWARD_RIVAL);
else if (rank <= 500)
team->SendReward(ARENA_REWARD_CHALLENGER);
}
// 5. 重置积分(软重置)
for (auto& [id, team] : _arenaTeams)
{
team->SetRating(1500); // 重置到 1500
team->SetSeasonGames(0);
team->SetSeasonWins(0);
team->SetFrozen(false);
}
}
五、实战:做一个「周常竞技场任务」
需求:每周打 10 场 2v2,奖励额外荣誉和箱子。
-- 1. 任务定义
INSERT INTO quest_template (
ID, Type, Flags, SpecialFlags,
MinLevel, QuestSortID,
LogTitle, LogDescription,
RequiredSpellCast1, RequiredSpellCastTarget1, -- 用占位符
RewardXP, RewardMoney, RewardItem1, RewardItemCount1
) VALUES (
90020, 0, 4096, 2, -- 周常
80, -375,
'周常:竞技场热身', '完成 10 场 2v2 竞技场',
0, 0,
0, 500000, -- 50G
47241, 5 -- 5 个牌子
);
-- 2. 条件:本周 2v2 场次 >= 10
-- 需要在代码里检查 arena_team_member.weekGames
C++ 判定逻辑:
bool Player::CanCompleteQuest(uint32 questId)
{
if (questId == 90020) // 周常竞技场
{
// 找玩家的 2v2 队伍
if (ArenaTeam* team = GetArenaTeam(ARENA_TYPE_2v2))
{
if (team->GetWeekGames() >= 10)
return true;
}
return false;
}
// ... 其他任务
}
结语
竞技场系统的核心是数学模型 + 实时判定。Elo 匹配保证公平,K 值衰减防止刷分,赛季结算制造目标感。理解了 rating 计算和匹配范围逻辑,做跨服竞技场、天梯排行榜、赛季专属坐骑都只是参数调整。下一篇讲怎么做一个「观战系统」,让玩家可以实时观看好友的竞技场。