AzerothCore 战场系统开发:从状态机、旗帜机制到奖励结算的完整链路 原创

温馨提示:
本文最后更新于 2026-10-22,已超过 0 天没有更新。 若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我。

战场是 AzerothCore 中最复杂的 PvP 系统:状态机管理比赛流程、积分判定决定胜负、奖励结算涉及荣誉/声望/物品。这篇以战歌峡谷(Warsong Gulch)为例,从 Battleground 基类出发,讲清楚旗帜机制、积分系统和奖励发放的实现原理。

一、Battleground 基类:状态机是核心

所有战场都继承自 Battleground 基类,核心是状态机:

enum BattlegroundStatus
{
    STATUS_NONE             = 0,
    STATUS_WAIT_QUEUE       = 1,  // 排队中
    STATUS_WAIT_JOIN        = 2,  // 等待玩家进入
    STATUS_IN_PROGRESS      = 3,  // 进行中
    STATUS_WAIT_LEAVE       = 4,  // 等待离开
};

状态切换由 BattlegroundMgr 驱动,关键时间点:

  • STATUS_WAIT_QUEUE → STATUS_WAIT_JOIN:人数凑齐,发邀请
  • STATUS_WAIT_JOIN → STATUS_IN_PROGRESS:倒计时结束,开门
  • STATUS_IN_PROGRESS → STATUS_WAIT_LEAVE:一方获胜,结算

二、战歌峡谷:旗帜机制的完整实现

战歌的核心是「夺旗-交旗」循环。关键类 BattlegroundWSG:

class BattlegroundWSG : public Battleground
{
    // 旗帜状态
    enum WSGFlagState
    {
        WSG_FLAG_STATE_ON_BASE      = 0,  // 在基地
        WSG_FLAG_STATE_ON_PLAYER    = 1,  // 被玩家携带
        WSG_FLAG_STATE_ON_GROUND    = 2,  // 掉在地上
    };
    
    // 积分
    uint32 m_TeamScores[2];         // 联盟/部落得分
    uint32 m_FlagState[2];          // 双方旗帜状态
    ObjectGuid m_FlagKeepers[2];    // 携带旗帜的玩家
};

2.1 夺旗判定

玩家点击敌方旗帜时触发:

void BattlegroundWSG::EventPlayerClickedOnFlag(Player* player, GameObject* go)
{
    // 1. 校验:不能是自己队伍的旗帜
    if (GetTeamIndexByTeamId(player->GetTeamId()) == GetFlagTeamIndex(go))
        return;
    
    // 2. 校验:旗帜必须在基地或地上
    if (m_FlagState[flagTeam] != WSG_FLAG_STATE_ON_BASE &&
        m_FlagState[flagTeam] != WSG_FLAG_STATE_ON_GROUND)
        return;
    
    // 3. 捡起旗帜
    m_FlagState[flagTeam] = WSG_FLAG_STATE_ON_PLAYER;
    m_FlagKeepers[flagTeam] = player->GetGUID();
    player->CastSpell(player, SPELL_WSG_FLAG, true);  // 给玩家上旗帜 buff
    
    // 4. 广播消息
    SendMessageToAll(LANG_BG_WS_FLAG_TAKEN, ...);
}

2.2 交旗得分

玩家把旗帜带回自己基地时:

void BattlegroundWSG::EventPlayerCapturedFlag(Player* player)
{
    // 1. 校验:必须携带敌方旗帜
    if (!player->HasAura(SPELL_WSG_FLAG))
        return;
    
    // 2. 校验:己方旗帜必须在基地(不能也被夺走)
    if (m_FlagState[playerTeam] != WSG_FLAG_STATE_ON_BASE)
    {
        SendMessageToPlayer(player, LANG_BG_WS_FLAG_CANT_CAP, ...);
        return;
    }
    
    // 3. 得分
    m_TeamScores[playerTeam]++;
    UpdateWorldState(WSG_WORLD_STATE_ALLIANCE_SCORE, m_TeamScores[ALLIANCE]);
    
    // 4. 重置敌方旗帜
    m_FlagState[enemyTeam] = WSG_FLAG_STATE_ON_BASE;
    RespawnFlag(enemyTeam);
    
    // 5. 检查胜利
    if (m_TeamScores[playerTeam] >= 3)
        EndBattleground(playerTeam);
}

三、积分系统:不仅仅是比分

战歌的积分不只是 3 分制,还有荣誉击杀、助攻、时间加成:

行为 奖励
夺旗成功 +198 荣誉
击杀敌方 基础荣誉 × 战场系数
协助击杀 50% 荣誉
获胜方 额外 3 个战歌徽章
失败方 1 个战歌徽章

这些数值在 Battleground.h 的 BGHonorReward 结构体里定义,可以通过 worldserver.conf 调整:

Battleground.HonorWinner = 198
Battleground.HonorLoser  = 50
Battleground.HonorKill   = 20.8

四、奖励发放:不只是荣誉

战场结束时,Battleground::EndBattleground 触发奖励结算:

void Battleground::EndBattleground(uint32 winner)
{
    // 1. 计算荣誉
    uint32 honorWinner = sWorld->getIntConfig(CONFIG_BG_HONOR_WINNER);
    uint32 honorLoser  = sWorld->getIntConfig(CONFIG_BG_HONOR_LOSER);
    
    // 2. 发放荣誉
    for (auto& [guid, player] : GetPlayers())
    {
        uint32 honor = (GetTeamIndexByTeamId(player->GetTeamId()) == winner) 
                       ? honorWinner : honorLoser;
        player->ModifyHonorPoints(honor);
    }
    
    // 3. 发放徽章
    uint32 markWinner = 3, markLoser = 1;
    RewardMarkOfHonor(winner, markWinner);
    RewardMarkOfHonor(GetOtherTeam(winner), markLoser);
    
    // 4. 声望(战歌峡谷是银翼要塞/战歌氏族)
    if (GetTypeID() == BATTLEGROUND_WS)
    {
        RewardReputation(winner, FACTION_SILVERWING_SENTINELS, 35);
        RewardReputation(winner, FACTION_WARSONG_OUTRIDERS, 35);
    }
    
    // 5. 每日首胜额外奖励
    if (IsFirstWinOfDay(player))
        RewardDailyFirstWin(player);
}

五、自定义:做一个「双倍荣誉周末」

结合前面知识,实现每周五-周日双倍荣誉:

// 在 Battleground::EndBattleground 里加时间判断
void Battleground::EndBattleground(uint32 winner)
{
    // ... 原有逻辑 ...
    
    // 检查是否周末
    time_t now = time(nullptr);
    tm* local = localtime(&now);
    bool isWeekend = (local->tm_wday == 5 || local->tm_wday == 6 || local->tm_wday == 0);
    
    uint32 honorMultiplier = isWeekend ? 2 : 1;
    
    for (auto& [guid, player] : GetPlayers())
    {
        uint32 baseHonor = (GetTeamIndexByTeamId(player->GetTeamId()) == winner) 
                           ? honorWinner : honorLoser;
        player->ModifyHonorPoints(baseHonor * honorMultiplier);
        
        if (isWeekend)
            ChatHandler(player->GetSession()).SendSysMessage("周末双倍荣誉已生效!");
    }
}

结语

战场系统的精髓在于状态机驱动 + 事件响应。基类管理排队/进出/结算,子类实现具体玩法(夺旗/占塔/资源争夺)。理解了 BattlegroundWSG 的旗帜状态切换和积分判定,再看阿拉希盆地、奥特兰克山谷就会发现是同一套框架的不同实现。下一篇讲怎么做一个全新的自定义战场。