AzerothCore 公会系统开发:从创建、权限到银行与事件的完整链路 原创

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

公会是 AzerothCore 中最复杂的社交系统:等级、权限、银行、事件、消息——每个功能都涉及多表关联和实时同步。这篇从 guild 表出发,讲清楚公会创建、权限管理、银行系统和事件通知的实现原理。

一、核心表:guild 家族

公会系统涉及 6 张核心表:

表 作用
guild 公会基本信息(名称、等级、经验、金钱)
guild_member 成员列表(GUID、等级、职位、备注)
guild_rank 职位定义(名称、权限、银行权限)
guild_bank_tab 银行标签页(名称、图标、权限)
guild_bank_item 银行物品(标签页、槽位、物品 GUID)
guild_eventlog 事件日志(加入、离开、晋升、银行操作)

二、公会创建:不只是 INSERT

创建公会的完整链路:

// Guild::Create 的简化逻辑
bool Guild::Create(Player* pLeader, std::string const& name)
{
    // 1. 校验名称唯一
    if (sGuildMgr->GetGuildByName(name))
        return false;
    
    // 2. 生成 guild Id
    uint32 guildId = sGuildMgr->GenerateGuildId();
    
    // 3. 插入主表
    CharacterDatabase.Execute(
        "INSERT INTO guild (guildid, name, leaderguid, createdate) "
        "VALUES ({}, '{}', {}, UNIX_TIMESTAMP())",
        guildId, name, pLeader->GetGUID().GetCounter());
    
    // 4. 创建默认职位(会长、官员、成员、新人)
    for (int i = 0; i < GUILD_RANKS_DEFAULT; ++i)
        CreateDefaultRank(i);
    
    // 5. 会长加入
    AddMember(pLeader->GetGUID(), GUILD_RANK_LEADER);
    
    // 6. 广播给在线玩家
    SendGuildRosterToAll();
    
    return true;
}

三、权限系统:Rank 的位掩码设计

guild_rank 的 rights 字段是位掩码:

enum GuildRankRights
{
    GR_RIGHT_EMPTY              = 0x00000000,
    GR_RIGHT_GCHATLISTEN        = 0x00000001,  // 听公会聊天
    GR_RIGHT_GCHATSPEAK         = 0x00000002,  // 说公会聊天
    GR_RIGHT_OFFCHATLISTEN      = 0x00000004,  // 听官员聊天
    GR_RIGHT_OFFCHATSPEAK       = 0x00000008,  // 说官员聊天
    GR_RIGHT_INVITE             = 0x00000010,  // 邀请
    GR_RIGHT_REMOVE             = 0x00000020,  // 踢人
    GR_RIGHT_PROMOTE            = 0x00000040,  // 晋升
    GR_RIGHT_DEMOTE             = 0x00000080,  // 降职
    GR_RIGHT_SETMOTD            = 0x00000100,  // 设置 MOTD
    GR_RIGHT_EPNOTE             = 0x00000200,  // 编辑公共备注
    GR_RIGHT_VIEWOFFNOTE        = 0x00000400,  // 查看官员备注
    GR_RIGHT_EOFFNOTE           = 0x00000800,  // 编辑官员备注
    GR_RIGHT_MODIFY_GUILD_INFO  = 0x00001000,  // 修改公会信息
    GR_RIGHT_WITHDRAW_GOLD      = 0x00002000,  // 提取金币
    GR_RIGHT_WITHDRAW_REPAIR    = 0x00004000,  // 修理费
    GR_RIGHT_WITHDRAW_ITEM      = 0x00008000,  // 提取物品
    GR_RIGHT_ALL                = 0xFFFFFFFF,
};

检查权限的代码:

bool Guild::HasRankRight(uint32 rankId, GuildRankRights right) const
{
    if (rankId >= _ranks.size())
        return false;
    return (_ranks[rankId].GetRights() & right) == right;
}

// 使用
if (!guild->HasRankRight(member->GetRank(), GR_RIGHT_WITHDRAW_ITEM))
    return false;  // 无权提取物品

四、银行系统:Tab 权限 + 物品追踪

公会银行不是简单的「大背包」,而是带权限的共享存储:

// 每个 Tab 有独立的权限
struct GuildBankTab
{
    uint32 TabId;
    std::string Name;
    std::string Icon;
    uint32 Rights;        // 谁能看
    uint32 WithdrawRights; // 谁能取
};

// 存取记录
struct GuildBankEventLog
{
    uint32 GuildId;
    uint32 TabId;
    uint32 EventType;     // 存入/取出/移动
    uint32 PlayerGuid;
    uint32 ItemEntry;
    uint32 Count;
    uint32 TimeStamp;
};

关键设计:物品移动是原子操作,必须同时更新 guild_bank_item 和玩家背包,任何一方失败都要回滚。AzerothCore 用事务保证:

// 伪代码
CharacterDatabase.BeginTransaction();
try {
    // 1. 从银行移除
    RemoveItemFromTab(tabId, slotId);
    
    // 2. 添加到玩家背包
    player->AddItem(itemEntry, count);
    
    // 3. 记录日志
    LogBankEvent(tabId, GB_EVENT_WITHDRAW, playerGuid, itemEntry, count);
    
    CharacterDatabase.CommitTransaction();
} catch (...) {
    CharacterDatabase.RollbackTransaction();
    throw;
}

五、事件系统:实时通知的关键

公会事件(加入、离开、晋升、银行操作)需要实时推送给在线成员:

void Guild::BroadcastEvent(GuildEvents event, uint32 arg1, uint32 arg2, uint32 arg3)
{
    // 构建数据包
    WorldPacket data(SMSG_GUILD_EVENT, 64);
    data << uint8(event);
    data << uint8(3);  // 参数数量
    data << arg1 << arg2 << arg3;
    
    // 发送给所有在线成员
    for (auto& member : _members)
    {
        if (Player* player = member.FindPlayer())
            player->SendDirectMessage(&data);
    }
    
    // 写入事件日志
    CharacterDatabase.Execute(
        "INSERT INTO guild_eventlog (guildid, eventtype, arg1, arg2, arg3, timestamp) "
        "VALUES ({}, {}, {}, {}, {}, UNIX_TIMESTAMP())",
        GetId(), event, arg1, arg2, arg3);
}

六、实战:做一个「公会贡献榜」

需求:统计成员对公会的贡献(存款、捐物、带新人),每周结算发奖励。

-- 1. 加贡献字段
ALTER TABLE guild_member ADD COLUMN contribution INT UNSIGNED DEFAULT 0;

-- 2. 记录贡献(触发器或代码)
-- 存款时 +1
-- 捐物时 +物品价值
-- 带新人完成任务 +50

-- 3. 每周结算(worldserver.conf 加定时器)
-- Guild.WeeklyReset = 1

-- 4. 结算逻辑
void Guild::WeeklyContributionReset()
{
    // 排序取前 10
    auto sorted = GetMembersSortedByContribution();
    
    for (int i = 0; i < 10 && i < sorted.size(); ++i)
    {
        Player* player = sorted[i]->FindPlayer();
        if (player)
        {
            // 发邮件奖励
            SendGuildRewardMail(player, i + 1);
        }
    }
    
    // 清零
    CharacterDatabase.Execute(
        "UPDATE guild_member SET contribution = 0 WHERE guildid = {}", GetId());
}

结语

公会系统的复杂度在于多表关联 + 实时同步 + 权限细分。理解了 Rank 位掩码、银行 Tab 权限、事件广播这三个核心机制,做公会等级、公会成就、跨服公会都只是参数扩展。下一篇讲怎么做一个「公会副本进度排行榜」。