diff --git a/tests/host/autohunt_pull_test.cpp b/tests/host/autohunt_pull_test.cpp index e107f2f7..e867cdf8 100644 --- a/tests/host/autohunt_pull_test.cpp +++ b/tests/host/autohunt_pull_test.cpp @@ -63,6 +63,12 @@ struct CHARACTER_MANAGER { std::map mobs; static CHARACTER_MANAGER& instance() { static CHARACTER_MANAGER value; return value; } Character* Find(DWORD vid) { return mobs[vid]; } + int GetMobExpRate(LPCHARACTER) { return 200; } + int GetMobGoldAmountRate(LPCHARACTER) { return 300; } + int GetMobGoldDropRate(LPCHARACTER) { return 400; } + int GetMobItemRate(LPCHARACTER ch) { assert(ch); return 999; } // locale rules require a real character + int GetGlobalMobItemRate() { return 500; } + }; using LPSECTREE=Sectree*; struct SectreeMap { @@ -90,6 +96,12 @@ int get_global_time() { return now; } #define ACMD(name) void name(LPCHARACTER ch,const char* argument) #define MT_AUTOHUNT_SERVER_TEST #include "../../tools/server/autohunt-pull/autohunt_pull.h" +constexpr int PRIV_EXP_PCT=4, PRIV_ITEM_DROP=1, PRIV_GOLD_DROP=2, PRIV_GOLD10_DROP=3; +struct CPrivManager { + static CPrivManager& instance() { static CPrivManager pm; return pm; } + int GetPriv(LPCHARACTER ch, int type) { assert(ch); return type*10; } +}; +#include "../../tools/server/autohunt-pull-range/server_bonuses.h" int main() { Sectree sec; @@ -160,5 +172,17 @@ int main() { for(auto vid:ten.mobs) assert(vid/10 < 10); auto sparse=mt_autohunt::SelectGroups({{1,1,100,true},{1,2,110,true},{2,3,50,true}},20); assert(sparse.groups==2 && sparse.mobs.size()==3 && sparse.mobs[0]==3); + // The status command must use the global item rate, never the locale getter with NULL. + Character bonusOwner; bonusOwner.pc=true; + do_server_bonuses(&bonusOwner, ""); + assert(bonusOwner.replies.back()=="ServerBonuses 200 300 400 500 40 10 20 30"); + do_server_bonuses(&bonusOwner, ""); + assert(bonusOwner.replies.size()==1); + now+=2; + do_server_bonuses(&bonusOwner, ""); + assert(bonusOwner.replies.size()==2); + Character notPlayer; + do_server_bonuses(¬Player, ""); + assert(notPlayer.replies.empty()); puts("autohunt_pull_test: PASS"); } diff --git a/tools/server/autohunt-pull-range/README.md b/tools/server/autohunt-pull-range/README.md index 5704abcd..2db39881 100644 --- a/tools/server/autohunt-pull-range/README.md +++ b/tools/server/autohunt-pull-range/README.md @@ -29,3 +29,17 @@ Android 点击图标可看到提示,macOS 悬停显示;FPS 移到顶部中 `port.login_flow` 覆盖真实 Python 配置和聊天封包链、石头优先与回退、加成图标新增/更新/移除; 桌面及 `MT_TEST_MOBILE=1` 均通过。Mi 8 Lite 真机在模拟服务器场景显示图标和“服务器经验 200%”提示。 macOS app 与 Android Release native/debug APK 已构建;APK 已安装到 63c36adb。 + +## 加成查询崩溃修正 + +首次部署后,真实角色执行 `server_bonuses` 导致地图进程 SIGSEGV: +`GetMobItemRate(NULL)` 在当前地区规则中调用 `CHARACTER::IsOverTime` 并解引用空角色。 +最初的模拟服务器显示测试没有执行真实服务端的地区分支,因此未覆盖这一故障。 + +修正增加 `CHARACTER_MANAGER::GetGlobalMobItemRate()`,直接读取全局倍率; +奖励结算使用的角色倍率接口及地区规则保持不变。其余三个倍率接口原本就支持空角色。 +`host.autohunt_pull` 现在直接执行实际的 `server_bonuses.h` 命令,模拟地区接口拒绝空角色, +检查全局物品倍率、四项活动加成、非玩家过滤和查询冷却。 + +故障期间已回滚到 `/root/backup-bin/20261001-075226/` 恢复服务,崩溃栈和 core 保存在 +`/root/autohunt-range-20260930/`;修复构建目录为 `/root/rebuild/20261001-080615/`。 diff --git a/tools/server/autohunt-pull-range/server.patch b/tools/server/autohunt-pull-range/server.patch index c23b16b2..34b6b2eb 100644 --- a/tools/server/autohunt-pull-range/server.patch +++ b/tools/server/autohunt-pull-range/server.patch @@ -142,16 +142,18 @@ +int AutoHuntPullLeash(LPCHARACTER mob, LPCHARACTER victim); --- /dev/null +++ b/server/game/src/server_bonuses.h -@@ -0,0 +1,12 @@ +@@ -0,0 +1,14 @@ +// PORT: report actual global rates and the character's effective event privileges. ++#ifndef MT_AUTOHUNT_SERVER_TEST +#include "priv_manager.h" ++#endif +ACMD(do_server_bonuses) { + if (!ch->IsPC() || get_global_time() < ch->GetQuestFlag("server_bonuses.next_query")) return; + ch->SetQuestFlag("server_bonuses.next_query", get_global_time() + 2); + CHARACTER_MANAGER& cm = CHARACTER_MANAGER::instance(); + CPrivManager& pm = CPrivManager::instance(); + ch->ChatPacket(CHAT_TYPE_COMMAND, "ServerBonuses %d %d %d %d %d %d %d %d", -+ cm.GetMobExpRate(NULL), cm.GetMobGoldAmountRate(NULL), cm.GetMobGoldDropRate(NULL), cm.GetMobItemRate(NULL), ++ cm.GetMobExpRate(NULL), cm.GetMobGoldAmountRate(NULL), cm.GetMobGoldDropRate(NULL), cm.GetGlobalMobItemRate(), + pm.GetPriv(ch, PRIV_EXP_PCT), pm.GetPriv(ch, PRIV_ITEM_DROP), + pm.GetPriv(ch, PRIV_GOLD_DROP), pm.GetPriv(ch, PRIV_GOLD10_DROP)); +} @@ -174,3 +176,14 @@ if (Return()) return true; } +--- a/server/game/src/char_manager.h ++++ b/server/game/src/char_manager.h +@@ -92,6 +92,8 @@ + void SetUserDamageRatePremium(int value) { m_iUserDamageRatePremium = value; } + void SetUserDamageRate(int value ) { m_iUserDamageRate = value; } + int GetMobItemRate(LPCHARACTER ch); ++ // PORT: query the global rate without dereferencing a character in locale-specific rules. ++ int GetGlobalMobItemRate() const { return m_iMobItemRate; } + int GetMobDamageRate(LPCHARACTER ch); + int GetMobGoldAmountRate(LPCHARACTER ch); + int GetMobGoldDropRate(LPCHARACTER ch); diff --git a/tools/server/autohunt-pull-range/server_bonuses.h b/tools/server/autohunt-pull-range/server_bonuses.h index ccda33b6..6a7cc72d 100644 --- a/tools/server/autohunt-pull-range/server_bonuses.h +++ b/tools/server/autohunt-pull-range/server_bonuses.h @@ -1,12 +1,14 @@ // PORT: report actual global rates and the character's effective event privileges. +#ifndef MT_AUTOHUNT_SERVER_TEST #include "priv_manager.h" +#endif ACMD(do_server_bonuses) { if (!ch->IsPC() || get_global_time() < ch->GetQuestFlag("server_bonuses.next_query")) return; ch->SetQuestFlag("server_bonuses.next_query", get_global_time() + 2); CHARACTER_MANAGER& cm = CHARACTER_MANAGER::instance(); CPrivManager& pm = CPrivManager::instance(); ch->ChatPacket(CHAT_TYPE_COMMAND, "ServerBonuses %d %d %d %d %d %d %d %d", - cm.GetMobExpRate(NULL), cm.GetMobGoldAmountRate(NULL), cm.GetMobGoldDropRate(NULL), cm.GetMobItemRate(NULL), + cm.GetMobExpRate(NULL), cm.GetMobGoldAmountRate(NULL), cm.GetMobGoldDropRate(NULL), cm.GetGlobalMobItemRate(), pm.GetPriv(ch, PRIV_EXP_PCT), pm.GetPriv(ch, PRIV_ITEM_DROP), pm.GetPriv(ch, PRIV_GOLD_DROP), pm.GetPriv(ch, PRIV_GOLD10_DROP)); }