AzerothCore Module 开发实战:从零构建自定义功能模块 原创
温馨提示:
本文最后更新于 2026-06-18,已超过 0 天没有更新。
若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我。
一、什么是 AzerothCore Module?
AzerothCore 的 Module 系统是其最强大的扩展机制之一。与直接修改核心源码不同,Module 允许开发者以插件化的方式添加功能,而无需修改 TrinityCore 分支的核心代码。这意味着:
- 不污染核心:所有修改隔离在 Module 目录中,核心升级时无需重新适配
- 易于分发:Module 可以打包分享,其他服主只需复制到 modules 目录即可使用
- 热插拔:通过 CMake 配置即可启用或禁用,无需手动合并代码
二、Module 架构解析
2.1 目录结构
一个标准的 AzerothCore Module 遵循以下目录结构:
modules/
└── mod-custom-name/
├── CMakeLists.txt # 构建配置
├── README.md # 文档说明
├── include/
│ └── mod_custom_name/
│ └── CustomName.h # 头文件
├── src/
│ ├── CustomName.cpp # 核心实现
│ └── CustomNameScripts.cpp # 脚本注册
└── sql/
└── world/
└── 2026_01_01_00_custom_name.sql # 数据库变更
2.2 CMakeLists.txt 模板
每个 Module 的核心是 CMakeLists.txt,它告诉构建系统如何编译 Module:
# 模块名称(必须唯一)
set(MOD_NAME mod-custom-name)
# 源文件列表
set(MOD_SOURCES
${MOD_NAME}/src/CustomName.cpp
${MOD_NAME}/src/CustomNameScripts.cpp
)
# 头文件目录
set(MOD_HEADERS
${MOD_NAME}/include/
)
# 注册到 AzerothCore 构建系统
ac_add_module(${MOD_NAME} "${MOD_SOURCES}" "${MOD_HEADERS}")
# 链接依赖库(如有需要)
target_link_libraries(${MOD_NAME} PRIVATE
${AC_LIBRARY_SCRIPTS}
)
三、实战:编写一个玩家欢迎 Module
下面我们从一个完整的实战案例开始,逐步构建一个功能完整的 Module。
3.1 功能需求
- 玩家首次登录时发送欢迎消息
- 记录玩家在线时长
- 提供
.online命令查询在线时长 - 玩家上线时公告通知公会成员
3.2 头文件定义
// include/mod_welcome/WelcomeMod.h
#ifndef WELCOME_MOD_H
#define WELCOME_MOD_H
#include "ScriptMgr.h"
#include "Player.h"
#include "Chat.h"
#include "WorldSession.h"
class WelcomeMod : public PlayerScript
{
public:
WelcomeMod() : PlayerScript("WelcomeMod") { }
// 玩家登录时调用
void OnLogin(Player* player) override;
// 玩家首次登录
void OnFirstLogin(Player* player) override;
// 玩家登出时调用
void OnLogout(Player* player) override;
private:
// 存储玩家在线时长的 Map
static std::map<uint32, uint32> m_onlineTime;
static std::map<uint32, uint32> m_sessionStart;
};
#endif
3.3 核心实现
// src/WelcomeMod.cpp
#include "WelcomeMod.h"
#include "Chat.h"
#include "Language.h"
#include "World.h"
std::map<uint32, uint32> WelcomeMod::m_onlineTime;
std::map<uint32, uint32> WelcomeMod::m_sessionStart;
void WelcomeMod::OnLogin(Player* player)
{
// 记录本次会话开始时间
m_sessionStart[player->GetGUID().GetCounter()] = time(nullptr);
// 发送欢迎消息
std::string welcomeMsg = "|cff00ccff欢迎回到艾泽拉斯,";
welcomeMsg += player->GetName();
welcomeMsg += "!|r";
ChatHandler(player->GetSession()).SendSysMessage(welcomeMsg);
// 获取总在线时长
uint32 totalMinutes = m_onlineTime[player->GetGUID().GetCounter()] / 60;
if (totalMinutes > 0)
{
std::ostringstream ss;
ss << "|cff00ff00你已累计在线 " << totalMinutes << " 分钟|r";
ChatHandler(player->GetSession()).SendSysMessage(ss.str());
}
// 通知公会成员
if (Guild* guild = player->GetGuild())
{
std::ostringstream ss;
ss << "|cff00ccff[公会] " << player->GetName() << " 上线了!|r";
guild->BroadcastToMembers(player->GetSession(), false, ss.str());
}
}
void WelcomeMod::OnFirstLogin(Player* player)
{
// 首次登录特殊欢迎
std::string welcomeMsg = "|cffffcc00欢迎来到艾泽拉斯,";
welcomeMsg += player->GetName();
welcomeMsg += "!愿你在这片土地上书写传奇!|r";
// 发送系统公告
sWorld.SendServerMessage(SERVER_MSG_STRING, welcomeMsg);
}
void WelcomeMod::OnLogout(Player* player)
{
uint32 guid = player->GetGUID().GetCounter();
uint32 sessionStart = m_sessionStart[guid];
if (sessionStart > 0)
{
uint32 elapsed = time(nullptr) - sessionStart;
m_onlineTime[guid] += elapsed;
m_sessionStart.erase(guid);
}
}
3.4 命令注册
// src/WelcomeModScripts.cpp
#include "WelcomeMod.h"
#include "Chat.h"
class welcome_commandscript : public CommandScript
{
public:
welcome_commandscript() : CommandScript("welcome_commandscript") { }
std::vector<ChatCommand> GetCommands() const override
{
static std::vector<ChatCommand> welcomeCommandTable =
{
{ "online", SEC_PLAYER, true, &HandleOnlineCommand, "" }
};
static std::vector<ChatCommand> commandTable =
{
{ "welcome", SEC_PLAYER, true, nullptr, "", welcomeCommandTable }
};
return commandTable;
}
static bool HandleOnlineCommand(ChatHandler* handler, const char* /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
uint32 guid = player->GetGUID().GetCounter();
uint32 totalSeconds = WelcomeMod::m_onlineTime[guid];
uint32 hours = totalSeconds / 3600;
uint32 minutes = (totalSeconds % 3600) / 60;
uint32 seconds = totalSeconds % 60;
std::ostringstream ss;
ss << "|cff00ff00你的累计在线时间:"
<< hours << " 小时 " << minutes << " 分钟 " << seconds << " 秒|r";
handler->SendSysMessage(ss.str());
return true;
}
};
void AddSC_welcome_mod_scripts()
{
new welcome_commandscript();
}
四、数据库集成
如果 Module 需要持久化数据,需要创建对应的数据库表:
-- sql/world/2026_01_01_00_welcome_mod.sql
DROP TABLE IF EXISTS `mod_welcome_player_stats`;
CREATE TABLE `mod_welcome_player_stats` (
`guid` INT UNSIGNED NOT NULL,
`total_online_seconds` INT UNSIGNED DEFAULT 0,
`last_login` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`login_count` INT UNSIGNED DEFAULT 0,
PRIMARY KEY (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然后在 OnLogin 和 OnLogout 中读写数据库:
// 从数据库加载
QueryResult result = CharacterDatabase.PQuery(
"SELECT total_online_seconds, login_count FROM mod_welcome_player_stats WHERE guid = %u",
player->GetGUID().GetCounter());
if (result)
{
Field* fields = result->Fetch();
m_onlineTime[guid] = fields[0].Get<uint32>();
}
// 保存到数据库
CharacterDatabase.PExecute(
"INSERT INTO mod_welcome_player_stats (guid, total_online_seconds, login_count) "
"VALUES (%u, %u, 1) ON DUPLICATE KEY UPDATE "
"total_online_seconds = %u, login_count = login_count + 1, last_login = NOW()",
guid, m_onlineTime[guid], m_onlineTime[guid]);
五、编译与部署
5.1 启用 Module
# 克隆 Module 到 modules 目录
cd azerothcore-wotlk/modules
git clone https://github.com/your/mod-custom-name.git
# 重新运行 CMake
cd ..
mkdir build && cd build
cmake .. -DMODULES=mod-custom-name
# 编译
make -j$(nproc)
5.2 应用 SQL 变更
# 导入数据库变更
mysql -u root -p world < modules/mod-custom-name/sql/world/2026_01_01_00_welcome_mod.sql
mysql -u root -p characters < modules/mod-custom-name/sql/characters/2026_01_01_00_welcome_mod.sql
六、调试技巧
6.1 日志输出
// 使用 AzerothCore 日志系统
TC_LOG_INFO("module.welcome", "Player %s logged in", player->GetName().c_str());
TC_LOG_DEBUG("module.welcome", "Session start: %u", sessionStart);
TC_LOG_ERROR("module.welcome", "Failed to load stats for player %u", guid);
6.2 热重载
AzerothCore 支持部分 Module 的热重载:
# 在游戏内使用 .reload 命令
.reload config # 重新加载配置文件
.reload all # 重新加载所有脚本
七、总结
通过本文的实战演练,我们从零构建了一个完整的 AzerothCore Module,涵盖了:
- Module 架构:目录结构、CMake 配置、构建集成
- 脚本系统:PlayerScript、CommandScript 的使用
- 数据库集成:数据持久化与查询
- 编译部署:完整的构建和部署流程
AzerothCore 的 Module 系统为私服开发者提供了强大的扩展能力。掌握 Module 开发,意味着你可以为服务器添加任意自定义功能,而无需担心核心升级带来的兼容性问题。