--- a/server/game/src/sectree_manager.h +++ b/server/game/src/sectree_manager.h @@ -88,6 +88,18 @@ } */ } + + // PORT: snapshot traversal for the complete auto-hunt radius. + template + void for_each_in_rect(DWORD minX, DWORD minY, DWORD maxX, DWORD maxY, Func& func) { + FCollectEntity collector; + for (DWORD x = minX / SECTREE_SIZE * SECTREE_SIZE; x <= maxX; x += SECTREE_SIZE) + for (DWORD y = minY / SECTREE_SIZE * SECTREE_SIZE; y <= maxY; y += SECTREE_SIZE) { + LPSECTREE tree = Find(x, y); + if (tree) tree->for_each_entity(collector); + } + collector.ForEach(func); + } void DumpAllToSysErr() { SECTREE_MAP::MapType::iterator i; --- a/server/game/src/autohunt_pull.h +++ b/server/game/src/autohunt_pull.h @@ -3,8 +3,37 @@ #include "group_select.h" #else #include "sectree_manager.h" +#include "autohunt_pull_range.h" #include "autohunt_group_select.h" #endif + +// PORT: only mobs pulled by this command receive a temporary extended pursuit leash. +struct AutoHuntPullLease { + LPCHARACTER mob; + DWORD ownerPID; + int radius; + int expires; +}; +static std::map s_autoHuntPullLeases; +int AutoHuntPullLeash(LPCHARACTER mob, LPCHARACTER victim) { + std::map::iterator it = s_autoHuntPullLeases.find((DWORD)mob->GetVID()); + if (it == s_autoHuntPullLeases.end()) return 4000; + const AutoHuntPullLease& lease = it->second; + if (lease.mob != mob || !victim || !victim->IsPC() || victim->IsDead() || + victim->GetPlayerID() != lease.ownerPID || mob->GetMapIndex() != victim->GetMapIndex() || + get_global_time() >= lease.expires) { + s_autoHuntPullLeases.erase(it); + return 4000; + } + return std::max(4000, lease.radius + 1); +} +static void AutoHuntPullPrune(DWORD cancelPID = 0) { + for (std::map::iterator it = s_autoHuntPullLeases.begin(); it != s_autoHuntPullLeases.end();) { + if (it->second.expires <= get_global_time() || (cancelPID && it->second.ownerPID == cancelPID)) + s_autoHuntPullLeases.erase(it++); + else ++it; + } +} struct AutoHuntPullCollector { LPCHARACTER owner; @@ -35,8 +64,11 @@ unsigned int request = 0; char extra = 0; if (sscanf(argument, "%d %d %d %u %c", &groups, &radius, &bosses, &request, &extra) != 4 || - (groups != 3 && groups != 10 && groups != 20) || radius < 500 || radius > 5000 || + (groups != 0 && groups != 3 && groups != 10 && groups != 20) || radius < 500 || radius > 15000 || (bosses != 0 && bosses != 1)) return; + if (!ch->IsPC()) return; + AutoHuntPullPrune(groups == 0 ? ch->GetPlayerID() : 0); + if (groups == 0) return; const int now = get_global_time(); if (!ch->IsPC() || ch->IsDead() || ch->IsStun() || !ch->CanHandleItem() || !ch->GetSectree() || (ch->GetSectree()->GetAttribute(ch->GetX(), ch->GetY()) & ATTR_BANPK) || @@ -47,13 +79,23 @@ // Server-side cooldown survives reconnects and switching auto hunt on/off. ch->SetQuestFlag("autohunt.next_pull", now + 10); AutoHuntPullCollector collector(ch, radius, bosses != 0); - ch->GetSectree()->ForEachAround(collector); + // ForEachAround only covers nine cells: visit every cell intersecting the requested radius. + LPSECTREE_MAP map = SECTREE_MANAGER::instance().GetMap(ch->GetMapIndex()); + if (map) map->for_each_in_rect(std::max(0, int(ch->GetX()) - radius), + std::max(0, int(ch->GetY()) - radius), ch->GetX() + radius, ch->GetY() + radius, collector); const mt_autohunt::Selection selected = mt_autohunt::SelectGroups(collector.candidates, groups); for (size_t i = 0; i < selected.mobs.size(); ++i) { LPCHARACTER mob = CHARACTER_MANAGER::instance().Find(selected.mobs[i]); if (!mob) continue; + if (mob->IsDead() || (mob->GetVictim() && mob->GetVictim() != ch)) continue; + AutoHuntPullLease lease = {mob, ch->GetPlayerID(), radius, now + 180}; + s_autoHuntPullLeases[(DWORD)mob->GetVID()] = lease; if (!mob->GetVictim()) mob->BeginFight(ch); ch->ChatPacket(CHAT_TYPE_COMMAND, "AutoHuntGather %u mob %u", request, selected.mobs[i]); } ch->ChatPacket(CHAT_TYPE_COMMAND, "AutoHuntGather %u done %d", request, selected.groups); } + +#ifndef MT_AUTOHUNT_SERVER_TEST +#include "server_bonuses.h" +#endif --- a/server/game/src/char_state.cpp +++ b/server/game/src/char_state.cpp @@ -3,6 +3,7 @@ #include "utils.h" #include "vector.h" #include "char.h" +#include "autohunt_pull_range.h" #include "battle.h" #include "char_manager.h" #include "packet.h" @@ -1035,7 +1036,7 @@ float fDist = DISTANCE_APPROX(GetX() - victim->GetX(), GetY() - victim->GetY()); - if (fDist >= 4000.0f) // 40미터 이상 멀어지면 포기 + if (fDist >= AutoHuntPullLeash(this, victim)) // 40미터 이상 멀어지면 포기 { MonsterLog("타겟이 멀어서 포기"); SetVictim(NULL); --- a/server/game/src/cmd.cpp +++ b/server/game/src/cmd.cpp @@ -7,6 +7,7 @@ #include "desc.h" ACMD(do_autohunt_pull); +ACMD(do_server_bonuses); ACMD(do_user_horse_ride); ACMD(do_user_horse_back); ACMD(do_user_horse_feed); @@ -281,6 +282,7 @@ { { "!RESERVED!", NULL, 0, POS_DEAD, GM_IMPLEMENTOR }, /* 반드시 이 것이 처음이어야 한다. */ { "autohunt_pull", do_autohunt_pull, 0, POS_STANDING, GM_PLAYER }, + { "server_bonuses", do_server_bonuses, 0, POS_DEAD, GM_PLAYER }, { "who", do_who, 0, POS_DEAD, GM_IMPLEMENTOR }, { "war", do_war, 0, POS_DEAD, GM_PLAYER }, { "warp", do_warp, 0, POS_DEAD, GM_LOW_WIZARD }, --- /dev/null +++ b/server/game/src/autohunt_pull_range.h @@ -0,0 +1,3 @@ +#pragma once +// PORT extension: ordinary monster pursuit remains 40 metres. +int AutoHuntPullLeash(LPCHARACTER mob, LPCHARACTER victim); --- /dev/null +++ b/server/game/src/server_bonuses.h @@ -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.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)); +} --- a/server/game/src/char.cpp +++ b/server/game/src/char.cpp @@ -4,6 +4,7 @@ #include "../../common/VnumHelper.h" #include "char.h" +#include "autohunt_pull_range.h" #include "config.h" #include "utils.h" @@ -5440,7 +5441,7 @@ if (get_dword_time() - m_pkMobInst->m_dwLastAttackedTime >= 15000) // 마지막으로 공격받은지 15초가 지났고 { // 마지막 맞은 곳으로 부터 50미터 이상 차이나면 포기하고 돌아간다. - if (5000 < DISTANCE_APPROX(m_pkMobInst->m_posLastAttacked.x - GetX(), m_pkMobInst->m_posLastAttacked.y - GetY())) + if (MAX(5000, AutoHuntPullLeash(this, pkChr)) < DISTANCE_APPROX(m_pkMobInst->m_posLastAttacked.x - GetX(), m_pkMobInst->m_posLastAttacked.y - GetY())) 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);