ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AzerothCore GM指令开发:从零手写一条 ChatCommand 命令并跑通

AzerothCore GM指令开发:从零手写一条 ChatCommand 命令并跑通 AzerothCore GM指令开发:从零手写一条 ChatCommand 命令并跑通【免费下载链接】azerothcore-wotlkComplete Open Source and Modular solution for MMO项目地址: https://gitcode.com/GitHub_Trending/az/azerothcore-wotlkGM 要按一组 GUID 批量修正离线玩家状态,现有 /teleport 只认在线会话,定制又得先摸清注册链路。这份备忘只交付一件事:在 AzerothCore 的 ChatCommand 框架下手写一条 GM指令——最小可编译命令、路由与参数解析规则,再到四步生产化改造。5分钟跑通:注册第一个自定义命令在你的工作区新建文件(按cs_前缀功能标识惯例),内容就四件套:一个处理函数、一张命令表、一个 CommandScript 子类、一个AddSC_注册函数:#include Chat.h #include CommandScript.h #include Player.h using namespace Acore::ChatCommands; static bool HandleFullHealCommand(ChatHandler* handler, PlayerIdentifier target) { Player* player target.GetConnectedPlayer(); if (!player) { handler-SendErrorMessage(LANG_PLAYER_NOT_FOUND); return false; } player-SetHealth(player-GetMaxHealth()); handler-PSendSysMessage(已为 %s 恢复满血, player-GetName()); return true; } static ChatCommandTable commandTable { { fullheal, HandleFullHealCommand, SEC_GAMEMASTER, Console::No } }; class fullheal_commandscript : public CommandScript { public: fullheal_commandscript() : CommandScript(fullheal_commandscript) { } ChatCommandTable GetCommands() const override { return commandTable; } }; void AddSC_fullheal_commandscript() { new fullheal_commandscript(); }文件放进工作区的src/server/scripts/Commands/,编译即过;不接线命令不会生效, 这一步最容易翻车。加载器靠两处把全部AddSC_*拼进调用列表,工作区副本里各补一行:// cs_script_loader.cpp —— 新增声明 void AddSC_fullheal_commandscript(); // ScriptLoader.cpp.in.cmake —— 生成调用列表处新增同名调用 // AddSC_fullheal_commandscript();构建后/fullheal 某玩家或/fullheal GUID即可把目标补满血。注册链路是:GetCommands()返回命令表 →CommandScript基类(定义见src/server/game/Scripting/ScriptDefines/CommandScript.h)持有表与脚本名 →AddSC_函数负责new出实例 → 加载器在服务启动时统一调用。处理函数签名被HandlerToTuple静态检查:必须形如bool(ChatHandler*, …),否则编译期直接报错。命令路由匹配过程:ChatCommandTable 如何走到处理函数框架核心是两个类型定义,位置在src/server/game/Chat/ChatCommands/ChatCommand.h:using ChatCommandTable std::vectorChatCommandBuilder; // ChatCommandBuilder: InvokerEntry{处理函数, 帮助文本, 等级, Console} 或 SubCommandEntry{子表引用}以/teleport name npc id 123为例(表结构取自src/server/scripts/Commands/cs_tele.cpp),逐级下钻:分四步走:LoadCommandMap把所有脚本的GetCommands()拍平进一张大小写不敏感的顶层 map,构建一次、查询复用;需要热注册时另有InvalidateCommandMap强制下次查询重建。TryExecuteCommand按空格切出第一个词查表:节点挂子表就把剩余参数截下来再进一层,节点挂处理函数则停止下钻。命中处理函数后,CommandInvoker按函数签名类型调ConsumeFromOffset,ArgInfoT::TryConsume一次吃掉一个 token;整串参数必须被完全消费,多一个词都算失败。解析通过就调用处理函数,失败就把错误文案直接发给调用者。空字符串条目是该层级的裸命令,对应无子命令的形态;别名解析发生在节点查找阶段。权限等级与参数速查权限位收uint32 securityLevel,取值来自src/common/Common.h的SEC_*枚举;仓库内建命令也常传rbac::RBAC_PERM_*键,同一参数两种用法,框架按值分流:落入 RBAC 键域走HasPermission权限点判定,否则走账号等级IsAvailable比较。Console列独立于等级,只决定该命令能否出现在控制台并在那里执行。名称类型行为SEC_PLAYER等级 0普通玩家, 无 GM 命令SEC_MODERATOR等级 1审核类, 命令极少SEC_GAMEMASTER等级 2常规 GM 命令门槛SEC_ADMINISTRATOR等级 3危险操作门槛SEC_CONSOLE等级 4仅服务端控制台rbac::RBAC_PERM_*RBAC 键走权限点精确判定uint32数值整词转无符号整数float数值整词转浮点, 拒 inf/nanstd::string字符串取一个 token, 遇空格截断PlayerIdentifier标签类型名字或 GUID → 在线玩家GameTele const*标签类型按名查 game_tele 传送点枚举类型enumEnumUtils 名字↔值映射VariantA, B组合A 解析失败再试 BOptionalT组合允许该参数缺省从 Demo 到生产:四步增量改造四步全部只动cs_fullheal.cpp本身,框架侧零改动。第 1 步 加子命令。裸命令拆成自身目标两路,原处理函数挂条目保持兼容:static bool HandleFullHealSelfCommand(ChatHandler* handler) // 新增 { Player* player handler-GetSession()-GetPlayer(); if (!player) return false; player-SetHealth(player-GetMaxHealth()); return true; } // 新增: 原入口改挂子表 static ChatCommandTable fullHealCommandTable { { self, HandleFullHealSelfCommand, SEC_GAMEMASTER, Console::No }, { , HandleFullHealCommand, SEC_GAMEMASTER, Console::No } }; // commandTable 相应变为 { fullheal, fullHealCommandTable }第 2 步 加权限校验。目标账号等级可能高于执行者,动目标前必须先过一道HasLowerSecurity:if (handler-HasLowerSecurity(player, ObjectGuid::Empty)) // 新增 { handler-SendErrorMessage(LANG_COMMAND_SECURITY_LOWLEVEL); return false; }第 3 步 离线玩家。开篇那个痛点就卡在这:坑最密的一步。GetConnectedPlayer()对离线目标返回空,改成按 GUID 直接从库加载:static bool HandleFullHealOfflineCommand(ChatHandler* handler, uint32 guid) // 新增 { Player* player handler-GetSession()-LoadPlayer(guid); if (!player) { handler-SendErrorMessage(LANG_PLAYER_NOT_FOUND); return false; } player-SetHealth(player-GetMaxHealth()); player-Save(); // 新增: 离线改动必须落库 return true; } // 新增: 挂到 fullHealCommandTable { offline, HandleFullHealOfflineCommand, SEC_ADMINISTRATOR, Console::Yes }第 4 步 多语言报错。玩家可见文案一律走 Language 键,不内联裸串;新文案先在src/server/game/Miscellaneous/Language.h(工作区副本)登记键:handler-SendErrorMessage(LANG_PLAYER_NOT_FOUND); // 新增: 查无此号 handler-SendErrorMessage(LANG_COMMAND_SECURITY_LOWLEVEL); // 新增: 等级不足取词走GetAcoreString(handler, key),键查不到时框架有兜底文案;纯控制台报错可以不走这套。踩坑清单现象 → 根因 → 修法,全部对应前文链路:命令不出现 → 加载器没调 AddSC → 两文件各补声明与调用报用法错误 → 输入多余词未被完全消费 → 函数签名对齐参数个数GM 可用控制台不可用 → Console::No 挡掉 → 改 Console::Yes能操作更高等级账号 → 漏了 HasLowerSecurity → 操作前校验目标带空格的名字被截断 → string 只取一个 token → 换 PlayerIdentifier 标签编译期 static_assert → 签名不符 → 首参必须是 ChatHandler*命令跑通之后,值得继续挖的方向有三个:命令别名(节点查找阶段的别名解析)、GetAutoCompletionsFor的输入补全、以及处理函数内的调用审计日志。入口源码:src/server/game/Chat/ChatCommands/ChatCommand.h、src/server/scripts/Commands/cs_tele.cpp、src/server/scripts/Commands/cs_script_loader.cpp。【免费下载链接】azerothcore-wotlkComplete Open Source and Modular solution for MMO项目地址: https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进