Files
Viano-Core/tests/clientless/test_alchemist_stone_proc.py
T

67 lines
4.9 KiB
Python

"""Execute production potion proc handlers and native event metadata fallback."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/scripts/Spells/spell_generic.cpp').read_text();s=s[s.index('class spell_gen_alchemist_stone :'):]
def method(src,start):
a=src.index(start);o=src.index('{',a);depth=1;b=o+1
while depth:
depth+=(src[b]=='{')-(src[b]=='}');b+=1
return src[a:b]
unit=(root/'src/server/game/Entities/Unit/Unit.cpp').read_text()
code=r'''
#include <cassert>
#include <cstdint>
#include <vector>
using uint32=uint32_t;using int32=int32_t;
constexpr int SPELLFAMILY_POTION=13,SPELL_EFFECT_HEAL=10,SPELL_EFFECT_ENERGIZE=30;
constexpr unsigned ALCHEMIST_STONE_HEAL=21399,ALCHEMIST_STONE_MANA=21400;
struct SpellInfo{int SpellFamilyName=SPELLFAMILY_POTION,effect=SPELL_EFFECT_HEAL;bool HasEffect(int v)const{return effect==v;}};
struct DamageInfo{uint32 amount;SpellInfo const*info;uint32 GetDamage()const{return amount;}SpellInfo const*GetSpellInfo()const{return info;}};
struct HealInfo{uint32 heal,effective;SpellInfo const*info;uint32 GetHeal()const{return heal;}SpellInfo const*GetSpellInfo()const{return info;}};
struct Spell{SpellInfo const*info;SpellInfo const*GetSpellInfo()const{return info;}};
struct ProcEventInfo{
Spell*_spell=nullptr;DamageInfo*_damageInfo=nullptr;HealInfo*_healInfo=nullptr;
SpellInfo const*GetSpellInfo()const;
DamageInfo*GetDamageInfo(){return _damageInfo;}HealInfo*GetHealInfo(){return _healInfo;}
};
'''+method(unit,'SpellInfo const* ProcEventInfo::GetSpellInfo() const')+r'''
struct AuraEffect{};
struct CastSpellExtraArgs{AuraEffect const*aura;int32 bp=0;CastSpellExtraArgs(AuraEffect const*a):aura(a){}CastSpellExtraArgs&AddSpellBP0(int32 n){bp=n;return *this;}};
struct Target{struct Cast{unsigned spell;int32 bp;AuraEffect const*aura;};std::vector<Cast>casts;void CastSpell(Target*t,unsigned id,CastSpellExtraArgs args){assert(t==this);casts.push_back({id,args.bp,args.aura});}};
struct Script{Target target;unsigned suppressed=0;void PreventDefaultAction(){++suppressed;}Target*GetTarget(){return &target;}
'''+method(s,'bool CheckProc(')+method(s,'void HandleProc(')+r'''
};
int main(){
Script script;AuraEffect aura;SpellInfo healing;HealInfo heal{1000,200,&healing};
ProcEventInfo event;event._healInfo=&heal;
// Exact crash shape: a healing potion has no DamageInfo.
assert(!event.GetDamageInfo()&&script.CheckProc(event));script.HandleProc(&aura,event);
assert(script.target.casts.size()==1&&script.target.casts.back().spell==21399&&script.target.casts.back().bp==400&&script.target.casts.back().aura==&aura);
assert(script.suppressed==1); // preserve native default-action suppression
// Spell metadata can come from the cast even without damage metadata.
Spell cast{&healing};event._spell=&cast;heal.heal=7;script.HandleProc(&aura,event);assert(script.target.casts.back().bp==2);
// Actual saved crash: Drakzug, potion 43185, raw heal 3728, DamageInfo=null.
heal.heal=3728;script.HandleProc(&aura,event);assert(script.target.casts.back().bp==1491);
heal.heal=0;auto count=script.target.casts.size();script.HandleProc(&aura,event);assert(script.target.casts.size()==count);
// Existing energize damage payload is preserved, including metadata fallback.
SpellInfo mana{SPELLFAMILY_POTION,SPELL_EFFECT_ENERGIZE};DamageInfo damage{500,&mana};
event={};event._damageInfo=&damage;assert(script.CheckProc(event));script.HandleProc(&aura,event);assert(script.target.casts.back().spell==21400&&script.target.casts.back().bp==200);
count=script.target.casts.size();damage.amount=0;script.HandleProc(&aura,event);assert(script.target.casts.size()==count);
// Missing payloads and missing metadata never dereference null or invent a bonus.
event={};assert(!script.CheckProc(event));script.HandleProc(&aura,event);
cast.info=&mana;event._spell=&cast;assert(script.CheckProc(event));script.HandleProc(&aura,event);
damage={500,nullptr};event={};event._damageInfo=&damage;assert(!script.CheckProc(event));script.HandleProc(&aura,event);
cast.info=&healing;event._spell=&cast;assert(script.CheckProc(event));script.HandleProc(&aura,event); // no HealInfo
assert(script.target.casts.size()==count);
// Generic triggered bonuses and non-potion heals cannot recurse into the stone.
SpellInfo other{0,SPELL_EFFECT_HEAL};heal={1000,1000,&other};event={};event._healInfo=&heal;
assert(!script.CheckProc(event));script.HandleProc(&aura,event);assert(script.target.casts.size()==count);
}
'''
with tempfile.TemporaryDirectory() as d:
p=Path(d);(p/'test.cpp').write_text(code)
subprocess.run(['c++','-std=c++17','-fsanitize=address,undefined','-fno-omit-frame-pointer','-g',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
subprocess.run([str(p/'test')],check=True)
print('Alchemist Stone production proc: heal-only crash, metadata fallback, 40% raw heal/rounding, existing mana payload, missing/zero data and non-potion recursion guards PASS (ASan/UBSan)')