Files
metin2-client/tests/host/autohunt_pull_test.cpp
T

189 lines
8.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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, SECTREE_SIZE=6400;
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 for_each_entity(F& f) { for(auto* e:entities) f(e); }
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; }
DWORD GetPlayerID() { return vid; }
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 GetMobExpRate(LPCHARACTER) { return 200; }
int GetMobGoldAmountRate(LPCHARACTER) { return 300; }
int GetMobGoldDropRate(LPCHARACTER) { return 400; }
int GetMobItemRate(LPCHARACTER ch) { assert(ch); return 999; } // locale rules require a real character
int GetGlobalMobItemRate() { return 500; }
};
using LPSECTREE=Sectree*;
struct SectreeMap {
std::map<std::pair<int,int>, Sectree*> cells;
template<class F> void for_each_in_rect(int x0,int y0,int x1,int y1,F& f) {
for (const auto& cell:cells)
if (cell.first.first>=x0/SECTREE_SIZE && cell.first.first<=x1/SECTREE_SIZE &&
cell.first.second>=y0/SECTREE_SIZE && cell.first.second<=y1/SECTREE_SIZE)
cell.second->for_each_entity(f);
}
Sectree* Find(int x,int y) {
auto it=cells.find({x/SECTREE_SIZE,y/SECTREE_SIZE});
return it==cells.end() ? nullptr : it->second;
}
};
using LPSECTREE_MAP=SectreeMap*;
struct SECTREE_MANAGER {
SectreeMap map;
static SECTREE_MANAGER& instance() { static SECTREE_MANAGER m; return m; }
SectreeMap* GetMap(int) { return &map; }
};
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"
constexpr int PRIV_EXP_PCT=4, PRIV_ITEM_DROP=1, PRIV_GOLD_DROP=2, PRIV_GOLD10_DROP=3;
struct CPrivManager {
static CPrivManager& instance() { static CPrivManager pm; return pm; }
int GetPriv(LPCHARACTER ch, int type) { assert(ch); return type*10; }
};
#include "../../tools/server/autohunt-pull-range/server_bonuses.h"
int main() {
Sectree sec;
Character owner; owner.pc=true; owner.sec=&sec; owner.vid=1;
SECTREE_MANAGER::instance().map.cells[{0,0}]=&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 15001 0 12");
do_autohunt_pull(&owner,"3 5000 0 12 extra");
assert(owner.replies.empty());
// Large ranges visit distant sectors and extend only the selected owner's pursuit.
Sectree distant;
Character far; far.vid=1000; far.x=14000; far.sec=&distant;
distant.entities.push_back(&far);
SECTREE_MANAGER::instance().map.cells[{2,0}]=&distant;
CHARACTER_MANAGER::instance().mobs[far.vid]=&far;
sec.entities.clear(); now+=10;
do_autohunt_pull(&owner,"20 10000 0 13");
assert(owner.replies.back()=="AutoHuntGather 13 done 0" && !far.victim);
now+=10;
do_autohunt_pull(&owner,"20 15000 0 14");
assert(owner.replies.back()=="AutoHuntGather 14 done 1" && far.victim==&owner);
assert(AutoHuntPullLeash(&far,&owner)==15001);
assert(AutoHuntPullLeash(&mobs[49],&owner)==4000);
do_autohunt_pull(&owner,"0 15000 0 15");
assert(AutoHuntPullLeash(&far,&owner)==4000);
now+=10; do_autohunt_pull(&owner,"3 15000 0 16");
assert(AutoHuntPullLeash(&far,&other)==4000);
now+=10; do_autohunt_pull(&owner,"3 15000 0 17");
now+=180;
assert(AutoHuntPullLeash(&far,&owner)==4000);
// 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);
// The status command must use the global item rate, never the locale getter with NULL.
Character bonusOwner; bonusOwner.pc=true;
do_server_bonuses(&bonusOwner, "");
assert(bonusOwner.replies.back()=="ServerBonuses 200 300 400 500 40 10 20 30");
do_server_bonuses(&bonusOwner, "");
assert(bonusOwner.replies.size()==1);
now+=2;
do_server_bonuses(&bonusOwner, "");
assert(bonusOwner.replies.size()==2);
Character notPlayer;
do_server_bonuses(&notPlayer, "");
assert(notPlayer.replies.empty());
puts("autohunt_pull_test: PASS");
}