Allow destination micro activities and coordinate group departures while prioritizing combat assistance and shared leader routes. Remove artificial world-group capacity limits, improve PvP coordination and resurrection, fix parent-zone graveyard lookup and cross-map corpse cleanup, and add hourly death recovery. Include regression tests, documented live validation and repeatable test scenarios.
68 lines
4.5 KiB
Python
68 lines
4.5 KiB
Python
"""Exercise production catalog expansion with the testserver's Cataclysm DBC records."""
|
|
from pathlib import Path
|
|
import struct,subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
source=(root/'src/server/game/World/AdventureLocationMgr.cpp').read_text()
|
|
def function(sig):
|
|
a=source.index(sig);b=source.index('{',a);e=b+1;n=1
|
|
while n:n+=(source[e]=='{')-(source[e]=='}');e+=1
|
|
return source[a:e]
|
|
def dbc(name):
|
|
b=(Path('/home/wow_admin/wow_cata_server/data/dbc/enUS')/name).read_bytes()
|
|
magic,count,fields,size,strings=struct.unpack_from('<4s4I',b);assert magic==b'WDBC'
|
|
return [struct.unpack_from('<'+'I'*fields,b,20+n*size)for n in range(count)]
|
|
maps={33,34,36,469,533,671}
|
|
lfg=[r for r in dbc('LFGDungeons.dbc')if r[7]in maps]
|
|
modes=[r for r in dbc('MapDifficulty.dbc')if r[1]in maps]
|
|
header=(root/'src/server/game/World/AdventureLocationMgr.h').read_text();a=header.index('struct AdventureLocation');b=header.index('struct AdventureMicroAction',a)
|
|
code=r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
using uint8=unsigned char;using uint32=unsigned;using uint64=unsigned long long;using Difficulty=unsigned;
|
|
constexpr unsigned RAID_DIFFICULTY_25MAN_NORMAL=1;
|
|
struct ClientlessWorldPoint{std::string Id;};
|
|
'''+header[a:b]+r'''
|
|
struct MapEntry{unsigned ID;bool raid;bool IsRaid(){return raid;}bool IsDungeon(){return true;}};
|
|
struct Maps{std::unordered_map<unsigned,MapEntry>entries;MapEntry*LookupEntry(unsigned id){return entries.count(id)?&entries.at(id):nullptr;}}sMapStore;
|
|
struct MapDifficulty{unsigned maxPlayers;};struct DBC{std::unordered_map<uint64,MapDifficulty>entries;MapDifficulty*GetMapDifficultyData(unsigned id,Difficulty mode){auto key=(uint64(id)<<8)|mode;return entries.count(key)?&entries.at(key):nullptr;}}sDBCManager;
|
|
struct LFGEntry{unsigned MapID,DifficultyID,MinLevel,Maxlevel;};std::vector<LFGEntry*>sLFGDungeonStore;
|
|
struct Access{unsigned levelMin,levelMax;};struct Objects{Access*GetAccessRequirement(unsigned,Difficulty){return nullptr;}}objects;auto sObjectMgr=&objects;
|
|
struct AdventureLocationMgr{struct Band{unsigned Min=0,Max=0;};std::unordered_map<uint64,Band>_instanceBands;std::vector<AdventureLocation>_locations;void BuildInstanceVariants();
|
|
AdventureLocation const*Find(std::string const&key){for(auto const&l:_locations)if(l.Point.Id==key)return &l;return nullptr;}
|
|
void LoadBands(){
|
|
'''
|
|
a=source.index(' _instanceBands.clear();');b=source.index(' if (! WorldDatabase.Query',a)
|
|
code+=source[a:b]+'}\n};\n'+function('void AdventureLocationMgr::BuildInstanceVariants(')+'\nint main(){\n'
|
|
for id in maps:code+=f'sMapStore.entries[{id}]={{{id},{str(id in {469,533,671}).lower()}}};\n'
|
|
for r in modes:code+=f'sDBCManager.entries[({r[1]}ull<<8)|{r[2]}]={{{r[5]}}};\n'
|
|
for r in lfg:code+=f'sLFGDungeonStore.push_back(new LFGEntry{{{r[7]},{r[8]},{r[2]},{r[3]}}});\n'
|
|
code+='AdventureLocationMgr mgr;mgr.LoadBands();\n'
|
|
for id in maps:code+=f'{{AdventureLocation l;l.Point.Id="map{id}";l.Name="Map{id}";l.InstanceMap={id};mgr._locations.push_back(l);}}\n'
|
|
code+=r'''
|
|
mgr.BuildInstanceVariants();
|
|
for(auto id:{33,36}){
|
|
auto normal=mgr.Find("map"+std::to_string(id)),heroic=mgr.Find("map"+std::to_string(id)+"_d1");
|
|
assert(normal&&heroic&&normal->InstanceDifficulty==0&&heroic->InstanceDifficulty==1);
|
|
assert(normal->MinLevel<30&&normal->MaxLevel<30); // excludes obsolete84..85 normal SFK row
|
|
assert(heroic->MinLevel==85&&heroic->MaxLevel==85&&heroic->InstanceCapacity==5);
|
|
assert(heroic->BaseKey==normal->Point.Id&&heroic->Name.find("heroisch")!=std::string::npos);
|
|
}
|
|
assert(mgr.Find("map34")->MaxLevel==30&&!mgr.Find("map34_d1"));
|
|
auto classic=mgr.Find("map469");assert(classic->InstanceCapacity==40&&classic->InstanceDifficulty==0&&classic->MinLevel==60);
|
|
assert(!mgr.Find("map469_d1")&&!mgr.Find("map469_d2")&&!mgr.Find("map469_d3")); // native zero-capacity placeholders
|
|
assert(mgr.Find("map533")->InstanceCapacity==25&&mgr.Find("map533_d0")->InstanceCapacity==10);
|
|
for(unsigned mode=0;mode<4;++mode){auto key=mode==1?"map671":"map671_d"+std::to_string(mode);auto l=mgr.Find(key);
|
|
assert(l&&l->InstanceDifficulty==mode&&l->MinLevel==85&&l->InstanceCapacity==(mode%2?25:10));
|
|
assert(l->BaseKey=="map671");}
|
|
for(auto entry:sLFGDungeonStore)delete entry;
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory()as d:
|
|
p=Path(d);(p/'test.cpp').write_text(code)
|
|
subprocess.run(['c++','-std=c++17',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
|
|
subprocess.run([str(p/'test')],check=True)
|
|
print('Real Cataclysm DBC: dungeon normal/heroic, obsolete SFK row, classic raid placeholders, native10/25 normal/heroic and physical entrance reuse passed.')
|