Fix Alchemist Stone procs for healing potion events

This commit is contained in:
Nico committed 2026-11-05 10:29:35 +01:00
1 parent aba5aeba2c
commit 2403edcf18
3 files changed
+133 -10

No files matched your search

@@ -0,0 +1,38 @@
# Alchemistenstein: Heilungsereignis ohne DamageInfo
Der gesicherte Core worldserver-20261105-002730.core zeigt SIGSEGV in
DamageInfo::GetSpellInfo(this=nullptr), aufgerufen aus dem CheckProc des
Alchemistensteins in spell_generic.cpp. Akteur: Drakzug; Trankzauber 43185;
HealInfo enthält 3728 Heilung, DamageInfo ist null. Der native Zauberpfad
übergibt bei reinen Heiltreffern absichtlich HealInfo ohne DamageInfo.
CheckProc verwendet jetzt die vorhandene native Ereignisabfrage
ProcEventInfo::GetSpellInfo und prüft fehlende Metadaten. HandleProc prüft diese
Daten ebenfalls und verwendet für Heiltränke HealInfo::GetHeal. So bleiben
40 Prozent der ursprünglichen Heilung einschließlich Überheilung als
zusätzliche Heilung erhalten (3728 -> 1491 nach bisheriger Rundung).
Der bisherige Energize-Zweig verwendet weiterhin einen vorhandenen
DamageInfo-Betrag. Fehlende oder leere Beträge erzeugen keinen Bonuszauber.
Eine allgemeine Neuerfassung von Manawiederherstellungsbeträgen in der Engine
ist nicht Teil dieses Crashfixes. Nicht-Trank-Ereignisse erzeugen keinen Bonus.
Der Regressionstest test_alchemist_stone_proc führt beide Produktionshandler
sowie die native Metadatenabfrage mit ASan und UBSan aus: der konkrete Dumpfall,
HealInfo ohne DamageInfo, ursprüngliche statt effektive Heilung, Rundung,
bisherige Mana-Payloads, fehlende Metadaten und Payloads, Nullbeträge und
Nicht-Trank-Ereignisse. Test und Build mit -j10 bestanden.
Der Server war bei Buildbeginn bereits beendet; Loaderkonfiguration wurde auf
0 gesetzt, es gab keine laufenden Bots für einen Logout. Vor Installation und
Start wurden Prozess- und tmux-Abwesenheit geprüft. Weitere Ergebnisse in
SESSION-RESUME.md und checkpoints/2026-11-05-alchemist-stone/.
Das ursprüngliche passende Crashbinary liegt zur weiteren Diagnose unter
checkpoints/2026-11-05-edwin-crash/worldserver-crashed; Core und Backtrace bleiben
im Crashverzeichnis erhalten. Keine Dumps wurden gelöscht.
Installation und Neustart erfolgreich: PID 4477, frisches Ready und Ports
8085/8087 geprüft. Regulärer Loader aktiv, 118 Bots online, kein ausstehender
sicherer Logout. Ein zusätzlicher Ingame-Heiltranktest wurde nicht durchgeführt;
der konkrete Corefall wird durch den Produktionstest reproduziert.
+29 -10
View File
@@ -199,24 +199,43 @@ class spell_gen_alchemist_stone : public SpellScriptLoader
bool CheckProc(ProcEventInfo& eventInfo)
{
return eventInfo.GetDamageInfo()->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_POTION;
SpellInfo const* spellInfo = eventInfo.GetSpellInfo();
return spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_POTION;
}
void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo)
{
PreventDefaultAction();
uint32 spellId = 0;
int32 bp = int32(eventInfo.GetDamageInfo()->GetDamage() * 0.4f);
if (eventInfo.GetDamageInfo()->GetSpellInfo()->HasEffect(SPELL_EFFECT_HEAL))
spellId = ALCHEMIST_STONE_HEAL;
else if (eventInfo.GetDamageInfo()->GetSpellInfo()->HasEffect(SPELL_EFFECT_ENERGIZE))
spellId = ALCHEMIST_STONE_MANA;
if (!spellId)
SpellInfo const* spellInfo = eventInfo.GetSpellInfo();
if (!spellInfo || spellInfo->SpellFamilyName != SPELLFAMILY_POTION)
return;
uint32 spellId = 0;
uint32 amount = 0;
if (spellInfo->HasEffect(SPELL_EFFECT_HEAL))
{
// Healing hits carry HealInfo, not DamageInfo. Use the
// original heal, retaining the potion bonus with overheal.
if (HealInfo const* healInfo = eventInfo.GetHealInfo())
{
spellId = ALCHEMIST_STONE_HEAL;
amount = healInfo->GetHeal();
}
}
else if (spellInfo->HasEffect(SPELL_EFFECT_ENERGIZE))
{
if (DamageInfo const* damageInfo = eventInfo.GetDamageInfo())
{
spellId = ALCHEMIST_STONE_MANA;
amount = damageInfo->GetDamage();
}
}
if (!spellId || !amount)
return;
int32 bp = int32(amount * 0.4f);
GetTarget()->CastSpell(GetTarget(), spellId, CastSpellExtraArgs(aurEff).AddSpellBP0(bp));
}
@@ -0,0 +1,66 @@
"""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)')