mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-03-16 14:05:28 +00:00
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>
This commit is contained in:
484
src/test/mocks/AuraScriptTestFramework.h
Normal file
484
src/test/mocks/AuraScriptTestFramework.h
Normal file
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* 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
|
||||
367
src/test/mocks/AuraStub.h
Normal file
367
src/test/mocks/AuraStub.h
Normal file
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* 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_STUB_H
|
||||
#define AZEROTHCORE_AURA_STUB_H
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class SpellInfo;
|
||||
class UnitStub;
|
||||
|
||||
/**
|
||||
* @brief Lightweight stub for AuraEffect proc-related functionality
|
||||
*/
|
||||
class AuraEffectStub
|
||||
{
|
||||
public:
|
||||
AuraEffectStub(uint8_t effIndex = 0, int32_t amount = 0, uint32_t auraType = 0)
|
||||
: _effIndex(effIndex), _amount(amount), _auraType(auraType) {}
|
||||
|
||||
virtual ~AuraEffectStub() = default;
|
||||
|
||||
[[nodiscard]] uint8_t GetEffIndex() const { return _effIndex; }
|
||||
[[nodiscard]] int32_t GetAmount() const { return _amount; }
|
||||
[[nodiscard]] uint32_t GetAuraType() const { return _auraType; }
|
||||
[[nodiscard]] int32_t GetBaseAmount() const { return _baseAmount; }
|
||||
[[nodiscard]] float GetCritChance() const { return _critChance; }
|
||||
|
||||
void SetEffIndex(uint8_t effIndex) { _effIndex = effIndex; }
|
||||
void SetAmount(int32_t amount) { _amount = amount; }
|
||||
void SetAuraType(uint32_t auraType) { _auraType = auraType; }
|
||||
void SetBaseAmount(int32_t baseAmount) { _baseAmount = baseAmount; }
|
||||
void SetCritChance(float critChance) { _critChance = critChance; }
|
||||
|
||||
// Periodic tracking
|
||||
[[nodiscard]] bool IsPeriodic() const { return _isPeriodic; }
|
||||
[[nodiscard]] int32_t GetTotalTicks() const { return _totalTicks; }
|
||||
[[nodiscard]] uint32_t GetTickNumber() const { return _tickNumber; }
|
||||
|
||||
void SetPeriodic(bool isPeriodic) { _isPeriodic = isPeriodic; }
|
||||
void SetTotalTicks(int32_t totalTicks) { _totalTicks = totalTicks; }
|
||||
void SetTickNumber(uint32_t tickNumber) { _tickNumber = tickNumber; }
|
||||
|
||||
private:
|
||||
uint8_t _effIndex = 0;
|
||||
int32_t _amount = 0;
|
||||
int32_t _baseAmount = 0;
|
||||
uint32_t _auraType = 0;
|
||||
float _critChance = 0.0f;
|
||||
bool _isPeriodic = false;
|
||||
int32_t _totalTicks = 0;
|
||||
uint32_t _tickNumber = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Lightweight stub for AuraApplication functionality
|
||||
*/
|
||||
class AuraApplicationStub
|
||||
{
|
||||
public:
|
||||
AuraApplicationStub() = default;
|
||||
virtual ~AuraApplicationStub() = default;
|
||||
|
||||
[[nodiscard]] uint8_t GetEffectMask() const { return _effectMask; }
|
||||
[[nodiscard]] bool HasEffect(uint8_t effIndex) const
|
||||
{
|
||||
return (_effectMask & (1 << effIndex)) != 0;
|
||||
}
|
||||
[[nodiscard]] bool IsPositive() const { return _isPositive; }
|
||||
[[nodiscard]] uint8_t GetSlot() const { return _slot; }
|
||||
|
||||
void SetEffectMask(uint8_t mask) { _effectMask = mask; }
|
||||
void SetEffect(uint8_t effIndex, bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
_effectMask |= (1 << effIndex);
|
||||
else
|
||||
_effectMask &= ~(1 << effIndex);
|
||||
}
|
||||
void SetPositive(bool isPositive) { _isPositive = isPositive; }
|
||||
void SetSlot(uint8_t slot) { _slot = slot; }
|
||||
|
||||
private:
|
||||
uint8_t _effectMask = 0x07; // All 3 effects by default
|
||||
bool _isPositive = true;
|
||||
uint8_t _slot = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Lightweight stub for Aura proc-related functionality
|
||||
*/
|
||||
class AuraStub
|
||||
{
|
||||
public:
|
||||
AuraStub(uint32_t id = 0, uint32_t spellFamilyName = 0)
|
||||
: _id(id), _spellFamilyName(spellFamilyName)
|
||||
{
|
||||
// Create 3 effect slots by default
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
_effects[i] = std::make_unique<AuraEffectStub>(static_cast<uint8_t>(i));
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~AuraStub() = default;
|
||||
|
||||
// Basic identification
|
||||
[[nodiscard]] uint32_t GetId() const { return _id; }
|
||||
[[nodiscard]] uint32_t GetSpellFamilyName() const { return _spellFamilyName; }
|
||||
|
||||
void SetId(uint32_t id) { _id = id; }
|
||||
void SetSpellFamilyName(uint32_t familyName) { _spellFamilyName = familyName; }
|
||||
|
||||
// Effect access
|
||||
[[nodiscard]] AuraEffectStub* GetEffect(uint8_t effIndex) const
|
||||
{
|
||||
return (effIndex < 3) ? _effects[effIndex].get() : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasEffect(uint8_t effIndex) const
|
||||
{
|
||||
return effIndex < 3 && _effects[effIndex] != nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint8_t GetEffectMask() const
|
||||
{
|
||||
uint8_t mask = 0;
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
{
|
||||
if (_effects[i])
|
||||
mask |= (1 << i);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Charges management
|
||||
[[nodiscard]] uint8_t GetCharges() const { return _charges; }
|
||||
[[nodiscard]] bool IsUsingCharges() const { return _isUsingCharges; }
|
||||
|
||||
void SetCharges(uint8_t charges) { _charges = charges; }
|
||||
void SetUsingCharges(bool usingCharges) { _isUsingCharges = usingCharges; }
|
||||
|
||||
virtual bool DropCharge()
|
||||
{
|
||||
if (_charges > 0)
|
||||
{
|
||||
--_charges;
|
||||
_chargeDropped = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool WasChargeDropped() const { return _chargeDropped; }
|
||||
void ResetChargeDropped() { _chargeDropped = false; }
|
||||
|
||||
// Duration
|
||||
[[nodiscard]] int32_t GetDuration() const { return _duration; }
|
||||
[[nodiscard]] int32_t GetMaxDuration() const { return _maxDuration; }
|
||||
[[nodiscard]] bool IsPermanent() const { return _maxDuration == -1; }
|
||||
|
||||
void SetDuration(int32_t duration) { _duration = duration; }
|
||||
void SetMaxDuration(int32_t maxDuration) { _maxDuration = maxDuration; }
|
||||
|
||||
// Cooldown tracking
|
||||
using TimePoint = std::chrono::steady_clock::time_point;
|
||||
|
||||
[[nodiscard]] bool IsProcOnCooldown(TimePoint now) const
|
||||
{
|
||||
return now < _procCooldown;
|
||||
}
|
||||
|
||||
void AddProcCooldown(TimePoint cooldownEnd)
|
||||
{
|
||||
_procCooldown = cooldownEnd;
|
||||
}
|
||||
|
||||
void ResetProcCooldown()
|
||||
{
|
||||
_procCooldown = TimePoint::min();
|
||||
}
|
||||
|
||||
// Stack amount
|
||||
[[nodiscard]] uint8_t GetStackAmount() const { return _stackAmount; }
|
||||
void SetStackAmount(uint8_t amount) { _stackAmount = amount; }
|
||||
|
||||
/**
|
||||
* @brief Modify stack amount (for PROC_ATTR_USE_STACKS_FOR_CHARGES)
|
||||
* Mimics Aura::ModStackAmount() - removes aura if stacks reach 0
|
||||
*/
|
||||
virtual bool ModStackAmount(int32_t amount, bool /* resetPeriodicTimer */ = true)
|
||||
{
|
||||
int32_t newAmount = static_cast<int32_t>(_stackAmount) + amount;
|
||||
if (newAmount <= 0)
|
||||
{
|
||||
_stackAmount = 0;
|
||||
Remove();
|
||||
return true; // Aura removed
|
||||
}
|
||||
_stackAmount = static_cast<uint8_t>(newAmount);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Aura flags
|
||||
[[nodiscard]] bool IsPassive() const { return _isPassive; }
|
||||
[[nodiscard]] bool IsRemoved() const { return _isRemoved; }
|
||||
|
||||
void SetPassive(bool isPassive) { _isPassive = isPassive; }
|
||||
void SetRemoved(bool isRemoved) { _isRemoved = isRemoved; }
|
||||
|
||||
/**
|
||||
* @brief Mark aura as removed (for charge exhaustion)
|
||||
* Mimics Aura::Remove()
|
||||
*/
|
||||
virtual void Remove()
|
||||
{
|
||||
_isRemoved = true;
|
||||
}
|
||||
|
||||
// Application management
|
||||
AuraApplicationStub& GetOrCreateApplication()
|
||||
{
|
||||
if (!_application)
|
||||
_application = std::make_unique<AuraApplicationStub>();
|
||||
return *_application;
|
||||
}
|
||||
|
||||
[[nodiscard]] AuraApplicationStub* GetApplication() const
|
||||
{
|
||||
return _application.get();
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t _id = 0;
|
||||
uint32_t _spellFamilyName = 0;
|
||||
|
||||
std::unique_ptr<AuraEffectStub> _effects[3];
|
||||
std::unique_ptr<AuraApplicationStub> _application;
|
||||
|
||||
uint8_t _charges = 0;
|
||||
bool _isUsingCharges = false;
|
||||
bool _chargeDropped = false;
|
||||
|
||||
int32_t _duration = -1;
|
||||
int32_t _maxDuration = -1;
|
||||
|
||||
TimePoint _procCooldown = TimePoint::min();
|
||||
|
||||
uint8_t _stackAmount = 1;
|
||||
bool _isPassive = false;
|
||||
bool _isRemoved = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GMock-enabled Aura stub for verification
|
||||
*/
|
||||
class MockAuraStub : public AuraStub
|
||||
{
|
||||
public:
|
||||
MockAuraStub(uint32_t id = 0, uint32_t spellFamilyName = 0)
|
||||
: AuraStub(id, spellFamilyName) {}
|
||||
|
||||
MOCK_METHOD(bool, DropCharge, (), (override));
|
||||
MOCK_METHOD(bool, ModStackAmount, (int32_t amount, bool resetPeriodicTimer), (override));
|
||||
MOCK_METHOD(void, Remove, (), (override));
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for creating AuraStub instances with fluent API
|
||||
*/
|
||||
class AuraStubBuilder
|
||||
{
|
||||
public:
|
||||
AuraStubBuilder() : _stub(std::make_unique<AuraStub>()) {}
|
||||
|
||||
AuraStubBuilder& WithId(uint32_t id)
|
||||
{
|
||||
_stub->SetId(id);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithSpellFamilyName(uint32_t familyName)
|
||||
{
|
||||
_stub->SetSpellFamilyName(familyName);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithCharges(uint8_t charges)
|
||||
{
|
||||
_stub->SetCharges(charges);
|
||||
_stub->SetUsingCharges(charges > 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithDuration(int32_t duration)
|
||||
{
|
||||
_stub->SetDuration(duration);
|
||||
_stub->SetMaxDuration(duration);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithStackAmount(uint8_t amount)
|
||||
{
|
||||
_stub->SetStackAmount(amount);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithPassive(bool isPassive)
|
||||
{
|
||||
_stub->SetPassive(isPassive);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithEffect(uint8_t effIndex, int32_t amount, uint32_t auraType = 0)
|
||||
{
|
||||
if (AuraEffectStub* eff = _stub->GetEffect(effIndex))
|
||||
{
|
||||
eff->SetAmount(amount);
|
||||
eff->SetAuraType(auraType);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuraStubBuilder& WithPeriodicEffect(uint8_t effIndex, int32_t amount, int32_t totalTicks)
|
||||
{
|
||||
if (AuraEffectStub* eff = _stub->GetEffect(effIndex))
|
||||
{
|
||||
eff->SetAmount(amount);
|
||||
eff->SetPeriodic(true);
|
||||
eff->SetTotalTicks(totalTicks);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::unique_ptr<AuraStub> Build()
|
||||
{
|
||||
return std::move(_stub);
|
||||
}
|
||||
|
||||
AuraStub* BuildRaw()
|
||||
{
|
||||
return _stub.release();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AuraStub> _stub;
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_AURA_STUB_H
|
||||
259
src/test/mocks/DamageHealInfoStub.h
Normal file
259
src/test/mocks/DamageHealInfoStub.h
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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_DAMAGE_HEAL_INFO_STUB_H
|
||||
#define AZEROTHCORE_DAMAGE_HEAL_INFO_STUB_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class SpellInfo;
|
||||
class UnitStub;
|
||||
|
||||
/**
|
||||
* @brief Lightweight stub for DamageInfo
|
||||
*
|
||||
* Mirrors the key fields of DamageInfo for proc testing without
|
||||
* requiring actual Unit objects.
|
||||
*/
|
||||
class DamageInfoStub
|
||||
{
|
||||
public:
|
||||
DamageInfoStub() = default;
|
||||
|
||||
DamageInfoStub(uint32_t damage, uint32_t originalDamage, uint32_t schoolMask,
|
||||
uint8_t attackType, SpellInfo const* spellInfo = nullptr)
|
||||
: _damage(damage)
|
||||
, _originalDamage(originalDamage)
|
||||
, _schoolMask(schoolMask)
|
||||
, _attackType(attackType)
|
||||
, _spellInfo(spellInfo)
|
||||
{}
|
||||
|
||||
virtual ~DamageInfoStub() = default;
|
||||
|
||||
// Damage values
|
||||
[[nodiscard]] uint32_t GetDamage() const { return _damage; }
|
||||
[[nodiscard]] uint32_t GetOriginalDamage() const { return _originalDamage; }
|
||||
[[nodiscard]] uint32_t GetAbsorb() const { return _absorb; }
|
||||
[[nodiscard]] uint32_t GetResist() const { return _resist; }
|
||||
[[nodiscard]] uint32_t GetBlock() const { return _block; }
|
||||
|
||||
void SetDamage(uint32_t damage) { _damage = damage; }
|
||||
void SetOriginalDamage(uint32_t damage) { _originalDamage = damage; }
|
||||
void SetAbsorb(uint32_t absorb) { _absorb = absorb; }
|
||||
void SetResist(uint32_t resist) { _resist = resist; }
|
||||
void SetBlock(uint32_t block) { _block = block; }
|
||||
|
||||
// School and attack type
|
||||
[[nodiscard]] uint32_t GetSchoolMask() const { return _schoolMask; }
|
||||
[[nodiscard]] uint8_t GetAttackType() const { return _attackType; }
|
||||
|
||||
void SetSchoolMask(uint32_t schoolMask) { _schoolMask = schoolMask; }
|
||||
void SetAttackType(uint8_t attackType) { _attackType = attackType; }
|
||||
|
||||
// Spell info
|
||||
[[nodiscard]] SpellInfo const* GetSpellInfo() const { return _spellInfo; }
|
||||
void SetSpellInfo(SpellInfo const* spellInfo) { _spellInfo = spellInfo; }
|
||||
|
||||
// Hit result flags
|
||||
[[nodiscard]] uint32_t GetHitMask() const { return _hitMask; }
|
||||
void SetHitMask(uint32_t hitMask) { _hitMask = hitMask; }
|
||||
|
||||
private:
|
||||
uint32_t _damage = 0;
|
||||
uint32_t _originalDamage = 0;
|
||||
uint32_t _absorb = 0;
|
||||
uint32_t _resist = 0;
|
||||
uint32_t _block = 0;
|
||||
uint32_t _schoolMask = 1; // SPELL_SCHOOL_MASK_NORMAL
|
||||
uint8_t _attackType = 0; // BASE_ATTACK
|
||||
uint32_t _hitMask = 0;
|
||||
SpellInfo const* _spellInfo = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Lightweight stub for HealInfo
|
||||
*
|
||||
* Mirrors the key fields of HealInfo for proc testing.
|
||||
*/
|
||||
class HealInfoStub
|
||||
{
|
||||
public:
|
||||
HealInfoStub() = default;
|
||||
|
||||
HealInfoStub(uint32_t heal, uint32_t effectiveHeal, uint32_t absorb,
|
||||
SpellInfo const* spellInfo = nullptr)
|
||||
: _heal(heal)
|
||||
, _effectiveHeal(effectiveHeal)
|
||||
, _absorb(absorb)
|
||||
, _spellInfo(spellInfo)
|
||||
{}
|
||||
|
||||
virtual ~HealInfoStub() = default;
|
||||
|
||||
// Heal values
|
||||
[[nodiscard]] uint32_t GetHeal() const { return _heal; }
|
||||
[[nodiscard]] uint32_t GetEffectiveHeal() const { return _effectiveHeal; }
|
||||
[[nodiscard]] uint32_t GetAbsorb() const { return _absorb; }
|
||||
[[nodiscard]] uint32_t GetOverheal() const { return _heal > _effectiveHeal ? _heal - _effectiveHeal : 0; }
|
||||
|
||||
void SetHeal(uint32_t heal) { _heal = heal; }
|
||||
void SetEffectiveHeal(uint32_t effectiveHeal) { _effectiveHeal = effectiveHeal; }
|
||||
void SetAbsorb(uint32_t absorb) { _absorb = absorb; }
|
||||
|
||||
// Spell info
|
||||
[[nodiscard]] SpellInfo const* GetSpellInfo() const { return _spellInfo; }
|
||||
void SetSpellInfo(SpellInfo const* spellInfo) { _spellInfo = spellInfo; }
|
||||
|
||||
// Hit result flags
|
||||
[[nodiscard]] uint32_t GetHitMask() const { return _hitMask; }
|
||||
void SetHitMask(uint32_t hitMask) { _hitMask = hitMask; }
|
||||
|
||||
private:
|
||||
uint32_t _heal = 0;
|
||||
uint32_t _effectiveHeal = 0;
|
||||
uint32_t _absorb = 0;
|
||||
uint32_t _hitMask = 0;
|
||||
SpellInfo const* _spellInfo = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for creating DamageInfoStub instances with fluent API
|
||||
*/
|
||||
class DamageInfoStubBuilder
|
||||
{
|
||||
public:
|
||||
DamageInfoStubBuilder() = default;
|
||||
|
||||
DamageInfoStubBuilder& WithDamage(uint32_t damage)
|
||||
{
|
||||
_stub.SetDamage(damage);
|
||||
_stub.SetOriginalDamage(damage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithOriginalDamage(uint32_t damage)
|
||||
{
|
||||
_stub.SetOriginalDamage(damage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithSchoolMask(uint32_t schoolMask)
|
||||
{
|
||||
_stub.SetSchoolMask(schoolMask);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithAttackType(uint8_t attackType)
|
||||
{
|
||||
_stub.SetAttackType(attackType);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithSpellInfo(SpellInfo const* spellInfo)
|
||||
{
|
||||
_stub.SetSpellInfo(spellInfo);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithAbsorb(uint32_t absorb)
|
||||
{
|
||||
_stub.SetAbsorb(absorb);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithResist(uint32_t resist)
|
||||
{
|
||||
_stub.SetResist(resist);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithBlock(uint32_t block)
|
||||
{
|
||||
_stub.SetBlock(block);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStubBuilder& WithHitMask(uint32_t hitMask)
|
||||
{
|
||||
_stub.SetHitMask(hitMask);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DamageInfoStub Build() { return _stub; }
|
||||
|
||||
private:
|
||||
DamageInfoStub _stub;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for creating HealInfoStub instances with fluent API
|
||||
*/
|
||||
class HealInfoStubBuilder
|
||||
{
|
||||
public:
|
||||
HealInfoStubBuilder() = default;
|
||||
|
||||
HealInfoStubBuilder& WithHeal(uint32_t heal)
|
||||
{
|
||||
_stub.SetHeal(heal);
|
||||
_stub.SetEffectiveHeal(heal); // Assume all effective unless overridden
|
||||
return *this;
|
||||
}
|
||||
|
||||
HealInfoStubBuilder& WithEffectiveHeal(uint32_t effectiveHeal)
|
||||
{
|
||||
_stub.SetEffectiveHeal(effectiveHeal);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HealInfoStubBuilder& WithOverheal(uint32_t overheal)
|
||||
{
|
||||
// Overheal = Heal - EffectiveHeal
|
||||
// So EffectiveHeal = Heal - Overheal
|
||||
if (_stub.GetHeal() >= overheal)
|
||||
{
|
||||
_stub.SetEffectiveHeal(_stub.GetHeal() - overheal);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
HealInfoStubBuilder& WithAbsorb(uint32_t absorb)
|
||||
{
|
||||
_stub.SetAbsorb(absorb);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HealInfoStubBuilder& WithSpellInfo(SpellInfo const* spellInfo)
|
||||
{
|
||||
_stub.SetSpellInfo(spellInfo);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HealInfoStubBuilder& WithHitMask(uint32_t hitMask)
|
||||
{
|
||||
_stub.SetHitMask(hitMask);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HealInfoStub Build() { return _stub; }
|
||||
|
||||
private:
|
||||
HealInfoStub _stub;
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_DAMAGE_HEAL_INFO_STUB_H
|
||||
565
src/test/mocks/ProcChanceTestHelper.h
Normal file
565
src/test/mocks/ProcChanceTestHelper.h
Normal file
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
* 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_PROC_CHANCE_TEST_HELPER_H
|
||||
#define AZEROTHCORE_PROC_CHANCE_TEST_HELPER_H
|
||||
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "AuraStub.h"
|
||||
#include "UnitStub.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* @brief Helper class for testing proc chance calculations
|
||||
*
|
||||
* Provides standalone implementations of proc-related calculations
|
||||
* that can be tested without requiring full game objects.
|
||||
*/
|
||||
class ProcChanceTestHelper
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Calculate PPM proc chance
|
||||
* Implements the formula: (WeaponSpeed * PPM) / 600.0f
|
||||
*
|
||||
* @param weaponSpeed Weapon attack speed in milliseconds
|
||||
* @param ppm Procs per minute value
|
||||
* @param ppmModifier Additional PPM modifier (from talents/auras)
|
||||
* @return Proc chance as percentage (0-100+)
|
||||
*/
|
||||
static float CalculatePPMChance(uint32 weaponSpeed, float ppm, float ppmModifier = 0.0f)
|
||||
{
|
||||
if (ppm <= 0.0f)
|
||||
return 0.0f;
|
||||
|
||||
float modifiedPPM = ppm + ppmModifier;
|
||||
return (static_cast<float>(weaponSpeed) * modifiedPPM) / 600.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate level 60+ reduction
|
||||
* Implements PROC_ATTR_REDUCE_PROC_60: 3.333% reduction per level above 60
|
||||
*
|
||||
* @param baseChance Base proc chance
|
||||
* @param actorLevel Actor's level
|
||||
* @return Reduced proc chance
|
||||
*/
|
||||
static float ApplyLevel60Reduction(float baseChance, uint32 actorLevel)
|
||||
{
|
||||
if (actorLevel <= 60)
|
||||
return baseChance;
|
||||
|
||||
// Reduction = (level - 60) / 30, capped at 1.0
|
||||
float reduction = static_cast<float>(actorLevel - 60) / 30.0f;
|
||||
return std::max(0.0f, (1.0f - reduction) * baseChance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Simulate CalcProcChance() from SpellAuras.cpp
|
||||
*
|
||||
* @param procEntry The proc configuration
|
||||
* @param actorLevel Actor's level (for PROC_ATTR_REDUCE_PROC_60)
|
||||
* @param weaponSpeed Weapon speed (for PPM calculation)
|
||||
* @param chanceModifier Talent/aura modifier to chance
|
||||
* @param ppmModifier Talent/aura modifier to PPM
|
||||
* @param hasDamageInfo Whether a DamageInfo is present (enables PPM)
|
||||
* @return Calculated proc chance
|
||||
*/
|
||||
static float SimulateCalcProcChance(
|
||||
SpellProcEntry const& procEntry,
|
||||
uint32 actorLevel = 80,
|
||||
uint32 weaponSpeed = 2500,
|
||||
float chanceModifier = 0.0f,
|
||||
float ppmModifier = 0.0f,
|
||||
bool hasDamageInfo = true)
|
||||
{
|
||||
float chance = procEntry.Chance;
|
||||
|
||||
// PPM calculation overrides base chance if PPM > 0 and we have DamageInfo
|
||||
if (hasDamageInfo && procEntry.ProcsPerMinute > 0.0f)
|
||||
{
|
||||
chance = CalculatePPMChance(weaponSpeed, procEntry.ProcsPerMinute, ppmModifier);
|
||||
}
|
||||
|
||||
// Apply chance modifier (SPELLMOD_CHANCE_OF_SUCCESS)
|
||||
chance += chanceModifier;
|
||||
|
||||
// Apply level 60+ reduction if attribute is set
|
||||
if (procEntry.AttributesMask & PROC_ATTR_REDUCE_PROC_60)
|
||||
{
|
||||
chance = ApplyLevel60Reduction(chance, actorLevel);
|
||||
}
|
||||
|
||||
return chance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Simulate charge consumption from ConsumeProcCharges()
|
||||
*
|
||||
* @param aura The aura stub to modify
|
||||
* @param procEntry The proc configuration
|
||||
* @return true if aura was removed (charges/stacks exhausted)
|
||||
*/
|
||||
static bool SimulateConsumeProcCharges(AuraStub* aura, SpellProcEntry const& procEntry)
|
||||
{
|
||||
if (!aura)
|
||||
return false;
|
||||
|
||||
if (procEntry.AttributesMask & PROC_ATTR_USE_STACKS_FOR_CHARGES)
|
||||
{
|
||||
return aura->ModStackAmount(-1);
|
||||
}
|
||||
else if (aura->IsUsingCharges())
|
||||
{
|
||||
aura->DropCharge();
|
||||
if (aura->GetCharges() == 0)
|
||||
{
|
||||
aura->Remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if proc is on cooldown
|
||||
*
|
||||
* @param aura The aura stub
|
||||
* @param now Current time point
|
||||
* @return true if proc is blocked by cooldown
|
||||
*/
|
||||
static bool IsProcOnCooldown(AuraStub const* aura, std::chrono::steady_clock::time_point now)
|
||||
{
|
||||
if (!aura)
|
||||
return false;
|
||||
return aura->IsProcOnCooldown(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply proc cooldown to aura
|
||||
*
|
||||
* @param aura The aura stub
|
||||
* @param now Current time point
|
||||
* @param cooldownMs Cooldown duration in milliseconds
|
||||
*/
|
||||
static void ApplyProcCooldown(AuraStub* aura, std::chrono::steady_clock::time_point now, uint32 cooldownMs)
|
||||
{
|
||||
if (!aura || cooldownMs == 0)
|
||||
return;
|
||||
aura->AddProcCooldown(now + std::chrono::milliseconds(cooldownMs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if spell has mana cost (for PROC_ATTR_REQ_MANA_COST)
|
||||
*
|
||||
* @param spellInfo The spell info to check
|
||||
* @return true if spell has mana cost > 0
|
||||
*/
|
||||
static bool SpellHasManaCost(SpellInfo const* spellInfo)
|
||||
{
|
||||
if (!spellInfo)
|
||||
return false;
|
||||
return spellInfo->ManaCost > 0 || spellInfo->ManaCostPercentage > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get common weapon speeds for testing
|
||||
*/
|
||||
static constexpr uint32 WEAPON_SPEED_FAST_DAGGER = 1400; // 1.4 sec
|
||||
static constexpr uint32 WEAPON_SPEED_NORMAL_SWORD = 2500; // 2.5 sec
|
||||
static constexpr uint32 WEAPON_SPEED_SLOW_2H = 3300; // 3.3 sec
|
||||
static constexpr uint32 WEAPON_SPEED_VERY_SLOW = 3800; // 3.8 sec
|
||||
static constexpr uint32 WEAPON_SPEED_STAFF = 3600; // 3.6 sec (common feral staff)
|
||||
|
||||
/**
|
||||
* @brief Shapeshift form base attack speeds (from SpellShapeshiftForm.dbc)
|
||||
*/
|
||||
static constexpr uint32 FORM_SPEED_CAT = 1000; // Cat Form: 1.0 sec
|
||||
static constexpr uint32 FORM_SPEED_BEAR = 2500; // Bear/Dire Bear: 2.5 sec
|
||||
|
||||
/**
|
||||
* @brief Simulate effective procs per minute
|
||||
*
|
||||
* Given a per-swing chance and the actual swing interval, calculate
|
||||
* how many procs occur per minute on average.
|
||||
*
|
||||
* @param chancePerSwing Proc chance per swing (0-100+)
|
||||
* @param actualSwingSpeedMs Actual time between swings in milliseconds
|
||||
* @return Average procs per minute
|
||||
*/
|
||||
static float CalculateEffectivePPM(float chancePerSwing, uint32 actualSwingSpeedMs)
|
||||
{
|
||||
if (actualSwingSpeedMs == 0)
|
||||
return 0.0f;
|
||||
float swingsPerMinute = 60000.0f / static_cast<float>(actualSwingSpeedMs);
|
||||
return swingsPerMinute * (chancePerSwing / 100.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Common PPM values from spell_proc database
|
||||
*/
|
||||
static constexpr float PPM_OMEN_OF_CLARITY = 6.0f;
|
||||
static constexpr float PPM_JUDGEMENT_OF_LIGHT = 15.0f;
|
||||
static constexpr float PPM_WINDFURY_WEAPON = 2.0f;
|
||||
|
||||
// =============================================================================
|
||||
// Triggered Spell Filtering - simulates SpellAuras.cpp:2191-2209
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Auto-attack proc flag mask (hunter auto-shot, wands exception)
|
||||
* These triggered spells are allowed to proc without TRIGGERED_CAN_PROC
|
||||
*/
|
||||
static constexpr uint32 AUTO_ATTACK_PROC_FLAG_MASK =
|
||||
PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK |
|
||||
PROC_FLAG_DONE_RANGED_AUTO_ATTACK | PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK;
|
||||
|
||||
/**
|
||||
* @brief Configuration for simulating triggered spell filtering
|
||||
*/
|
||||
struct TriggeredSpellConfig
|
||||
{
|
||||
bool isTriggered = false; // Spell::IsTriggered()
|
||||
bool auraHasCanProcFromProcs = false; // SPELL_ATTR3_CAN_PROC_FROM_PROCS on proc aura
|
||||
bool spellHasNotAProc = false; // SPELL_ATTR3_NOT_A_PROC on triggering spell
|
||||
uint32 triggeredByAuraSpellId = 0; // GetTriggeredByAuraSpellInfo()->Id
|
||||
uint32 procAuraSpellId = 0; // The aura's spell ID (for self-loop check)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simulate triggered spell filtering
|
||||
* Implements the self-loop prevention and triggered spell blocking from SpellAuras.cpp
|
||||
*
|
||||
* @param config Configuration for the triggered spell
|
||||
* @param procEntry The proc entry being checked
|
||||
* @param eventTypeMask The event type mask from ProcEventInfo
|
||||
* @return true if proc should be blocked (return 0), false if allowed
|
||||
*/
|
||||
static bool ShouldBlockTriggeredSpell(
|
||||
TriggeredSpellConfig const& config,
|
||||
SpellProcEntry const& procEntry,
|
||||
uint32 eventTypeMask)
|
||||
{
|
||||
// Self-loop prevention: block if triggered by the same aura
|
||||
// SpellAuras.cpp:2191-2192
|
||||
if (config.triggeredByAuraSpellId != 0 &&
|
||||
config.triggeredByAuraSpellId == config.procAuraSpellId)
|
||||
{
|
||||
return true; // Block: self-loop detected
|
||||
}
|
||||
|
||||
// Check if triggered spell filtering applies
|
||||
// SpellAuras.cpp:2195-2208
|
||||
if (!config.auraHasCanProcFromProcs &&
|
||||
!(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) &&
|
||||
!(eventTypeMask & AUTO_ATTACK_PROC_FLAG_MASK))
|
||||
{
|
||||
// Filter triggered spells unless they have NOT_A_PROC
|
||||
if (config.isTriggered && !config.spellHasNotAProc)
|
||||
{
|
||||
return true; // Block: triggered spell without exceptions
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Allow proc
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DisableEffectsMask - simulates SpellAuras.cpp:2244-2258
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Apply DisableEffectsMask to get final proc effect mask
|
||||
*
|
||||
* @param initialMask Initial effect mask (usually 0x07 for all 3 effects)
|
||||
* @param disableEffectsMask Mask of effects to disable
|
||||
* @return Resulting effect mask after applying disable mask
|
||||
*/
|
||||
static uint8 ApplyDisableEffectsMask(uint8 initialMask, uint32 disableEffectsMask)
|
||||
{
|
||||
uint8 result = initialMask;
|
||||
for (uint8 i = 0; i < 3; ++i)
|
||||
{
|
||||
if (disableEffectsMask & (1u << i))
|
||||
result &= ~(1 << i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if proc should be blocked due to all effects being disabled
|
||||
*
|
||||
* @param initialMask Initial effect mask
|
||||
* @param disableEffectsMask Mask of effects to disable
|
||||
* @return true if all effects disabled (proc blocked), false otherwise
|
||||
*/
|
||||
static bool ShouldBlockDueToDisabledEffects(uint8 initialMask, uint32 disableEffectsMask)
|
||||
{
|
||||
return ApplyDisableEffectsMask(initialMask, disableEffectsMask) == 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PPM Modifier Simulation - simulates Unit.cpp:10378-10390
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief PPM modifier configuration for testing SPELLMOD_PROC_PER_MINUTE
|
||||
*/
|
||||
struct PPMModifierConfig
|
||||
{
|
||||
float flatModifier = 0.0f; // Additive PPM modifier
|
||||
float pctModifier = 1.0f; // Multiplicative PPM modifier (1.0 = no change)
|
||||
bool hasSpellModOwner = true; // Whether GetSpellModOwner() returns valid player
|
||||
bool hasSpellProto = true; // Whether spellProto is provided
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Calculate PPM chance with spell modifiers
|
||||
* Simulates GetPPMProcChance() with SPELLMOD_PROC_PER_MINUTE
|
||||
*/
|
||||
static float CalculatePPMChanceWithModifiers(
|
||||
uint32 weaponSpeed,
|
||||
float basePPM,
|
||||
PPMModifierConfig const& modConfig)
|
||||
{
|
||||
if (basePPM <= 0.0f)
|
||||
return 0.0f;
|
||||
|
||||
float ppm = basePPM;
|
||||
|
||||
// Apply modifiers only if we have spell proto and spell mod owner
|
||||
if (modConfig.hasSpellProto && modConfig.hasSpellModOwner)
|
||||
{
|
||||
// Apply flat modifier first (SPELLMOD_FLAT)
|
||||
ppm += modConfig.flatModifier;
|
||||
// Apply percent modifier (SPELLMOD_PCT)
|
||||
ppm *= modConfig.pctModifier;
|
||||
}
|
||||
|
||||
return (static_cast<float>(weaponSpeed) * ppm) / 600.0f;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Equipment Requirements - simulates SpellAuras.cpp:2260-2298
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Item classes for equipment requirement checking
|
||||
*/
|
||||
static constexpr int32 ITEM_CLASS_WEAPON = 2;
|
||||
static constexpr int32 ITEM_CLASS_ARMOR = 4;
|
||||
static constexpr int32 ITEM_CLASS_ANY = -1; // No requirement
|
||||
|
||||
/**
|
||||
* @brief Attack types for weapon slot mapping
|
||||
*/
|
||||
static constexpr uint8 BASE_ATTACK = 0;
|
||||
static constexpr uint8 OFF_ATTACK = 1;
|
||||
static constexpr uint8 RANGED_ATTACK = 2;
|
||||
|
||||
/**
|
||||
* @brief Configuration for simulating equipment requirements
|
||||
*/
|
||||
struct EquipmentConfig
|
||||
{
|
||||
bool isPassive = true; // Aura::IsPassive()
|
||||
bool isPlayer = true; // Target is player
|
||||
int32 equippedItemClass = ITEM_CLASS_ANY; // SpellInfo::EquippedItemClass
|
||||
int32 equippedItemSubClassMask = 0; // SpellInfo::EquippedItemSubClassMask
|
||||
bool hasNoEquipRequirementAttr = false; // SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT
|
||||
uint8 attackType = BASE_ATTACK; // Attack type for weapon slot mapping
|
||||
bool isInFeralForm = false; // Player::IsInFeralForm()
|
||||
bool hasEquippedItem = true; // Item is equipped in the slot
|
||||
bool itemIsBroken = false; // Item::IsBroken()
|
||||
bool itemFitsRequirements = true; // Item::IsFitToSpellRequirements()
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simulate equipment requirement check
|
||||
* Returns true if proc should be blocked due to equipment requirements
|
||||
*
|
||||
* @param config Equipment configuration
|
||||
* @return true if proc should be blocked
|
||||
*/
|
||||
static bool ShouldBlockDueToEquipment(EquipmentConfig const& config)
|
||||
{
|
||||
// Only check for passive player auras with equipment requirements
|
||||
if (!config.isPassive || !config.isPlayer || config.equippedItemClass == ITEM_CLASS_ANY)
|
||||
return false;
|
||||
|
||||
// SPELL_ATTR3_NO_PROC_EQUIP_REQUIREMENT bypasses check
|
||||
if (config.hasNoEquipRequirementAttr)
|
||||
return false;
|
||||
|
||||
// Feral form blocks weapon procs
|
||||
if (config.equippedItemClass == ITEM_CLASS_WEAPON && config.isInFeralForm)
|
||||
return true;
|
||||
|
||||
// No item equipped in the required slot
|
||||
if (!config.hasEquippedItem)
|
||||
return true;
|
||||
|
||||
// Item is broken
|
||||
if (config.itemIsBroken)
|
||||
return true;
|
||||
|
||||
// Item doesn't fit spell requirements (wrong subclass, etc.)
|
||||
if (!config.itemFitsRequirements)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get equipment slot for attack type
|
||||
*
|
||||
* @param attackType Attack type (BASE_ATTACK, OFF_ATTACK, RANGED_ATTACK)
|
||||
* @return Equipment slot index (simulated)
|
||||
*/
|
||||
static uint8 GetWeaponSlotForAttackType(uint8 attackType)
|
||||
{
|
||||
switch (attackType)
|
||||
{
|
||||
case BASE_ATTACK:
|
||||
return 15; // EQUIPMENT_SLOT_MAINHAND
|
||||
case OFF_ATTACK:
|
||||
return 16; // EQUIPMENT_SLOT_OFFHAND
|
||||
case RANGED_ATTACK:
|
||||
return 17; // EQUIPMENT_SLOT_RANGED
|
||||
default:
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Conditions System - simulates SpellAuras.cpp:2232-2236
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Configuration for simulating conditions system
|
||||
*/
|
||||
struct ConditionsConfig
|
||||
{
|
||||
bool hasConditions = false; // ConditionMgr has conditions for this spell
|
||||
bool conditionsMet = true; // All conditions are satisfied
|
||||
uint32 sourceType = 24; // CONDITION_SOURCE_TYPE_SPELL_PROC
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simulate conditions check
|
||||
* Returns true if proc should be blocked due to conditions
|
||||
*
|
||||
* @param config Conditions configuration
|
||||
* @return true if proc should be blocked
|
||||
*/
|
||||
static bool ShouldBlockDueToConditions(ConditionsConfig const& config)
|
||||
{
|
||||
// No conditions configured - allow proc
|
||||
if (!config.hasConditions)
|
||||
return false;
|
||||
|
||||
// Check if conditions are met
|
||||
return !config.conditionsMet;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Test context for proc simulation scenarios
|
||||
*/
|
||||
class ProcTestScenario
|
||||
{
|
||||
public:
|
||||
ProcTestScenario() : _now(std::chrono::steady_clock::now()) {}
|
||||
|
||||
// Time control
|
||||
void AdvanceTime(std::chrono::milliseconds duration)
|
||||
{
|
||||
_now += duration;
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point GetNow() const { return _now; }
|
||||
|
||||
// Actor configuration
|
||||
UnitStub& GetActor() { return _actor; }
|
||||
UnitStub const& GetActor() const { return _actor; }
|
||||
|
||||
ProcTestScenario& WithActorLevel(uint8_t level)
|
||||
{
|
||||
_actor.SetLevel(level);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcTestScenario& WithWeaponSpeed(uint8_t attackType, uint32_t speed)
|
||||
{
|
||||
_actor.SetAttackTime(attackType, speed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Aura configuration
|
||||
std::unique_ptr<AuraStub>& GetAura() { return _aura; }
|
||||
|
||||
ProcTestScenario& WithAura(uint32_t spellId, uint8_t charges = 0, uint8_t stacks = 1)
|
||||
{
|
||||
_aura = std::make_unique<AuraStub>(spellId);
|
||||
_aura->SetCharges(charges);
|
||||
_aura->SetUsingCharges(charges > 0);
|
||||
_aura->SetStackAmount(stacks);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Simulate proc and return whether it triggered
|
||||
bool SimulateProc(SpellProcEntry const& procEntry, float rollResult = 0.0f)
|
||||
{
|
||||
if (!_aura)
|
||||
return false;
|
||||
|
||||
// Check cooldown
|
||||
if (ProcChanceTestHelper::IsProcOnCooldown(_aura.get(), _now))
|
||||
return false;
|
||||
|
||||
// Calculate chance
|
||||
float chance = ProcChanceTestHelper::SimulateCalcProcChance(
|
||||
procEntry,
|
||||
_actor.GetLevel(),
|
||||
_actor.GetAttackTime(0));
|
||||
|
||||
// Roll check (rollResult of 0 means always pass)
|
||||
if (rollResult > 0.0f && rollResult > chance)
|
||||
return false;
|
||||
|
||||
// Apply cooldown if set
|
||||
if (procEntry.Cooldown.count() > 0)
|
||||
{
|
||||
ProcChanceTestHelper::ApplyProcCooldown(_aura.get(), _now,
|
||||
static_cast<uint32>(procEntry.Cooldown.count()));
|
||||
}
|
||||
|
||||
// Consume charges
|
||||
ProcChanceTestHelper::SimulateConsumeProcCharges(_aura.get(), procEntry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::steady_clock::time_point _now;
|
||||
UnitStub _actor;
|
||||
std::unique_ptr<AuraStub> _aura;
|
||||
};
|
||||
|
||||
#endif // AZEROTHCORE_PROC_CHANCE_TEST_HELPER_H
|
||||
235
src/test/mocks/ProcEventInfoHelper.h
Normal file
235
src/test/mocks/ProcEventInfoHelper.h
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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_PROC_EVENT_INFO_HELPER_H
|
||||
#define AZEROTHCORE_PROC_EVENT_INFO_HELPER_H
|
||||
|
||||
#include "SpellMgr.h"
|
||||
#include "Unit.h"
|
||||
|
||||
/**
|
||||
* @brief Builder class for creating ProcEventInfo test instances
|
||||
*
|
||||
* This helper allows easy construction of ProcEventInfo objects for unit testing
|
||||
* the proc system without requiring full game objects.
|
||||
*/
|
||||
class ProcEventInfoBuilder
|
||||
{
|
||||
public:
|
||||
ProcEventInfoBuilder()
|
||||
: _actor(nullptr), _actionTarget(nullptr), _procTarget(nullptr),
|
||||
_typeMask(0), _spellTypeMask(0), _spellPhaseMask(0), _hitMask(0),
|
||||
_spell(nullptr), _damageInfo(nullptr), _healInfo(nullptr),
|
||||
_triggeredByAuraSpell(nullptr), _procAuraEffectIndex(-1) {}
|
||||
|
||||
ProcEventInfoBuilder& WithActor(Unit* actor)
|
||||
{
|
||||
_actor = actor;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithActionTarget(Unit* target)
|
||||
{
|
||||
_actionTarget = target;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithProcTarget(Unit* target)
|
||||
{
|
||||
_procTarget = target;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithTypeMask(uint32 typeMask)
|
||||
{
|
||||
_typeMask = typeMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithSpellTypeMask(uint32 spellTypeMask)
|
||||
{
|
||||
_spellTypeMask = spellTypeMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithSpellPhaseMask(uint32 spellPhaseMask)
|
||||
{
|
||||
_spellPhaseMask = spellPhaseMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithHitMask(uint32 hitMask)
|
||||
{
|
||||
_hitMask = hitMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithSpell(Spell const* spell)
|
||||
{
|
||||
_spell = spell;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithDamageInfo(DamageInfo* damageInfo)
|
||||
{
|
||||
_damageInfo = damageInfo;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithHealInfo(HealInfo* healInfo)
|
||||
{
|
||||
_healInfo = healInfo;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithTriggeredByAuraSpell(SpellInfo const* spellInfo)
|
||||
{
|
||||
_triggeredByAuraSpell = spellInfo;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfoBuilder& WithProcAuraEffectIndex(int8 index)
|
||||
{
|
||||
_procAuraEffectIndex = index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ProcEventInfo Build()
|
||||
{
|
||||
return ProcEventInfo(_actor, _actionTarget, _procTarget, _typeMask,
|
||||
_spellTypeMask, _spellPhaseMask, _hitMask,
|
||||
_spell, _damageInfo, _healInfo,
|
||||
_triggeredByAuraSpell, _procAuraEffectIndex);
|
||||
}
|
||||
|
||||
private:
|
||||
Unit* _actor;
|
||||
Unit* _actionTarget;
|
||||
Unit* _procTarget;
|
||||
uint32 _typeMask;
|
||||
uint32 _spellTypeMask;
|
||||
uint32 _spellPhaseMask;
|
||||
uint32 _hitMask;
|
||||
Spell const* _spell;
|
||||
DamageInfo* _damageInfo;
|
||||
HealInfo* _healInfo;
|
||||
SpellInfo const* _triggeredByAuraSpell;
|
||||
int8 _procAuraEffectIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder class for creating SpellProcEntry test instances
|
||||
*
|
||||
* This helper allows easy construction of SpellProcEntry objects for unit testing
|
||||
* the proc system.
|
||||
*/
|
||||
class SpellProcEntryBuilder
|
||||
{
|
||||
public:
|
||||
SpellProcEntryBuilder()
|
||||
{
|
||||
_entry = {};
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithSchoolMask(uint32 schoolMask)
|
||||
{
|
||||
_entry.SchoolMask = schoolMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithSpellFamilyName(uint32 familyName)
|
||||
{
|
||||
_entry.SpellFamilyName = familyName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithSpellFamilyMask(flag96 familyMask)
|
||||
{
|
||||
_entry.SpellFamilyMask = familyMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithProcFlags(uint32 procFlags)
|
||||
{
|
||||
_entry.ProcFlags = procFlags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithSpellTypeMask(uint32 spellTypeMask)
|
||||
{
|
||||
_entry.SpellTypeMask = spellTypeMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithSpellPhaseMask(uint32 spellPhaseMask)
|
||||
{
|
||||
_entry.SpellPhaseMask = spellPhaseMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithHitMask(uint32 hitMask)
|
||||
{
|
||||
_entry.HitMask = hitMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithAttributesMask(uint32 attributesMask)
|
||||
{
|
||||
_entry.AttributesMask = attributesMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithDisableEffectsMask(uint32 disableEffectsMask)
|
||||
{
|
||||
_entry.DisableEffectsMask = disableEffectsMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithProcsPerMinute(float ppm)
|
||||
{
|
||||
_entry.ProcsPerMinute = ppm;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithChance(float chance)
|
||||
{
|
||||
_entry.Chance = chance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithCooldown(Milliseconds cooldown)
|
||||
{
|
||||
_entry.Cooldown = cooldown;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntryBuilder& WithCharges(uint32 charges)
|
||||
{
|
||||
_entry.Charges = charges;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellProcEntry Build() const
|
||||
{
|
||||
return _entry;
|
||||
}
|
||||
|
||||
private:
|
||||
SpellProcEntry _entry;
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_PROC_EVENT_INFO_HELPER_H
|
||||
215
src/test/mocks/SpellInfoTestHelper.h
Normal file
215
src/test/mocks/SpellInfoTestHelper.h
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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_SPELL_INFO_TEST_HELPER_H
|
||||
#define AZEROTHCORE_SPELL_INFO_TEST_HELPER_H
|
||||
|
||||
#include "SpellInfo.h"
|
||||
#include "SharedDefines.h"
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @brief Helper class to create SpellEntry test instances
|
||||
*
|
||||
* This creates a SpellEntry with sensible defaults for unit testing.
|
||||
*/
|
||||
class TestSpellEntryHelper
|
||||
{
|
||||
public:
|
||||
TestSpellEntryHelper()
|
||||
{
|
||||
// Zero initialize all fields
|
||||
std::memset(&_entry, 0, sizeof(_entry));
|
||||
|
||||
// Set safe defaults
|
||||
_entry.EquippedItemClass = -1;
|
||||
_entry.SchoolMask = SPELL_SCHOOL_MASK_NORMAL;
|
||||
|
||||
// Initialize empty strings
|
||||
for (auto& name : _entry.SpellName)
|
||||
name = "";
|
||||
for (auto& rank : _entry.Rank)
|
||||
rank = "";
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithId(uint32 id)
|
||||
{
|
||||
_entry.Id = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithSpellFamilyName(uint32 familyName)
|
||||
{
|
||||
_entry.SpellFamilyName = familyName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithSpellFamilyFlags(uint32 flag0, uint32 flag1 = 0, uint32 flag2 = 0)
|
||||
{
|
||||
_entry.SpellFamilyFlags[0] = flag0;
|
||||
_entry.SpellFamilyFlags[1] = flag1;
|
||||
_entry.SpellFamilyFlags[2] = flag2;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithSchoolMask(uint32 schoolMask)
|
||||
{
|
||||
_entry.SchoolMask = schoolMask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithProcFlags(uint32 procFlags)
|
||||
{
|
||||
_entry.ProcFlags = procFlags;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithProcChance(uint32 procChance)
|
||||
{
|
||||
_entry.ProcChance = procChance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithProcCharges(uint32 procCharges)
|
||||
{
|
||||
_entry.ProcCharges = procCharges;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithDmgClass(uint32 dmgClass)
|
||||
{
|
||||
_entry.DmgClass = dmgClass;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithEffect(uint8 effIndex, uint32 effect, uint32 auraType = 0)
|
||||
{
|
||||
if (effIndex < MAX_SPELL_EFFECTS)
|
||||
{
|
||||
_entry.Effect[effIndex] = effect;
|
||||
_entry.EffectApplyAuraName[effIndex] = auraType;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithEffectTriggerSpell(uint8 effIndex, uint32 triggerSpell)
|
||||
{
|
||||
if (effIndex < MAX_SPELL_EFFECTS)
|
||||
{
|
||||
_entry.EffectTriggerSpell[effIndex] = triggerSpell;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellEntry const* Get() const
|
||||
{
|
||||
return &_entry;
|
||||
}
|
||||
|
||||
private:
|
||||
SpellEntry _entry;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder class for creating SpellInfo test instances
|
||||
*
|
||||
* This helper allows easy construction of SpellInfo objects for unit testing
|
||||
* without requiring DBC data.
|
||||
*/
|
||||
class SpellInfoBuilder
|
||||
{
|
||||
public:
|
||||
SpellInfoBuilder() : _entryHelper() {}
|
||||
|
||||
SpellInfoBuilder& WithId(uint32 id)
|
||||
{
|
||||
_entryHelper.WithId(id);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithSpellFamilyName(uint32 familyName)
|
||||
{
|
||||
_entryHelper.WithSpellFamilyName(familyName);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithSpellFamilyFlags(uint32 flag0, uint32 flag1 = 0, uint32 flag2 = 0)
|
||||
{
|
||||
_entryHelper.WithSpellFamilyFlags(flag0, flag1, flag2);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithSchoolMask(uint32 schoolMask)
|
||||
{
|
||||
_entryHelper.WithSchoolMask(schoolMask);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithProcFlags(uint32 procFlags)
|
||||
{
|
||||
_entryHelper.WithProcFlags(procFlags);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithProcChance(uint32 procChance)
|
||||
{
|
||||
_entryHelper.WithProcChance(procChance);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithProcCharges(uint32 procCharges)
|
||||
{
|
||||
_entryHelper.WithProcCharges(procCharges);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithDmgClass(uint32 dmgClass)
|
||||
{
|
||||
_entryHelper.WithDmgClass(dmgClass);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithEffect(uint8 effIndex, uint32 effect, uint32 auraType = 0)
|
||||
{
|
||||
_entryHelper.WithEffect(effIndex, effect, auraType);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithEffectTriggerSpell(uint8 effIndex, uint32 triggerSpell)
|
||||
{
|
||||
_entryHelper.WithEffectTriggerSpell(effIndex, triggerSpell);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Builds and returns a SpellInfo pointer
|
||||
// Note: Caller is responsible for lifetime management
|
||||
SpellInfo* Build()
|
||||
{
|
||||
return new SpellInfo(_entryHelper.Get());
|
||||
}
|
||||
|
||||
// Builds and returns a managed SpellInfo pointer
|
||||
std::unique_ptr<SpellInfo> BuildUnique()
|
||||
{
|
||||
return std::unique_ptr<SpellInfo>(new SpellInfo(_entryHelper.Get()));
|
||||
}
|
||||
|
||||
private:
|
||||
TestSpellEntryHelper _entryHelper;
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_SPELL_INFO_TEST_HELPER_H
|
||||
244
src/test/mocks/UnitStub.h
Normal file
244
src/test/mocks/UnitStub.h
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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_UNIT_STUB_H
|
||||
#define AZEROTHCORE_UNIT_STUB_H
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class SpellInfo;
|
||||
class Aura;
|
||||
class AuraEffect;
|
||||
class Item;
|
||||
|
||||
/**
|
||||
* @brief Lightweight stub for Unit proc-related functionality
|
||||
*
|
||||
* This stub provides controlled behavior for testing proc scripts
|
||||
* without requiring the full Unit hierarchy.
|
||||
*/
|
||||
class UnitStub
|
||||
{
|
||||
public:
|
||||
UnitStub() = default;
|
||||
virtual ~UnitStub() = default;
|
||||
|
||||
// Identity
|
||||
virtual bool IsPlayer() const { return _isPlayer; }
|
||||
virtual bool IsAlive() const { return _isAlive; }
|
||||
virtual bool IsFriendlyTo(UnitStub const* unit) const { return _isFriendly; }
|
||||
|
||||
void SetIsPlayer(bool isPlayer) { _isPlayer = isPlayer; }
|
||||
void SetIsAlive(bool isAlive) { _isAlive = isAlive; }
|
||||
void SetIsFriendly(bool isFriendly) { _isFriendly = isFriendly; }
|
||||
|
||||
// Aura management
|
||||
virtual bool HasAura(uint32_t spellId) const
|
||||
{
|
||||
return _auras.find(spellId) != _auras.end();
|
||||
}
|
||||
|
||||
virtual void AddAuraStub(uint32_t spellId)
|
||||
{
|
||||
_auras[spellId] = true;
|
||||
}
|
||||
|
||||
virtual void RemoveAuraStub(uint32_t spellId)
|
||||
{
|
||||
_auras.erase(spellId);
|
||||
}
|
||||
|
||||
// Spell casting tracking
|
||||
struct CastRecord
|
||||
{
|
||||
uint32_t spellId;
|
||||
bool triggered;
|
||||
int32_t bp0;
|
||||
int32_t bp1;
|
||||
int32_t bp2;
|
||||
};
|
||||
|
||||
virtual void RecordCast(uint32_t spellId, bool triggered = true,
|
||||
int32_t bp0 = 0, int32_t bp1 = 0, int32_t bp2 = 0)
|
||||
{
|
||||
_castHistory.push_back({spellId, triggered, bp0, bp1, bp2});
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<CastRecord> const& GetCastHistory() const { return _castHistory; }
|
||||
|
||||
[[nodiscard]] bool WasCast(uint32_t spellId) const
|
||||
{
|
||||
for (auto const& record : _castHistory)
|
||||
{
|
||||
if (record.spellId == spellId)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CountCasts(uint32_t spellId) const
|
||||
{
|
||||
size_t count = 0;
|
||||
for (auto const& record : _castHistory)
|
||||
{
|
||||
if (record.spellId == spellId)
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void ClearCastHistory() { _castHistory.clear(); }
|
||||
|
||||
// Health/Power
|
||||
virtual uint32_t GetMaxHealth() const { return _maxHealth; }
|
||||
virtual uint32_t GetHealth() const { return _health; }
|
||||
virtual uint32_t CountPctFromMaxHealth(int32_t pct) const
|
||||
{
|
||||
return (_maxHealth * static_cast<uint32_t>(pct)) / 100;
|
||||
}
|
||||
|
||||
void SetMaxHealth(uint32_t maxHealth) { _maxHealth = maxHealth; }
|
||||
void SetHealth(uint32_t health) { _health = health; }
|
||||
|
||||
// Weapon speed for PPM calculations
|
||||
virtual uint32_t GetAttackTime(uint8_t attType) const
|
||||
{
|
||||
return _attackTimes.count(attType) ? _attackTimes.at(attType) : 2000;
|
||||
}
|
||||
|
||||
void SetAttackTime(uint8_t attType, uint32_t time)
|
||||
{
|
||||
_attackTimes[attType] = time;
|
||||
}
|
||||
|
||||
// PPM modifier tracking for proc tests
|
||||
// Simulates Player::ApplySpellMod(spellId, SPELLMOD_PROC_PER_MINUTE, ppm)
|
||||
void SetPPMModifier(uint32_t spellId, float modifier)
|
||||
{
|
||||
_ppmModifiers[spellId] = modifier;
|
||||
}
|
||||
|
||||
void ClearPPMModifiers()
|
||||
{
|
||||
_ppmModifiers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate PPM proc chance with modifiers
|
||||
* Mimics Unit::GetPPMProcChance() formula: (WeaponSpeed * PPM) / 600.0f
|
||||
*/
|
||||
virtual float GetPPMProcChance(uint32_t weaponSpeed, float ppm, uint32_t spellId = 0) const
|
||||
{
|
||||
if (ppm <= 0.0f)
|
||||
return 0.0f;
|
||||
|
||||
// Apply PPM modifier if set for this spell
|
||||
float modifiedPPM = ppm;
|
||||
if (spellId > 0 && _ppmModifiers.count(spellId))
|
||||
modifiedPPM += _ppmModifiers.at(spellId);
|
||||
|
||||
return (static_cast<float>(weaponSpeed) * modifiedPPM) / 600.0f;
|
||||
}
|
||||
|
||||
// Chance modifier tracking for proc tests
|
||||
// Simulates Player::ApplySpellMod(spellId, SPELLMOD_CHANCE_OF_SUCCESS, chance)
|
||||
void SetChanceModifier(uint32_t spellId, float modifier)
|
||||
{
|
||||
_chanceModifiers[spellId] = modifier;
|
||||
}
|
||||
|
||||
void ClearChanceModifiers()
|
||||
{
|
||||
_chanceModifiers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply chance modifier for a spell
|
||||
*/
|
||||
float ApplyChanceModifier(uint32_t spellId, float baseChance) const
|
||||
{
|
||||
if (spellId > 0 && _chanceModifiers.count(spellId))
|
||||
return baseChance + _chanceModifiers.at(spellId);
|
||||
return baseChance;
|
||||
}
|
||||
|
||||
// Cooldowns
|
||||
virtual bool HasSpellCooldown(uint32_t spellId) const
|
||||
{
|
||||
return _cooldowns.find(spellId) != _cooldowns.end();
|
||||
}
|
||||
|
||||
virtual void AddSpellCooldown(uint32_t spellId)
|
||||
{
|
||||
_cooldowns[spellId] = true;
|
||||
}
|
||||
|
||||
virtual void RemoveSpellCooldown(uint32_t spellId)
|
||||
{
|
||||
_cooldowns.erase(spellId);
|
||||
}
|
||||
|
||||
// Class/Level
|
||||
virtual uint8_t GetClass() const { return _class; }
|
||||
virtual uint8_t GetLevel() const { return _level; }
|
||||
|
||||
void SetClass(uint8_t unitClass) { _class = unitClass; }
|
||||
void SetLevel(uint8_t level) { _level = level; }
|
||||
|
||||
private:
|
||||
bool _isPlayer = false;
|
||||
bool _isAlive = true;
|
||||
bool _isFriendly = false;
|
||||
|
||||
std::map<uint32_t, bool> _auras;
|
||||
std::vector<CastRecord> _castHistory;
|
||||
std::map<uint32_t, bool> _cooldowns;
|
||||
std::map<uint8_t, uint32_t> _attackTimes;
|
||||
std::map<uint32_t, float> _ppmModifiers; // PPM modifiers by spell ID
|
||||
std::map<uint32_t, float> _chanceModifiers; // Chance modifiers by spell ID
|
||||
|
||||
uint32_t _maxHealth = 10000;
|
||||
uint32_t _health = 10000;
|
||||
uint8_t _class = 1; // Warrior by default
|
||||
uint8_t _level = 80;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief GMock-enabled Unit stub for verification
|
||||
*/
|
||||
class MockUnitStub : public UnitStub
|
||||
{
|
||||
public:
|
||||
MOCK_METHOD(bool, IsPlayer, (), (const, override));
|
||||
MOCK_METHOD(bool, IsAlive, (), (const, override));
|
||||
MOCK_METHOD(bool, IsFriendlyTo, (UnitStub const* unit), (const, override));
|
||||
MOCK_METHOD(bool, HasAura, (uint32_t spellId), (const, override));
|
||||
MOCK_METHOD(uint32_t, GetMaxHealth, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, GetHealth, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, CountPctFromMaxHealth, (int32_t pct), (const, override));
|
||||
MOCK_METHOD(uint32_t, GetAttackTime, (uint8_t attType), (const, override));
|
||||
MOCK_METHOD(bool, HasSpellCooldown, (uint32_t spellId), (const, override));
|
||||
MOCK_METHOD(uint8_t, GetClass, (), (const, override));
|
||||
MOCK_METHOD(uint8_t, GetLevel, (), (const, override));
|
||||
MOCK_METHOD(float, GetPPMProcChance, (uint32_t weaponSpeed, float ppm, uint32_t spellId), (const, override));
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_UNIT_STUB_H
|
||||
Reference in New Issue
Block a user