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;
}

匹配流程:

  1. 玩家排队,记录 rating 和等待时间
  2. 每 5 秒扫一次队列,找 rating 在范围内的对手
  3. 计算匹配质量,选质量最高的配对
  4. 质量 < 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 计算和匹配范围逻辑,做跨服竞技场、天梯排行榜、赛季专属坐骑都只是参数调整。下一篇讲怎么做一个「观战系统」,让玩家可以实时观看好友的竞技场。