// PORT extension: free cloak-style auto-hunt pull, using the original BeginFight path. #ifdef MT_AUTOHUNT_SERVER_TEST #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; int radius; bool bosses; std::vector candidates; AutoHuntPullCollector(LPCHARACTER ch, int range, bool boss) : owner(ch), radius(range), bosses(boss) {} void operator()(LPENTITY ent) { if (!ent->IsType(ENTITY_CHARACTER)) return; LPCHARACTER mob = (LPCHARACTER)ent; if (!mob->IsMonster() || mob->IsPC() || mob->IsDead() || mob->GetMapIndex() != owner->GetMapIndex()) return; const int distance = DISTANCE_APPROX(mob->GetX() - owner->GetX(), mob->GetY() - owner->GetY()); if (distance > radius) return; const bool safe = !mob->GetSectree() || (mob->GetSectree()->GetAttribute(mob->GetX(), mob->GetY()) & ATTR_BANPK); mt_autohunt::Candidate c; c.group = mob->GetParty() ? uint64_t(uintptr_t(mob->GetParty())) << 1 : (uint64_t((DWORD)mob->GetVID()) << 1) | 1; c.vid = (DWORD)mob->GetVID(); c.distance = distance; c.eligible = !safe && (bosses || mob->GetMobRank() < MOB_RANK_BOSS) && (!mob->GetVictim() || mob->GetVictim() == owner) && (mob->GetVictim() == owner || mob->CanBeginFight()); candidates.push_back(c); } }; ACMD(do_autohunt_pull) { int groups = 0, radius = 0, bosses = 0; unsigned int request = 0; char extra = 0; if (sscanf(argument, "%d %d %d %u %c", &groups, &radius, &bosses, &request, &extra) != 4 || (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) || ch->GetHP() < ch->GetMaxHP() / 2 || now < ch->GetQuestFlag("autohunt.next_pull")) { ch->ChatPacket(CHAT_TYPE_COMMAND, "AutoHuntGather %u done 0", request); return; } // Server-side cooldown survives reconnects and switching auto hunt on/off. ch->SetQuestFlag("autohunt.next_pull", now + 10); AutoHuntPullCollector collector(ch, radius, bosses != 0); // 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