Files
Viano-Core/tests/clientless/test_adventure_combat_preparation.py
T
Nico c0c799c4b6
GCC PCH / build (push) Canceled after 0s
clang PCH / build (push) Canceled after 0s
Add AdventureBot combat reactions, training and corpse recovery
2026-10-27 23:17:05 +01:00

57 lines
3.7 KiB
Python

"""Run the level-60 Arms plan through production Player::LearnTalent."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/game/Entities/Player/Player.cpp').read_text()
a=s.index('bool Player::LearnTalent(');b=s.index('\nvoid Player::LearnPetTalent',a)
# Native 4.3.4 Talent.dbc rows used by this plan; Impale depends on Deep Wounds 3/3.
rows=[(10134,0,[84570,84571,84572]),(11163,0,[84579,84580]),(8176,1,[12834,12849,12867]),(8190,1,[29834,29838]),
(10138,2,[56636,56637,56638]),(10741,2,[16493,16494]),(11418,3,[86655,12330]),(9662,3,[29836,29859]),
(11223,3,[85730]),(10520,4,[84583,84587,84588]),(8208,4,[64976]),(8214,4,[29723,29725]),(8194,5,[46867,56611])]
code=r'''
#include "AdventureCombatPreparationPolicy.h"
#include <cassert>
#include <cstdint>
#include <set>
#include <unordered_map>
using uint32=uint32_t;using uint8=uint8_t;using int8=int8_t;
#define TC_LOG_ERROR(...) ((void)0)
#define TC_LOG_DEBUG(...) ((void)0)
constexpr int MAX_TALENT_RANK=5,REQ_PRIMARY_TREE_TALENTS=31;
struct TalentEntry{uint32 ID=0,TabID=746,TierID=0,ColumnIndex=0,SpellRank[5]={},PrereqTalent[3]={},PrereqRank[3]={};};
struct TalentTabEntry{uint32 ClassMask=1;};
struct Store{std::unordered_map<uint32,TalentEntry> rows;TalentEntry const* LookupEntry(uint32 id){auto i=rows.find(id);return i==rows.end()?nullptr:&i->second;}uint32 GetNumRows(){return 12000;}}sTalentStore;
struct Tabs{TalentTabEntry tab;auto LookupEntry(uint32){return &tab;}}sTalentTabStore;
struct Player{
uint32 points=26,mask=1;std::set<uint32> spells;
uint32 GetFreeTalentPoints(){return points;}uint32 getClassMask(){return mask;}uint32 GetPrimaryTalentTree(int){return 746;}int GetActiveSpec(){return 0;}
bool HasSpell(uint32 spell){return spells.count(spell);}
void LearnSpell(uint32 spell,bool){for(auto const& [id,t]:sTalentStore.rows)for(auto rank:t.SpellRank)if(rank==spell){for(auto old:t.SpellRank)spells.erase(old);}spells.insert(spell);}
void AddTalent(uint32,int,bool){}void SetFreeTalentPoints(uint32 p){points=p;}
bool LearnTalent(uint32,uint32);
};
'''+s[a:b]+'\nint main(){\n'
for id,tier,spells in rows:
ranks=spells+[0]*(5-len(spells))
code+=f'sTalentStore.rows[{id}]={{{id},746,{tier},0,{{'+','.join(map(str,ranks))+'},{0,0,0},{0,0,0}};\n'
code+=r'''
sTalentStore.rows[10741].PrereqTalent[0]=8176;sTalentStore.rows[10741].PrereqRank[0]=2;
Player p;
assert(!p.LearnTalent(10741,0)); // cannot skip native dependency/tier gates
unsigned spent=AdventureCombatPreparationPolicy::SpendArms60([&](uint32 id,uint32 rank){return p.LearnTalent(id,rank);},[&]{return p.points;});
assert(spent==26&&p.points==0);
assert(p.HasSpell(84572)&&p.HasSpell(84580)&&p.HasSpell(12867)&&p.HasSpell(29838));
assert(p.HasSpell(56638)&&p.HasSpell(16494)&&p.HasSpell(46867));
assert(!p.HasSpell(46924)); // Bladestorm requires 31 points; level60 does not get it
assert(AdventureCombatPreparationPolicy::SpendArms60([&](uint32 id,uint32 rank){return p.LearnTalent(id,rank);},[&]{return p.points;})==0);
Player wrongClass;wrongClass.mask=128;
assert(AdventureCombatPreparationPolicy::SpendArms60([&](uint32 id,uint32 rank){return wrongClass.LearnTalent(id,rank);},[&]{return wrongClass.points;})==0);
assert(wrongClass.points==26&&wrongClass.spells.empty());
}
'''
with tempfile.TemporaryDirectory() as tmp:
src=Path(tmp)/'talents.cpp';exe=Path(tmp)/'talents';src.write_text(code)
subprocess.run(['g++','-std=c++17','-I'+str(root/'src/server/game/World'),str(src),'-o',str(exe)],check=True)
subprocess.run([str(exe)],check=True)
print('Production talent learner: native tiers/dependencies/class gates, all 26 legal level60 points, no capstone bypass and repeat safety passed.')