Files
moonwell-core/src/test/mocks/AuraScriptTestFramework.h
T
blinkysc 4599f26ae9 refactor(Core/Spells): QAston proc system (#24233)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: QAston <qaston@gmail.com>
Co-authored-by: joschiwald <joschiwald@online.de>
Co-authored-by: ariel- <ariel-@users.noreply.github.com>
Co-authored-by: Kitzunu <24550914+Kitzunu@users.noreply.github.com>
Co-authored-by: blinkysc <your-github-email@example.com>
Co-authored-by: Tereneckla <Tereneckla@users.noreply.github.com>
Co-authored-by: Andrew <47818697+Nyeriah@users.noreply.github.com>
2026-02-18 08:31:53 -03:00

485 lines
14 KiB
C++

/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AZEROTHCORE_AURA_SCRIPT_TEST_FRAMEWORK_H
#define AZEROTHCORE_AURA_SCRIPT_TEST_FRAMEWORK_H
#include "AuraStub.h"
#include "DamageHealInfoStub.h"
#include "ProcEventInfoHelper.h"
#include "SpellInfoTestHelper.h"
#include "UnitStub.h"
#include "SpellMgr.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <memory>
#include <vector>
/**
* @brief Simulated proc result for testing
*/
struct ProcTestResult
{
bool shouldProc = false;
uint8_t effectMask = 0;
float procChance = 100.0f;
std::vector<uint32_t> spellsCast;
bool chargeConsumed = false;
bool cooldownSet = false;
};
/**
* @brief Context for a proc test scenario
*/
class ProcTestContext
{
public:
ProcTestContext() = default;
// Actor (the one doing something that triggers the proc)
UnitStub& GetActor() { return _actor; }
UnitStub const& GetActor() const { return _actor; }
// Target (the one being affected)
UnitStub& GetTarget() { return _target; }
UnitStub const& GetTarget() const { return _target; }
// The aura that might proc
AuraStub& GetAura() { return _aura; }
AuraStub const& GetAura() const { return _aura; }
// Damage info for damage-based procs
DamageInfoStub& GetDamageInfo() { return _damageInfo; }
DamageInfoStub const& GetDamageInfo() const { return _damageInfo; }
// Heal info for heal-based procs
HealInfoStub& GetHealInfo() { return _healInfo; }
HealInfoStub const& GetHealInfo() const { return _healInfo; }
// Setup methods
ProcTestContext& WithAuraId(uint32_t auraId)
{
_aura.SetId(auraId);
return *this;
}
ProcTestContext& WithAuraSpellFamily(uint32_t familyName)
{
_aura.SetSpellFamilyName(familyName);
return *this;
}
ProcTestContext& WithAuraCharges(uint8_t charges)
{
_aura.SetCharges(charges);
_aura.SetUsingCharges(charges > 0);
return *this;
}
ProcTestContext& WithActorAsPlayer(bool isPlayer = true)
{
_actor.SetIsPlayer(isPlayer);
return *this;
}
ProcTestContext& WithDamage(uint32_t damage, uint32_t schoolMask = 1)
{
_damageInfo.SetDamage(damage);
_damageInfo.SetOriginalDamage(damage);
_damageInfo.SetSchoolMask(schoolMask);
return *this;
}
ProcTestContext& WithHeal(uint32_t heal, uint32_t effectiveHeal = 0)
{
_healInfo.SetHeal(heal);
_healInfo.SetEffectiveHeal(effectiveHeal > 0 ? effectiveHeal : heal);
return *this;
}
ProcTestContext& WithCriticalHit()
{
_damageInfo.SetHitMask(PROC_HIT_CRITICAL);
_healInfo.SetHitMask(PROC_HIT_CRITICAL);
return *this;
}
ProcTestContext& WithNormalHit()
{
_damageInfo.SetHitMask(PROC_HIT_NORMAL);
_healInfo.SetHitMask(PROC_HIT_NORMAL);
return *this;
}
private:
UnitStub _actor;
UnitStub _target;
AuraStub _aura;
DamageInfoStub _damageInfo;
HealInfoStub _healInfo;
};
/**
* @brief Base fixture for AuraScript proc testing
*
* This provides infrastructure for testing proc behavior at the unit level
* without requiring full game objects.
*/
class AuraScriptProcTestFixture : public ::testing::Test
{
protected:
void SetUp() override
{
_context = std::make_unique<ProcTestContext>();
_spellInfos.clear();
}
void TearDown() override
{
for (auto* spellInfo : _spellInfos)
{
delete spellInfo;
}
_spellInfos.clear();
}
// Access the test context
ProcTestContext& Context() { return *_context; }
// Create and track a test SpellInfo
SpellInfo* CreateSpellInfo(uint32_t id, uint32_t familyName = 0,
uint32_t familyFlags0 = 0, uint32_t familyFlags1 = 0,
uint32_t familyFlags2 = 0)
{
auto* spellInfo = SpellInfoBuilder()
.WithId(id)
.WithSpellFamilyName(familyName)
.WithSpellFamilyFlags(familyFlags0, familyFlags1, familyFlags2)
.Build();
_spellInfos.push_back(spellInfo);
return spellInfo;
}
// Create a test SpellProcEntry
SpellProcEntry CreateProcEntry()
{
return SpellProcEntryBuilder().Build();
}
// Create a test ProcEventInfo
ProcEventInfo CreateEventInfo(uint32_t typeMask, uint32_t hitMask,
uint32_t spellTypeMask = PROC_SPELL_TYPE_MASK_ALL,
uint32_t spellPhaseMask = PROC_SPELL_PHASE_HIT)
{
return ProcEventInfoBuilder()
.WithTypeMask(typeMask)
.WithHitMask(hitMask)
.WithSpellTypeMask(spellTypeMask)
.WithSpellPhaseMask(spellPhaseMask)
.Build();
}
// Test if a proc entry would trigger with given event info
bool TestCanProc(SpellProcEntry const& procEntry, uint32_t typeMask,
uint32_t hitMask, SpellInfo const* triggerSpell = nullptr)
{
DamageInfo* damageInfoPtr = nullptr;
HealInfo* healInfoPtr = nullptr;
// Create real DamageInfo/HealInfo if we have a trigger spell
// Note: This requires the actual game classes, which may need adjustment
// For now, we use the stub approach
auto eventInfo = ProcEventInfoBuilder()
.WithTypeMask(typeMask)
.WithHitMask(hitMask)
.WithSpellTypeMask(PROC_SPELL_TYPE_MASK_ALL)
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
.Build();
return sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, eventInfo);
}
// Check if spell family matches
bool TestSpellFamilyMatch(uint32_t procFamilyName, flag96 const& procFamilyMask,
SpellInfo const* triggerSpell)
{
if (procFamilyName && triggerSpell)
{
if (procFamilyName != triggerSpell->SpellFamilyName)
return false;
if (procFamilyMask)
{
flag96 triggerMask;
triggerMask[0] = triggerSpell->SpellFamilyFlags[0];
triggerMask[1] = triggerSpell->SpellFamilyFlags[1];
triggerMask[2] = triggerSpell->SpellFamilyFlags[2];
if (!(triggerMask & procFamilyMask))
return false;
}
}
return true;
}
private:
std::unique_ptr<ProcTestContext> _context;
std::vector<SpellInfo*> _spellInfos;
};
/**
* @brief Helper class for testing specific proc scenarios
*
* Uses shared_ptr for resource management to allow safe copying
* in fluent builder pattern usage.
*/
class ProcScenarioBuilder
{
public:
ProcScenarioBuilder()
{
// Create a default SpellInfo for spell-type procs using shared_ptr
_defaultSpellInfo = std::shared_ptr<SpellInfo>(
SpellInfoBuilder()
.WithId(99999)
.WithSpellFamilyName(0)
.Build()
);
}
~ProcScenarioBuilder() = default;
// Configure the triggering action
ProcScenarioBuilder& OnMeleeAutoAttack()
{
_typeMask = PROC_FLAG_DONE_MELEE_AUTO_ATTACK;
_needsSpellInfo = false;
return *this;
}
ProcScenarioBuilder& OnTakenMeleeAutoAttack()
{
_typeMask = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
_needsSpellInfo = false;
return *this;
}
ProcScenarioBuilder& OnSpellDamage()
{
_typeMask = PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
_spellTypeMask = PROC_SPELL_TYPE_DAMAGE;
_needsSpellInfo = true;
_usesDamageInfo = true;
return *this;
}
ProcScenarioBuilder& OnTakenSpellDamage()
{
_typeMask = PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG;
_spellTypeMask = PROC_SPELL_TYPE_DAMAGE;
_needsSpellInfo = true;
_usesDamageInfo = true;
return *this;
}
ProcScenarioBuilder& OnHeal()
{
_typeMask = PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS;
_spellTypeMask = PROC_SPELL_TYPE_HEAL;
_needsSpellInfo = true;
_usesHealInfo = true;
return *this;
}
ProcScenarioBuilder& OnTakenHeal()
{
_typeMask = PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS;
_spellTypeMask = PROC_SPELL_TYPE_HEAL;
_needsSpellInfo = true;
_usesHealInfo = true;
return *this;
}
ProcScenarioBuilder& OnPeriodicDamage()
{
_typeMask = PROC_FLAG_DONE_PERIODIC;
_spellTypeMask = PROC_SPELL_TYPE_DAMAGE;
_needsSpellInfo = true;
_usesDamageInfo = true;
return *this;
}
ProcScenarioBuilder& OnPeriodicHeal()
{
_typeMask = PROC_FLAG_DONE_PERIODIC;
_spellTypeMask = PROC_SPELL_TYPE_HEAL;
_needsSpellInfo = true;
_usesHealInfo = true;
return *this;
}
ProcScenarioBuilder& OnKill()
{
_typeMask = PROC_FLAG_KILL;
_needsSpellInfo = false;
return *this;
}
ProcScenarioBuilder& OnDeath()
{
_typeMask = PROC_FLAG_DEATH;
_needsSpellInfo = false;
return *this;
}
// Configure hit result
ProcScenarioBuilder& WithCrit()
{
_hitMask = PROC_HIT_CRITICAL;
return *this;
}
ProcScenarioBuilder& WithNormalHit()
{
_hitMask = PROC_HIT_NORMAL;
return *this;
}
ProcScenarioBuilder& WithMiss()
{
_hitMask = PROC_HIT_MISS;
return *this;
}
ProcScenarioBuilder& WithDodge()
{
_hitMask = PROC_HIT_DODGE;
return *this;
}
ProcScenarioBuilder& WithParry()
{
_hitMask = PROC_HIT_PARRY;
return *this;
}
ProcScenarioBuilder& WithBlock()
{
_hitMask = PROC_HIT_BLOCK;
return *this;
}
ProcScenarioBuilder& WithFullBlock()
{
_hitMask = PROC_HIT_FULL_BLOCK;
return *this;
}
ProcScenarioBuilder& WithAbsorb()
{
_hitMask = PROC_HIT_ABSORB;
return *this;
}
// Note: PROC_HIT_ABSORB covers both partial and full absorb
// There is no separate PROC_HIT_FULL_ABSORB flag in AzerothCore
// Configure spell phase
ProcScenarioBuilder& OnCast()
{
_spellPhaseMask = PROC_SPELL_PHASE_CAST;
return *this;
}
ProcScenarioBuilder& OnHit()
{
_spellPhaseMask = PROC_SPELL_PHASE_HIT;
return *this;
}
ProcScenarioBuilder& OnFinish()
{
_spellPhaseMask = PROC_SPELL_PHASE_FINISH;
return *this;
}
// Build the scenario into a ProcEventInfo
ProcEventInfo Build()
{
auto builder = ProcEventInfoBuilder()
.WithTypeMask(_typeMask)
.WithHitMask(_hitMask)
.WithSpellTypeMask(_spellTypeMask)
.WithSpellPhaseMask(_spellPhaseMask);
// Create DamageInfo or HealInfo with SpellInfo for spell-type procs
if (_needsSpellInfo)
{
if (_usesDamageInfo)
{
// Create new DamageInfo if needed
if (!_damageInfo)
_damageInfo = std::make_shared<DamageInfo>(nullptr, nullptr, 100, _defaultSpellInfo.get(), SPELL_SCHOOL_MASK_FIRE, SPELL_DIRECT_DAMAGE);
builder.WithDamageInfo(_damageInfo.get());
}
else if (_usesHealInfo)
{
// Create new HealInfo if needed
if (!_healInfo)
_healInfo = std::make_shared<HealInfo>(nullptr, nullptr, 100, _defaultSpellInfo.get(), SPELL_SCHOOL_MASK_HOLY);
builder.WithHealInfo(_healInfo.get());
}
}
return builder.Build();
}
// Get individual values
[[nodiscard]] uint32_t GetTypeMask() const { return _typeMask; }
[[nodiscard]] uint32_t GetHitMask() const { return _hitMask; }
[[nodiscard]] uint32_t GetSpellTypeMask() const { return _spellTypeMask; }
[[nodiscard]] uint32_t GetSpellPhaseMask() const { return _spellPhaseMask; }
private:
uint32_t _typeMask = 0;
uint32_t _hitMask = PROC_HIT_NORMAL;
uint32_t _spellTypeMask = PROC_SPELL_TYPE_MASK_ALL;
uint32_t _spellPhaseMask = PROC_SPELL_PHASE_HIT;
bool _needsSpellInfo = false;
bool _usesDamageInfo = false;
bool _usesHealInfo = false;
std::shared_ptr<SpellInfo> _defaultSpellInfo;
std::shared_ptr<DamageInfo> _damageInfo;
std::shared_ptr<HealInfo> _healInfo;
};
// Convenience macros for proc testing
#define EXPECT_PROC_TRIGGERS(procEntry, scenario) \
do { \
auto _eventInfo = (scenario).Build(); \
EXPECT_TRUE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, _eventInfo)); \
} while(0)
#define EXPECT_PROC_DOES_NOT_TRIGGER(procEntry, scenario) \
do { \
auto _eventInfo = (scenario).Build(); \
EXPECT_FALSE(sSpellMgr->CanSpellTriggerProcOnEvent(procEntry, _eventInfo)); \
} while(0)
#endif //AZEROTHCORE_AURA_SCRIPT_TEST_FRAMEWORK_H