--- a/server/game/src/cmd.cpp +++ b/server/game/src/cmd.cpp @@ -6,6 +6,7 @@ #include "log.h" #include "desc.h" +ACMD(do_autohunt_pull); ACMD(do_user_horse_ride); ACMD(do_user_horse_back); ACMD(do_user_horse_feed); @@ -279,6 +280,7 @@ struct command_info cmd_info[] = { { "!RESERVED!", NULL, 0, POS_DEAD, GM_IMPLEMENTOR }, /* 반드시 이 것이 처음이어야 한다. */ + { "autohunt_pull", do_autohunt_pull, 0, POS_STANDING, 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 }, --- a/server/game/src/cmd_general.cpp +++ b/server/game/src/cmd_general.cpp @@ -37,6 +37,8 @@ #ifdef __AUCTION__ #include "auction_manager.h" #endif + +#include "autohunt_pull.h" extern int g_server_id; --- /dev/null +++ b/server/game/src/autohunt_pull.h @@ -0,0 +1,59 @@ +// 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_group_select.h" +#endif + +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 != 3 && groups != 10 && groups != 20) || radius < 500 || radius > 5000 || + (bosses != 0 && bosses != 1)) 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); + ch->GetSectree()->ForEachAround(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->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); +} --- /dev/null +++ b/server/game/src/autohunt_group_select.h @@ -0,0 +1,51 @@ +#ifndef MT_AUTOHUNT_GROUP_SELECT_H +#define MT_AUTOHUNT_GROUP_SELECT_H +#include +#include +#include +#include + +namespace mt_autohunt { +struct Candidate { + uint64_t group; // actual server party, or a unique key for an ungrouped monster + uint32_t vid; + int distance; + bool eligible; +}; +struct Selection { + int groups; + std::vector mobs; + Selection() : groups(0) {} +}; +inline Selection SelectGroups(const std::vector& candidates, int count) { + Selection out; + if (count != 3 && count != 10 && count != 20) return out; + struct Group { + uint64_t key; + int distance; + bool eligible; + std::vector mobs; + Group() : key(0), distance(2147483647), eligible(true) {} + }; + std::map groups; + for (size_t i = 0; i < candidates.size(); ++i) { + const Candidate& c = candidates[i]; + Group& g = groups[c.group]; + g.key = c.group; + g.distance = std::min(g.distance, c.distance); + g.eligible = g.eligible && c.eligible; + g.mobs.push_back(c.vid); + } + std::vector > ordered; + for (std::map::const_iterator i = groups.begin(); i != groups.end(); ++i) + if (i->second.eligible) ordered.push_back(std::make_pair(i->second.distance, i->first)); + std::sort(ordered.begin(), ordered.end()); + for (size_t i = 0; i < ordered.size() && out.groups < count; ++i) { + const Group& g = groups[ordered[i].second]; + out.mobs.insert(out.mobs.end(), g.mobs.begin(), g.mobs.end()); + ++out.groups; + } + return out; +} +} +#endif