Files
metin2-client/tools/server/autohunt-pull/server.patch
T

146 lines
5.6 KiB
Diff

--- 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<mt_autohunt::Candidate> 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 <algorithm>
+#include <vector>
+#include <map>
+#include <stdint.h>
+
+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<uint32_t> mobs;
+ Selection() : groups(0) {}
+};
+inline Selection SelectGroups(const std::vector<Candidate>& 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<uint32_t> mobs;
+ Group() : key(0), distance(2147483647), eligible(true) {}
+ };
+ std::map<uint64_t, Group> 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<std::pair<int, uint64_t> > ordered;
+ for (std::map<uint64_t, Group>::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