Add free cloak-style auto-hunt pull for 3 10 or 20 monster groups

This commit is contained in:
shen
2026-09-30 17:07:24 -07:00
parent d1f8d3dddd
commit 8a6aeaedf0
18 changed files with 558 additions and 96 deletions
+120
View File
@@ -0,0 +1,120 @@
// Compile and execute the shipped server command against a small server API fixture.
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include <cmath>
#include <cstdint>
using DWORD = uint32_t;
constexpr int ENTITY_CHARACTER=1, ATTR_BANPK=4, MOB_RANK_BOSS=4, CHAT_TYPE_COMMAND=5;
struct Character;
using LPCHARACTER=Character*;
struct Entity { virtual ~Entity() {} bool IsType(int) { return true; } };
using LPENTITY=Entity*;
struct Party {};
struct Sectree {
bool safe=false;
std::vector<Entity*> entities;
DWORD GetAttribute(long,long) { return safe ? ATTR_BANPK : 0; }
template<class F> void ForEachAround(F& f) { for(auto* e:entities) f(e); }
};
struct Character: Entity {
DWORD vid=0;
int x=0,y=0,rank=0,hp=100,maxhp=100;
bool pc=false,dead=false,stun=false,canHandle=true;
Sectree* sec=nullptr;
Party* party=nullptr;
Character* victim=nullptr;
int next=0, beginCount=0;
std::vector<std::string> replies;
bool IsMonster() { return !pc; }
bool IsPC() { return pc; }
bool IsDead() { return dead; }
bool IsStun() { return stun; }
bool CanHandleItem() { return canHandle; }
bool CanBeginFight() { return !victim && !dead && !stun; }
int GetHP() { return hp; }
int GetMaxHP() { return maxhp; }
int GetX() { return x; }
int GetY() { return y; }
int GetMapIndex() { return 1; }
int GetMobRank() { return rank; }
Sectree* GetSectree() { return sec; }
Party* GetParty() { return party; }
DWORD GetVID() { return vid; }
Character* GetVictim() { return victim; }
int GetQuestFlag(const char*) { return next; }
void SetQuestFlag(const char*,int value) { next=value; }
void BeginFight(Character* owner) { victim=owner; ++beginCount; }
void ChatPacket(int,const char* format,...) {
char buf[256]; va_list ap; va_start(ap,format); vsnprintf(buf,sizeof(buf),format,ap); va_end(ap);
replies.emplace_back(buf);
}
};
struct CHARACTER_MANAGER {
std::map<DWORD,Character*> mobs;
static CHARACTER_MANAGER& instance() { static CHARACTER_MANAGER value; return value; }
Character* Find(DWORD vid) { return mobs[vid]; }
};
int now=100;
int get_global_time() { return now; }
#define DISTANCE_APPROX(x,y) int(std::hypot(x,y))
#define ACMD(name) void name(LPCHARACTER ch,const char* argument)
#define MT_AUTOHUNT_SERVER_TEST
#include "../../tools/server/autohunt-pull/autohunt_pull.h"
int main() {
Sectree sec;
Character owner; owner.pc=true; owner.sec=&sec;
Character other; other.pc=true;
std::vector<Party> parties(25);
std::vector<Character> mobs(50);
for(size_t i=0;i<mobs.size();++i) {
auto& mob=mobs[i]; mob.vid=100+i; mob.x=100+int(i/2)*100; mob.sec=&sec; mob.party=&parties[i/2];
sec.entities.push_back(&mob); CHARACTER_MANAGER::instance().mobs[mob.vid]=&mob;
}
// A group owned by another player and a boss group are both excluded as whole groups.
mobs[0].victim=&other;
mobs[2].rank=MOB_RANK_BOSS;
do_autohunt_pull(&owner,"3 5000 0 7");
assert(owner.replies.back()=="AutoHuntGather 7 done 3");
assert(owner.replies.size()==7); // 3 actual parties × 2 members, then completion
for(int i=4;i<10;++i) assert(mobs[i].victim==&owner && mobs[i].beginCount==1);
assert(!mobs[1].victim && !mobs[3].victim && !mobs[10].victim);
owner.replies.clear();
do_autohunt_pull(&owner,"20 5000 1 8");
assert(owner.replies.size()==1 && owner.replies.back()=="AutoHuntGather 8 done 0"); // cooldown
now+=10;
owner.replies.clear();
do_autohunt_pull(&owner,"20 5000 1 9");
assert(owner.replies.back()=="AutoHuntGather 9 done 20");
assert(owner.replies.size()==41);
assert(mobs[4].beginCount==1); // already on this player; no redundant restart
now+=10; owner.replies.clear(); owner.hp=49;
do_autohunt_pull(&owner,"10 5000 1 10");
assert(owner.replies.back()=="AutoHuntGather 10 done 0");
owner.hp=100; sec.safe=true; owner.replies.clear();
do_autohunt_pull(&owner,"10 5000 1 11");
assert(owner.replies.back()=="AutoHuntGather 11 done 0");
sec.safe=false; owner.replies.clear();
do_autohunt_pull(&owner,"21 5000 0 12");
do_autohunt_pull(&owner,"3 5001 0 12");
do_autohunt_pull(&owner,"3 5000 0 12 extra");
assert(owner.replies.empty());
// Unequal group sizes, exact 10-group cap and nearest ordering independent of input order.
std::vector<mt_autohunt::Candidate> candidates;
for(int g=24;g>=0;--g)
for(int m=0;m<g%4+1;++m) candidates.push_back({uint64_t(g),uint32_t(g*10+m),g*100,true});
auto ten=mt_autohunt::SelectGroups(candidates,10);
assert(ten.groups==10 && ten.mobs.front()==0);
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);
puts("autohunt_pull_test: PASS");
}