67 lines
4.9 KiB
Python
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 ⌖}
|
|
'''+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)')
|