mirror of
https://gitcode.com/GitHub_Trending/az/azerothcore-wotlk.git
synced 2026-07-11 03:13:10 +00:00
feat(Test): Add integration test framework for game systems (#25035)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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_INTEGRATION_TEST_FIXTURE_H
|
||||
#define AZEROTHCORE_INTEGRATION_TEST_FIXTURE_H
|
||||
|
||||
#include "TestMap.h"
|
||||
#include "TestPlayer.h"
|
||||
#include "TestCreature.h"
|
||||
#include "WorldMock.h"
|
||||
#include "WorldSession.h"
|
||||
#include "DBCStores.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <vector>
|
||||
|
||||
using namespace testing;
|
||||
|
||||
// Faction template IDs for test creatures
|
||||
static constexpr uint32 TEST_FACTION_HOSTILE_TO_MONSTERS = 90001;
|
||||
static constexpr uint32 TEST_FACTION_HOSTILE_TO_ALL = 90002;
|
||||
|
||||
class IntegrationTestFixture : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
TestMap::EnsureDBC();
|
||||
EnsureFactionTemplates();
|
||||
|
||||
_originalWorld = sWorld.release();
|
||||
_worldMock = new NiceMock<WorldMock>();
|
||||
sWorld.reset(_worldMock);
|
||||
|
||||
static std::string emptyString;
|
||||
ON_CALL(*_worldMock, GetDataPath()).WillByDefault(ReturnRef(emptyString));
|
||||
ON_CALL(*_worldMock, GetRealmName()).WillByDefault(ReturnRef(emptyString));
|
||||
ON_CALL(*_worldMock, GetDefaultDbcLocale()).WillByDefault(Return(LOCALE_enUS));
|
||||
ON_CALL(*_worldMock, getRate(_)).WillByDefault(Return(1.0f));
|
||||
ON_CALL(*_worldMock, getBoolConfig(_)).WillByDefault(Return(false));
|
||||
ON_CALL(*_worldMock, getIntConfig(_)).WillByDefault(Return(0));
|
||||
ON_CALL(*_worldMock, getFloatConfig(_)).WillByDefault(Return(0.0f));
|
||||
ON_CALL(*_worldMock, GetPlayerSecurityLimit()).WillByDefault(Return(SEC_PLAYER));
|
||||
|
||||
_testMap = new TestMap();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
for (auto* creature : _trackedCreatures)
|
||||
{
|
||||
if (creature->IsInWorld())
|
||||
creature->RemoveFromWorld();
|
||||
delete creature;
|
||||
}
|
||||
_trackedCreatures.clear();
|
||||
|
||||
for (auto* player : _trackedPlayers)
|
||||
{
|
||||
if (player->IsInWorld())
|
||||
player->RemoveFromWorld();
|
||||
}
|
||||
_trackedPlayers.clear();
|
||||
|
||||
for (auto* tmpl : _ownedCreatureTemplates)
|
||||
delete tmpl;
|
||||
_ownedCreatureTemplates.clear();
|
||||
|
||||
delete _testMap;
|
||||
_testMap = nullptr;
|
||||
|
||||
IWorld* currentWorld = sWorld.release();
|
||||
delete currentWorld;
|
||||
_worldMock = nullptr;
|
||||
|
||||
sWorld.reset(_originalWorld);
|
||||
_originalWorld = nullptr;
|
||||
|
||||
// Intentional leaks of session/player to avoid database access in destructors
|
||||
}
|
||||
|
||||
TestPlayer* CreateTestPlayer(ObjectGuid::LowType guidLow = 1,
|
||||
std::string const& name = "TestPlayer",
|
||||
AccountTypes security = SEC_PLAYER)
|
||||
{
|
||||
auto* session = new WorldSession(guidLow, std::string(name), 0, nullptr, security,
|
||||
EXPANSION_WRATH_OF_THE_LICH_KING, 0, LOCALE_enUS, 0, false, false, 0);
|
||||
|
||||
auto* player = new TestPlayer(session);
|
||||
player->ForceInitValues(guidLow);
|
||||
session->SetPlayer(player);
|
||||
player->SetSession(session);
|
||||
player->SetMap(_testMap);
|
||||
player->AddToWorld();
|
||||
_trackedPlayers.push_back(player);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
TestCreature* CreateTestCreature(ObjectGuid::LowType guidLow, uint32 entry, uint32 factionId)
|
||||
{
|
||||
// CreatureTemplate must be heap-allocated AFTER WorldMock is installed
|
||||
// because CreatureMovementData() constructor calls sWorld->getIntConfig()
|
||||
auto* tmpl = new CreatureTemplate();
|
||||
tmpl->Entry = entry;
|
||||
tmpl->faction = factionId;
|
||||
tmpl->minlevel = 80;
|
||||
tmpl->maxlevel = 80;
|
||||
tmpl->unit_class = CLASS_WARRIOR;
|
||||
tmpl->DamageModifier = 1.0f;
|
||||
tmpl->BaseAttackTime = 2000;
|
||||
tmpl->RangeAttackTime = 2000;
|
||||
tmpl->BaseVariance = 1.0f;
|
||||
tmpl->RangeVariance = 1.0f;
|
||||
tmpl->ModHealth = 1.0f;
|
||||
_ownedCreatureTemplates.push_back(tmpl);
|
||||
|
||||
auto* creature = new TestCreature();
|
||||
creature->ForceInitValues(guidLow, entry);
|
||||
creature->SetTestMap(_testMap);
|
||||
creature->SetInWorld(true);
|
||||
creature->SetAlive(true);
|
||||
creature->SetPhase(1);
|
||||
creature->SetFaction(factionId);
|
||||
creature->SetLevel(80);
|
||||
creature->SetMaxHealth(10000);
|
||||
creature->SetHealth(10000);
|
||||
creature->InitializeThreatManager();
|
||||
_trackedCreatures.push_back(creature);
|
||||
|
||||
return creature;
|
||||
}
|
||||
|
||||
NiceMock<WorldMock>* GetWorldMock() { return _worldMock; }
|
||||
TestMap* GetTestMap() { return _testMap; }
|
||||
|
||||
private:
|
||||
static void EnsureFactionTemplates()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized)
|
||||
return;
|
||||
initialized = true;
|
||||
|
||||
// Faction 90001: "player-like" — ourMask=1 (FACTION_MASK_PLAYER), hostile to monsters (hostileMask=8)
|
||||
auto* f1 = new FactionTemplateEntry();
|
||||
std::memset(f1, 0, sizeof(FactionTemplateEntry));
|
||||
f1->ID = TEST_FACTION_HOSTILE_TO_MONSTERS;
|
||||
f1->faction = TEST_FACTION_HOSTILE_TO_MONSTERS;
|
||||
f1->ourMask = 0x1; // FACTION_MASK_PLAYER
|
||||
f1->friendlyMask = 0x1; // friendly to players
|
||||
f1->hostileMask = 0x8; // hostile to monsters
|
||||
sFactionTemplateStore.SetEntry(TEST_FACTION_HOSTILE_TO_MONSTERS, f1);
|
||||
|
||||
// Faction 90002: "monster" — ourMask=8 (FACTION_MASK_MONSTER), hostile to players (hostileMask=1)
|
||||
auto* f2 = new FactionTemplateEntry();
|
||||
std::memset(f2, 0, sizeof(FactionTemplateEntry));
|
||||
f2->ID = TEST_FACTION_HOSTILE_TO_ALL;
|
||||
f2->faction = TEST_FACTION_HOSTILE_TO_ALL;
|
||||
f2->ourMask = 0x8; // FACTION_MASK_MONSTER
|
||||
f2->friendlyMask = 0x0; // friendly to none
|
||||
f2->hostileMask = 0x1; // hostile to players
|
||||
sFactionTemplateStore.SetEntry(TEST_FACTION_HOSTILE_TO_ALL, f2);
|
||||
}
|
||||
|
||||
IWorld* _originalWorld = nullptr;
|
||||
NiceMock<WorldMock>* _worldMock = nullptr;
|
||||
TestMap* _testMap = nullptr;
|
||||
std::vector<TestPlayer*> _trackedPlayers;
|
||||
std::vector<TestCreature*> _trackedCreatures;
|
||||
std::vector<CreatureTemplate*> _ownedCreatureTemplates;
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_INTEGRATION_TEST_FIXTURE_H
|
||||
@@ -128,6 +128,13 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithEffectMiscValue(uint8 effIndex, int32 miscValue)
|
||||
{
|
||||
if (effIndex < MAX_SPELL_EFFECTS)
|
||||
_entry.EffectMiscValue[effIndex] = miscValue;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TestSpellEntryHelper& WithEffectDieSides(uint8 effIndex, int32 dieSides)
|
||||
{
|
||||
if (effIndex < MAX_SPELL_EFFECTS)
|
||||
@@ -233,6 +240,12 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithEffectMiscValue(uint8 effIndex, int32 miscValue)
|
||||
{
|
||||
_entryHelper.WithEffectMiscValue(effIndex, miscValue);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpellInfoBuilder& WithEffectDieSides(uint8 effIndex, int32 dieSides)
|
||||
{
|
||||
_entryHelper.WithEffectDieSides(effIndex, dieSides);
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
#include "DBCStores.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptDefines/AllMapScript.h"
|
||||
#include "ScriptDefines/AllSpellScript.h"
|
||||
#include "ScriptDefines/GlobalScript.h"
|
||||
#include "ScriptDefines/MiscScript.h"
|
||||
#include "ScriptDefines/PlayerScript.h"
|
||||
#include "ScriptDefines/UnitScript.h"
|
||||
#include "ScriptDefines/WorldObjectScript.h"
|
||||
#include "ScriptDefines/CommandScript.h"
|
||||
|
||||
TestMap::TestMap()
|
||||
: Map(0, 0, REGULAR_DIFFICULTY, nullptr)
|
||||
@@ -55,8 +58,11 @@ TestMap::~TestMap()
|
||||
// Initialize all script registries so CALL_ENABLED_HOOKS doesn't
|
||||
// crash on uninitialized vectors during Object/Unit/Map operations
|
||||
ScriptRegistry<AllMapScript>::InitEnabledHooksIfNeeded(ALLMAPHOOK_END);
|
||||
ScriptRegistry<AllSpellScript>::InitEnabledHooksIfNeeded(ALLSPELLHOOK_END);
|
||||
ScriptRegistry<GlobalScript>::InitEnabledHooksIfNeeded(GLOBALHOOK_END);
|
||||
ScriptRegistry<MiscScript>::InitEnabledHooksIfNeeded(MISCHOOK_END);
|
||||
ScriptRegistry<PlayerScript>::InitEnabledHooksIfNeeded(PLAYERHOOK_END);
|
||||
ScriptRegistry<UnitScript>::InitEnabledHooksIfNeeded(UNITHOOK_END);
|
||||
ScriptRegistry<WorldObjectScript>::InitEnabledHooksIfNeeded(WORLDOBJECTHOOK_END);
|
||||
ScriptRegistry<CommandSC>::InitEnabledHooksIfNeeded(ALLCOMMANDHOOK_END);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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_TEST_PLAYER_H
|
||||
#define AZEROTHCORE_TEST_PLAYER_H
|
||||
|
||||
#include "Player.h"
|
||||
|
||||
class TestPlayer : public Player
|
||||
{
|
||||
public:
|
||||
using Player::Player;
|
||||
|
||||
void UpdateObjectVisibility(bool /*forced*/ = true, bool /*fromUpdate*/ = false) override { }
|
||||
|
||||
void AddToWorld() override { Object::AddToWorld(); }
|
||||
void RemoveFromWorld() override { Object::RemoveFromWorld(); }
|
||||
|
||||
void SaveToDB(bool /*create*/, bool /*logout*/) { }
|
||||
void SaveToDB(CharacterDatabaseTransaction /*trans*/, bool /*create*/, bool /*logout*/) { }
|
||||
|
||||
void ForceInitValues(ObjectGuid::LowType guidLow = 1)
|
||||
{
|
||||
Object::_Create(guidLow, uint32(0), HighGuid::Player);
|
||||
}
|
||||
};
|
||||
|
||||
#endif //AZEROTHCORE_TEST_PLAYER_H
|
||||
@@ -15,16 +15,13 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Player.h"
|
||||
#include "TestPlayer.h"
|
||||
#include "TestMap.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldMock.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "ScriptDefines/MiscScript.h"
|
||||
#include "ScriptDefines/PlayerScript.h"
|
||||
#include "ScriptDefines/WorldObjectScript.h"
|
||||
#include "ScriptDefines/UnitScript.h"
|
||||
#include "ScriptDefines/CommandScript.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
@@ -68,25 +65,12 @@ public:
|
||||
inline static AccountTypes LastSecurity = SEC_PLAYER;
|
||||
};
|
||||
|
||||
class TestPlayer : public Player
|
||||
{
|
||||
public:
|
||||
using Player::Player;
|
||||
|
||||
void UpdateObjectVisibility(bool /*forced*/ = true, bool /*fromUpdate*/ = false) override { }
|
||||
|
||||
void ForceInitValues(ObjectGuid::LowType guidLow = 1)
|
||||
{
|
||||
Object::_Create(guidLow, uint32(0), HighGuid::Player);
|
||||
}
|
||||
};
|
||||
|
||||
class GmVisibleCommandTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
EnsureScriptRegistriesInitialized();
|
||||
TestMap::EnsureDBC();
|
||||
|
||||
TestVisibilityScript::EnsureRegistered();
|
||||
|
||||
@@ -139,20 +123,6 @@ protected:
|
||||
player->SetServerSideVisibility(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
|
||||
}
|
||||
|
||||
static void EnsureScriptRegistriesInitialized()
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (!initialized)
|
||||
{
|
||||
ScriptRegistry<MiscScript>::InitEnabledHooksIfNeeded(MISCHOOK_END);
|
||||
ScriptRegistry<WorldObjectScript>::InitEnabledHooksIfNeeded(WORLDOBJECTHOOK_END);
|
||||
ScriptRegistry<UnitScript>::InitEnabledHooksIfNeeded(UNITHOOK_END);
|
||||
ScriptRegistry<PlayerScript>::InitEnabledHooksIfNeeded(PLAYERHOOK_END);
|
||||
ScriptRegistry<CommandSC>::InitEnabledHooksIfNeeded(ALLCOMMANDHOOK_END);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
IWorld* originalWorld = nullptr;
|
||||
NiceMock<WorldMock>* worldMock = nullptr;
|
||||
WorldSession* session = nullptr;
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Unit test for the mod-omen-of-clarity glyph slot locking behavior.
|
||||
*
|
||||
* Verifies that when the "empowered omen" feature is enabled for a player,
|
||||
* the major glyph slot 5 (bit 0x20 in PLAYER_GLYPHS_ENABLED) stays locked
|
||||
* even after leveling to 80 (which normally enables that slot).
|
||||
*
|
||||
* Because the module's internal cache (s_oocEnabled) and helper functions
|
||||
* are static to its .cpp file, this test uses a test-local PlayerScript
|
||||
* that replicates the same glyph-locking logic the module uses. This
|
||||
* proves the mechanism works end-to-end through the hook system.
|
||||
*/
|
||||
|
||||
#include "TestPlayer.h"
|
||||
#include "TestMap.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldMock.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "ScriptDefines/PlayerScript.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifndef TEST_F
|
||||
#define TEST_F(fixture, name) void fixture##_##name()
|
||||
#endif
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace testing;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Constants matching the module's values
|
||||
static constexpr uint8 OOC_LOCKED_GLYPH_SLOT = 5;
|
||||
static constexpr uint32 OOC_LOCKED_SLOT_BIT = 0x20;
|
||||
|
||||
// Test-local cache mirroring the module's s_oocEnabled
|
||||
static std::unordered_set<uint32> s_testOocEnabled;
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Test PlayerScript: replicates the module's level-change
|
||||
// and quest-complete hooks for glyph slot locking.
|
||||
// -------------------------------------------------------
|
||||
class TestOocPlayerScript : public PlayerScript
|
||||
{
|
||||
public:
|
||||
TestOocPlayerScript()
|
||||
: PlayerScript("TestOocPlayerScript",
|
||||
{ PLAYERHOOK_ON_LEVEL_CHANGED,
|
||||
PLAYERHOOK_ON_PLAYER_COMPLETE_QUEST }) { }
|
||||
|
||||
void OnPlayerLevelChanged(Player* player,
|
||||
uint8 /*oldLevel*/) override
|
||||
{
|
||||
if (s_testOocEnabled.count(
|
||||
player->GetGUID().GetCounter()))
|
||||
{
|
||||
// Same logic as EnsureGlyphSlotLocked in the module
|
||||
uint32 bits = player->GetUInt32Value(
|
||||
PLAYER_GLYPHS_ENABLED);
|
||||
if (bits & OOC_LOCKED_SLOT_BIT)
|
||||
{
|
||||
player->SetUInt32Value(PLAYER_GLYPHS_ENABLED,
|
||||
bits & ~OOC_LOCKED_SLOT_BIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnPlayerCompleteQuest(Player* player,
|
||||
Quest const* /*quest*/) override
|
||||
{
|
||||
// Simplified: always enable for the completing player
|
||||
s_testOocEnabled.insert(
|
||||
player->GetGUID().GetCounter());
|
||||
|
||||
// Lock the slot (clear bit 0x20)
|
||||
uint32 bits = player->GetUInt32Value(
|
||||
PLAYER_GLYPHS_ENABLED);
|
||||
player->SetUInt32Value(PLAYER_GLYPHS_ENABLED,
|
||||
bits & ~OOC_LOCKED_SLOT_BIT);
|
||||
}
|
||||
|
||||
static void EnsureRegistered()
|
||||
{
|
||||
if (!Instance)
|
||||
Instance = new TestOocPlayerScript();
|
||||
}
|
||||
|
||||
inline static TestOocPlayerScript* Instance = nullptr;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Test fixture
|
||||
// -------------------------------------------------------
|
||||
class OmenOfClarityGlyphLockTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
TestMap::EnsureDBC();
|
||||
TestOocPlayerScript::EnsureRegistered();
|
||||
|
||||
originalWorld = sWorld.release();
|
||||
worldMock = new NiceMock<WorldMock>();
|
||||
sWorld.reset(worldMock);
|
||||
|
||||
static std::string emptyString;
|
||||
ON_CALL(*worldMock, GetDataPath())
|
||||
.WillByDefault(ReturnRef(emptyString));
|
||||
ON_CALL(*worldMock, GetRealmName())
|
||||
.WillByDefault(ReturnRef(emptyString));
|
||||
ON_CALL(*worldMock, GetDefaultDbcLocale())
|
||||
.WillByDefault(Return(LOCALE_enUS));
|
||||
ON_CALL(*worldMock, getRate(_))
|
||||
.WillByDefault(Return(1.0f));
|
||||
ON_CALL(*worldMock, getBoolConfig(_))
|
||||
.WillByDefault(Return(false));
|
||||
ON_CALL(*worldMock, getIntConfig(_))
|
||||
.WillByDefault(Return(0));
|
||||
ON_CALL(*worldMock, getFloatConfig(_))
|
||||
.WillByDefault(Return(0.0f));
|
||||
ON_CALL(*worldMock, GetPlayerSecurityLimit())
|
||||
.WillByDefault(Return(SEC_PLAYER));
|
||||
|
||||
session = new WorldSession(
|
||||
1, "test", 0, nullptr, SEC_PLAYER,
|
||||
EXPANSION_WRATH_OF_THE_LICH_KING,
|
||||
0, LOCALE_enUS, 0, false, false, 0);
|
||||
|
||||
player = new TestPlayer(session);
|
||||
player->ForceInitValues(42);
|
||||
session->SetPlayer(player);
|
||||
player->SetSession(session);
|
||||
|
||||
s_testOocEnabled.clear();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
s_testOocEnabled.clear();
|
||||
|
||||
// Intentional leaks of session/player to avoid
|
||||
// database access in destructors.
|
||||
IWorld* currentWorld = sWorld.release();
|
||||
delete currentWorld;
|
||||
worldMock = nullptr;
|
||||
|
||||
sWorld.reset(originalWorld);
|
||||
originalWorld = nullptr;
|
||||
session = nullptr;
|
||||
player = nullptr;
|
||||
}
|
||||
|
||||
/// Simulate what InitGlyphsForLevel() does for a given level.
|
||||
void SimulateGlyphsForLevel(uint8 level)
|
||||
{
|
||||
uint32 value = 0;
|
||||
if (level >= 15)
|
||||
value |= (0x01 | 0x02);
|
||||
if (level >= 30)
|
||||
value |= 0x08;
|
||||
if (level >= 50)
|
||||
value |= 0x04;
|
||||
if (level >= 70)
|
||||
value |= 0x10;
|
||||
if (level >= 80)
|
||||
value |= 0x20;
|
||||
player->SetUInt32Value(PLAYER_GLYPHS_ENABLED, value);
|
||||
}
|
||||
|
||||
IWorld* originalWorld = nullptr;
|
||||
NiceMock<WorldMock>* worldMock = nullptr;
|
||||
WorldSession* session = nullptr;
|
||||
TestPlayer* player = nullptr;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// TEST: Feature enabled → level to 80 → slot stays locked
|
||||
// ----------------------------------------------------------
|
||||
TEST_F(OmenOfClarityGlyphLockTest,
|
||||
GlyphSlotStaysLockedAfterLevelingTo80)
|
||||
{
|
||||
// 1. Enable the empowered omen feature for this player
|
||||
s_testOocEnabled.insert(
|
||||
player->GetGUID().GetCounter());
|
||||
|
||||
// 2. Lock glyph slot 5 (clear bit 0x20) as the module
|
||||
// does on quest completion
|
||||
SimulateGlyphsForLevel(79); // slots for level 79 (no 0x20)
|
||||
uint32 bitsAtLevel79 =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_EQ(bitsAtLevel79 & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "Slot 5 should not be enabled before level 80";
|
||||
|
||||
// 3. Simulate hitting level 80:
|
||||
// The game calls InitGlyphsForLevel() which sets 0x20,
|
||||
// THEN fires OnPlayerLevelChanged.
|
||||
SimulateGlyphsForLevel(80);
|
||||
uint32 bitsAfterInit =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_NE(bitsAfterInit & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "InitGlyphsForLevel should enable slot 5 at 80";
|
||||
|
||||
// 4. Fire the level-change hook (same as GiveLevel does)
|
||||
sScriptMgr->OnPlayerLevelChanged(player, 79);
|
||||
|
||||
// 5. Verify the hook cleared the bit
|
||||
uint32 bitsAfterHook =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_EQ(bitsAfterHook & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "Module hook should re-lock slot 5 after level 80";
|
||||
|
||||
// Other glyph slots should be unaffected
|
||||
EXPECT_NE(bitsAfterHook & 0x01, 0u); // slot 0 (level 15)
|
||||
EXPECT_NE(bitsAfterHook & 0x02, 0u); // slot 1 (level 15)
|
||||
EXPECT_NE(bitsAfterHook & 0x04, 0u); // slot 2 (level 50)
|
||||
EXPECT_NE(bitsAfterHook & 0x08, 0u); // slot 3 (level 30)
|
||||
EXPECT_NE(bitsAfterHook & 0x10, 0u); // slot 4 (level 70)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// TEST: Feature NOT enabled → level to 80 → slot stays open
|
||||
// ----------------------------------------------------------
|
||||
TEST_F(OmenOfClarityGlyphLockTest,
|
||||
GlyphSlotOpensNormallyWhenFeatureDisabled)
|
||||
{
|
||||
// Feature is NOT enabled (cache is empty)
|
||||
|
||||
SimulateGlyphsForLevel(80);
|
||||
sScriptMgr->OnPlayerLevelChanged(player, 79);
|
||||
|
||||
uint32 bits =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_NE(bits & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "Without the feature, slot 5 should remain enabled";
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// TEST: Enable at sub-80, level through multiple levels,
|
||||
// slot stays locked throughout
|
||||
// ----------------------------------------------------------
|
||||
TEST_F(OmenOfClarityGlyphLockTest,
|
||||
GlyphSlotStaysLockedThroughMultipleLevelUps)
|
||||
{
|
||||
s_testOocEnabled.insert(
|
||||
player->GetGUID().GetCounter());
|
||||
|
||||
// Level from 78 → 79 → 80 → 81 (if somehow possible)
|
||||
for (uint8 newLevel = 78; newLevel <= 80; ++newLevel)
|
||||
{
|
||||
SCOPED_TRACE("Level: " + std::to_string(newLevel));
|
||||
SimulateGlyphsForLevel(newLevel);
|
||||
sScriptMgr->OnPlayerLevelChanged(
|
||||
player, newLevel - 1);
|
||||
|
||||
uint32 bits =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_EQ(bits & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "Slot 5 should remain locked at level "
|
||||
<< static_cast<int>(newLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// TEST: Feature enabled mid-session then level up
|
||||
// ----------------------------------------------------------
|
||||
TEST_F(OmenOfClarityGlyphLockTest,
|
||||
QuestCompletionLocksSlotThenLevelUpKeepsItLocked)
|
||||
{
|
||||
// Player is level 79 with no feature
|
||||
SimulateGlyphsForLevel(79);
|
||||
EXPECT_EQ(
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED) &
|
||||
OOC_LOCKED_SLOT_BIT, 0u);
|
||||
|
||||
// Complete the quest → hook enables feature + locks slot
|
||||
sScriptMgr->OnPlayerCompleteQuest(player, nullptr);
|
||||
EXPECT_TRUE(s_testOocEnabled.count(
|
||||
player->GetGUID().GetCounter()) > 0)
|
||||
<< "Feature should be enabled after quest completion";
|
||||
|
||||
// Now level to 80 → game re-enables all slots
|
||||
SimulateGlyphsForLevel(80);
|
||||
uint32 bitsPreHook =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_NE(bitsPreHook & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "InitGlyphsForLevel should have set 0x20";
|
||||
|
||||
// Level-change hook fires
|
||||
sScriptMgr->OnPlayerLevelChanged(player, 79);
|
||||
|
||||
uint32 bitsPostHook =
|
||||
player->GetUInt32Value(PLAYER_GLYPHS_ENABLED);
|
||||
EXPECT_EQ(bitsPostHook & OOC_LOCKED_SLOT_BIT, 0u)
|
||||
<< "Slot 5 should be re-locked after leveling to 80";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "IntegrationTestFixture.h"
|
||||
#include "SpellInfoTestHelper.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifndef TEST_F
|
||||
#define TEST_F(fixture, name) void fixture##_##name()
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Integration test: Mage casts Frostbolt at a Warrior in PvP.
|
||||
*
|
||||
* What we test:
|
||||
* 1. The Frostbolt slow aura (SPELL_AURA_MOD_DECREASE_SPEED) is applied
|
||||
* 2. The warrior's run speed is reduced by the correct percentage
|
||||
* 3. The spell damage matches expected base points
|
||||
*
|
||||
* What we simulate vs. what's real:
|
||||
* ┌──────────────────────────────────────────────────────┐
|
||||
* │ REAL (actual game code) │
|
||||
* │ - SpellInfo built from SpellEntry (same as DBC) │
|
||||
* │ - Aura::TryRefreshStackOrCreate │
|
||||
* │ - AuraEffect with SPELL_AURA_MOD_DECREASE_SPEED │
|
||||
* │ - Unit::UpdateSpeed applies the slow modifier │
|
||||
* │ - SpellNonMeleeDamage struct for damage bookkeeping │
|
||||
* │ - Unit::GetSpeedRate / GetSpeed for verification │
|
||||
* ├──────────────────────────────────────────────────────┤
|
||||
* │ SIMULATED (bypassed for test isolation) │
|
||||
* │ - No full Spell::prepare → cast pipeline │
|
||||
* │ - No target selection / LoS / range checks │
|
||||
* │ - Aura applied directly via Unit::AddAura │
|
||||
* │ - Damage dealt directly, no resist/absorb rolls │
|
||||
* └──────────────────────────────────────────────────────┘
|
||||
*/
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Frostbolt Rank 14 (level 79) reference values
|
||||
static constexpr uint32 FROSTBOLT_SPELL_ID = 42842;
|
||||
static constexpr int32 FROSTBOLT_BASE_DAMAGE = 799; // base points for effect 0
|
||||
static constexpr int32 FROSTBOLT_SLOW_PCT = -40; // 40% speed reduction
|
||||
static constexpr int32 FROSTBOLT_DURATION_MS = 9000; // 9 seconds
|
||||
|
||||
class FrostboltPvPTest : public IntegrationTestFixture
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
IntegrationTestFixture::SetUp();
|
||||
|
||||
// Build a SpellInfo mimicking Frostbolt Rank 14:
|
||||
// Effect[0]: SPELL_EFFECT_SCHOOL_DAMAGE — base 799 frost damage
|
||||
// Effect[1]: SPELL_EFFECT_APPLY_AURA / SPELL_AURA_MOD_DECREASE_SPEED — -40%
|
||||
_frostboltInfo = SpellInfoBuilder()
|
||||
.WithId(FROSTBOLT_SPELL_ID)
|
||||
.WithSpellFamilyName(SPELLFAMILY_MAGE)
|
||||
.WithSpellFamilyFlags(0x20) // Frostbolt family flag
|
||||
.WithSchoolMask(SPELL_SCHOOL_MASK_FROST)
|
||||
.WithDmgClass(SPELL_DAMAGE_CLASS_MAGIC)
|
||||
.WithEffect(0, SPELL_EFFECT_SCHOOL_DAMAGE)
|
||||
.WithEffectBasePoints(0, FROSTBOLT_BASE_DAMAGE)
|
||||
.WithEffect(1, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_MOD_DECREASE_SPEED)
|
||||
.WithEffectBasePoints(1, FROSTBOLT_SLOW_PCT)
|
||||
.BuildUnique();
|
||||
|
||||
// Create a SpellDurationEntry so the aura has a real duration
|
||||
_durationEntry = {};
|
||||
_durationEntry.ID = 1;
|
||||
_durationEntry.Duration[0] = FROSTBOLT_DURATION_MS;
|
||||
_durationEntry.Duration[1] = FROSTBOLT_DURATION_MS;
|
||||
_durationEntry.Duration[2] = FROSTBOLT_DURATION_MS;
|
||||
// Patch the SpellInfo's DurationEntry pointer
|
||||
const_cast<SpellInfo*>(_frostboltInfo.get())->DurationEntry = &_durationEntry;
|
||||
}
|
||||
|
||||
std::unique_ptr<SpellInfo> _frostboltInfo;
|
||||
SpellDurationEntry _durationEntry;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// TEST: Frostbolt slow aura is applied and reduces speed
|
||||
// ─────────────────────────────────────────────────────────
|
||||
//
|
||||
// ┌───────────┐ Frostbolt ┌───────────┐
|
||||
// │ Mage │ ────────────► │ Warrior │
|
||||
// │ (player) │ │ (player) │
|
||||
// └───────────┘ └───────────┘
|
||||
// │
|
||||
// ┌─────▼─────┐
|
||||
// │ Slow Aura │
|
||||
// │ -40% spd │
|
||||
// └───────────┘
|
||||
//
|
||||
TEST_F(FrostboltPvPTest, SlowAuraAppliedAndReducesSpeed)
|
||||
{
|
||||
auto* mage = CreateTestPlayer(1, "Frostmage");
|
||||
auto* warrior = CreateTestPlayer(2, "Berserker");
|
||||
|
||||
// Record the warrior's baseline run speed rate (should be 1.0)
|
||||
float baseSpeedRate = warrior->GetSpeedRate(MOVE_RUN);
|
||||
ASSERT_FLOAT_EQ(baseSpeedRate, 1.0f)
|
||||
<< "Warrior should start at 100% run speed";
|
||||
|
||||
// Mage applies the Frostbolt aura (effect index 1 = slow) on the warrior.
|
||||
// effMask 0x2 = only effect[1] (the APPLY_AURA slow), skipping effect[0] (damage).
|
||||
Aura* slowAura = mage->AddAura(_frostboltInfo.get(), 0x2, warrior);
|
||||
ASSERT_NE(slowAura, nullptr) << "Frostbolt slow aura should be created";
|
||||
|
||||
// Verify the aura is on the warrior
|
||||
EXPECT_TRUE(warrior->HasAura(FROSTBOLT_SPELL_ID))
|
||||
<< "Warrior should have the Frostbolt aura";
|
||||
|
||||
// Verify aura duration
|
||||
EXPECT_EQ(slowAura->GetMaxDuration(), FROSTBOLT_DURATION_MS);
|
||||
|
||||
// Verify the slow amount on the aura effect
|
||||
AuraEffect const* slowEffect = slowAura->GetEffect(1);
|
||||
ASSERT_NE(slowEffect, nullptr);
|
||||
EXPECT_EQ(slowEffect->GetAmount(), FROSTBOLT_SLOW_PCT)
|
||||
<< "Slow effect should be -40%";
|
||||
|
||||
// Verify the warrior's run speed is actually reduced.
|
||||
// UpdateSpeed was called by HandleAuraModDecreaseSpeed.
|
||||
// Formula: speed = max(non_stack_bonus, stack_bonus) → 1.0
|
||||
// then AddPct(speed, -40) → 1.0 * (1 + (-40/100)) = 0.60
|
||||
float expectedSpeedRate = 0.60f;
|
||||
EXPECT_NEAR(warrior->GetSpeedRate(MOVE_RUN), expectedSpeedRate, 0.01f)
|
||||
<< "Warrior run speed should be 60% (slowed by 40%)";
|
||||
|
||||
// Walk speed should also be reduced
|
||||
EXPECT_NEAR(warrior->GetSpeedRate(MOVE_WALK), expectedSpeedRate, 0.01f)
|
||||
<< "Warrior walk speed should also be reduced by 40%";
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// TEST: Frostbolt damage matches expected base points
|
||||
// ─────────────────────────────────────────────────────────
|
||||
TEST_F(FrostboltPvPTest, DamageMatchesBasePoints)
|
||||
{
|
||||
auto* mage = CreateTestPlayer(1, "Frostmage");
|
||||
auto* warrior = CreateTestPlayer(2, "Berserker");
|
||||
|
||||
// Set warrior health high enough to survive
|
||||
warrior->SetMaxHealth(50000);
|
||||
warrior->SetHealth(50000);
|
||||
|
||||
// The SpellInfo's effect[0] base damage
|
||||
int32 baseDamage = _frostboltInfo->Effects[0].CalcValue(mage);
|
||||
EXPECT_EQ(baseDamage, FROSTBOLT_BASE_DAMAGE)
|
||||
<< "Base damage should match Frostbolt Rank 14";
|
||||
|
||||
// Simulate the damage using SpellNonMeleeDamage (same struct the real
|
||||
// spell pipeline uses to track damage/absorb/resist per hit).
|
||||
SpellNonMeleeDamage dmgInfo(mage, warrior, _frostboltInfo.get(),
|
||||
SPELL_SCHOOL_MASK_FROST);
|
||||
dmgInfo.damage = baseDamage;
|
||||
|
||||
// Record health before
|
||||
uint32 healthBefore = warrior->GetHealth();
|
||||
|
||||
// Deal the damage through the real DealDamage path
|
||||
Unit::DealDamage(mage, warrior, dmgInfo.damage, nullptr,
|
||||
SPELL_DIRECT_DAMAGE, SPELL_SCHOOL_MASK_FROST, _frostboltInfo.get());
|
||||
|
||||
uint32 healthAfter = warrior->GetHealth();
|
||||
uint32 actualDamage = healthBefore - healthAfter;
|
||||
|
||||
// With no spell power, no resilience, no armor (frost is magic),
|
||||
// and no absorb shields, the damage dealt equals base damage.
|
||||
EXPECT_EQ(actualDamage, uint32(FROSTBOLT_BASE_DAMAGE))
|
||||
<< "Damage dealt should equal base points with no modifiers";
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// TEST: Removing the aura restores full speed
|
||||
// ─────────────────────────────────────────────────────────
|
||||
TEST_F(FrostboltPvPTest, RemovingSlowRestoresSpeed)
|
||||
{
|
||||
auto* mage = CreateTestPlayer(1, "Frostmage");
|
||||
auto* warrior = CreateTestPlayer(2, "Berserker");
|
||||
|
||||
// Apply slow
|
||||
mage->AddAura(_frostboltInfo.get(), 0x2, warrior);
|
||||
ASSERT_NEAR(warrior->GetSpeedRate(MOVE_RUN), 0.60f, 0.01f);
|
||||
|
||||
// Remove the aura (dispel / expiry)
|
||||
warrior->RemoveAurasDueToSpell(FROSTBOLT_SPELL_ID);
|
||||
|
||||
// Speed should be back to normal
|
||||
EXPECT_FLOAT_EQ(warrior->GetSpeedRate(MOVE_RUN), 1.0f)
|
||||
<< "Speed should return to 100% after slow is removed";
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// TEST: Full scenario — slow + damage in sequence
|
||||
// ─────────────────────────────────────────────────────────
|
||||
//
|
||||
// Timeline:
|
||||
// ┌─────────┬──────────────┬──────────────┬────────────┐
|
||||
// │ Start │ Apply slow │ Deal damage │ Verify │
|
||||
// │ 100%spd │ → 60% speed │ → HP reduced │ slow+dmg │
|
||||
// └─────────┴──────────────┴──────────────┴────────────┘
|
||||
//
|
||||
TEST_F(FrostboltPvPTest, FullFrostboltScenario)
|
||||
{
|
||||
auto* mage = CreateTestPlayer(1, "Frostmage");
|
||||
auto* warrior = CreateTestPlayer(2, "Berserker");
|
||||
|
||||
warrior->SetMaxHealth(50000);
|
||||
warrior->SetHealth(50000);
|
||||
|
||||
// Verify pre-conditions
|
||||
ASSERT_FLOAT_EQ(warrior->GetSpeedRate(MOVE_RUN), 1.0f);
|
||||
ASSERT_EQ(warrior->GetHealth(), 50000u);
|
||||
|
||||
// Step 1: Apply the slow aura (effect[1] only)
|
||||
Aura* aura = mage->AddAura(_frostboltInfo.get(), 0x2, warrior);
|
||||
ASSERT_NE(aura, nullptr);
|
||||
|
||||
// Step 2: Deal the damage (effect[0] simulated)
|
||||
int32 damage = _frostboltInfo->Effects[0].CalcValue(mage);
|
||||
Unit::DealDamage(mage, warrior, damage, nullptr,
|
||||
SPELL_DIRECT_DAMAGE, SPELL_SCHOOL_MASK_FROST, _frostboltInfo.get());
|
||||
|
||||
// Verify both effects landed
|
||||
EXPECT_NEAR(warrior->GetSpeedRate(MOVE_RUN), 0.60f, 0.01f)
|
||||
<< "Warrior should be slowed to 60%";
|
||||
EXPECT_EQ(warrior->GetHealth(), 50000u - FROSTBOLT_BASE_DAMAGE)
|
||||
<< "Warrior HP should be reduced by Frostbolt base damage";
|
||||
EXPECT_TRUE(warrior->HasAura(FROSTBOLT_SPELL_ID))
|
||||
<< "Warrior should still have the Frostbolt debuff";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "IntegrationTestFixture.h"
|
||||
#include "ProcEventInfoHelper.h"
|
||||
#include "SpellInfoTestHelper.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#ifndef TEST_F
|
||||
#define TEST_F(fixture, name) void fixture##_##name()
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class ProcUnitIntegrationTest : public IntegrationTestFixture
|
||||
{
|
||||
};
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, CreatureAndPlayerAreHostile)
|
||||
{
|
||||
auto* player = CreateTestPlayer(1, "Hostile", SEC_PLAYER);
|
||||
// Player faction defaults to 0, set it to our test faction
|
||||
player->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, TEST_FACTION_HOSTILE_TO_MONSTERS);
|
||||
|
||||
auto* creature = CreateTestCreature(100, 99001, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
|
||||
ReputationRank reaction = player->GetReactionTo(creature);
|
||||
EXPECT_EQ(reaction, REP_HOSTILE);
|
||||
|
||||
ReputationRank reverseReaction = creature->GetReactionTo(player);
|
||||
EXPECT_EQ(reverseReaction, REP_HOSTILE);
|
||||
}
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, ProcEventInfoWithRealUnits)
|
||||
{
|
||||
auto* attacker = CreateTestPlayer(1, "Attacker", SEC_PLAYER);
|
||||
auto* victim = CreateTestCreature(100, 99001, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
|
||||
auto procInfo = ProcEventInfoBuilder()
|
||||
.WithActor(attacker)
|
||||
.WithActionTarget(victim)
|
||||
.WithProcTarget(victim)
|
||||
.WithTypeMask(PROC_FLAG_DONE_MELEE_AUTO_ATTACK)
|
||||
.WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE)
|
||||
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
|
||||
.WithHitMask(PROC_HIT_NORMAL)
|
||||
.Build();
|
||||
|
||||
EXPECT_EQ(procInfo.GetActor(), attacker);
|
||||
EXPECT_EQ(procInfo.GetActionTarget(), victim);
|
||||
EXPECT_EQ(procInfo.GetProcTarget(), victim);
|
||||
EXPECT_EQ(procInfo.GetTypeMask(), uint32(PROC_FLAG_DONE_MELEE_AUTO_ATTACK));
|
||||
}
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, ProcEntryMatchesSpellInfo)
|
||||
{
|
||||
auto spellInfo = SpellInfoBuilder()
|
||||
.WithId(99999)
|
||||
.WithSpellFamilyName(SPELLFAMILY_GENERIC)
|
||||
.WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK)
|
||||
.WithProcChance(100)
|
||||
.WithEffect(0, SPELL_EFFECT_APPLY_AURA, SPELL_AURA_PROC_TRIGGER_SPELL)
|
||||
.BuildUnique();
|
||||
|
||||
auto procEntry = SpellProcEntryBuilder()
|
||||
.WithProcFlags(PROC_FLAG_DONE_MELEE_AUTO_ATTACK)
|
||||
.WithSpellTypeMask(PROC_SPELL_TYPE_DAMAGE)
|
||||
.WithSpellPhaseMask(PROC_SPELL_PHASE_HIT)
|
||||
.WithHitMask(PROC_HIT_NORMAL)
|
||||
.WithChance(100.0f)
|
||||
.Build();
|
||||
|
||||
EXPECT_EQ(spellInfo->Id, 99999u);
|
||||
EXPECT_EQ(spellInfo->ProcFlags, uint32(PROC_FLAG_DONE_MELEE_AUTO_ATTACK));
|
||||
EXPECT_EQ(procEntry.ProcFlags, uint32(PROC_FLAG_DONE_MELEE_AUTO_ATTACK));
|
||||
EXPECT_FLOAT_EQ(procEntry.Chance, 100.0f);
|
||||
}
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, MultipleCreaturesCanBeCreated)
|
||||
{
|
||||
auto* player = CreateTestPlayer(1, "Attacker", SEC_PLAYER);
|
||||
auto* creature1 = CreateTestCreature(100, 99001, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
auto* creature2 = CreateTestCreature(101, 99002, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
|
||||
EXPECT_NE(creature1, creature2);
|
||||
EXPECT_NE(creature1->GetGUID(), creature2->GetGUID());
|
||||
EXPECT_EQ(creature1->GetHealth(), 10000u);
|
||||
EXPECT_EQ(creature2->GetHealth(), 10000u);
|
||||
EXPECT_NE(player->GetGUID(), creature1->GetGUID());
|
||||
}
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, ProcOnKillWithRealUnits)
|
||||
{
|
||||
auto* attacker = CreateTestPlayer(1, "Killer", SEC_PLAYER);
|
||||
auto* victim = CreateTestCreature(100, 99001, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
|
||||
// Build a kill-proc event
|
||||
auto procInfo = ProcEventInfoBuilder()
|
||||
.WithActor(attacker)
|
||||
.WithActionTarget(victim)
|
||||
.WithProcTarget(victim)
|
||||
.WithTypeMask(PROC_FLAG_KILL)
|
||||
.WithSpellTypeMask(PROC_SPELL_TYPE_NONE)
|
||||
.WithSpellPhaseMask(PROC_SPELL_PHASE_NONE)
|
||||
.WithHitMask(PROC_HIT_NORMAL)
|
||||
.Build();
|
||||
|
||||
EXPECT_EQ(procInfo.GetTypeMask(), uint32(PROC_FLAG_KILL));
|
||||
EXPECT_EQ(procInfo.GetActor(), attacker);
|
||||
EXPECT_EQ(procInfo.GetActionTarget(), victim);
|
||||
}
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, CreatureHealthCanBeManipulated)
|
||||
{
|
||||
auto* creature = CreateTestCreature(100, 99001, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
|
||||
EXPECT_EQ(creature->GetHealth(), 10000u);
|
||||
EXPECT_EQ(creature->GetMaxHealth(), 10000u);
|
||||
|
||||
creature->SetHealth(1);
|
||||
EXPECT_EQ(creature->GetHealth(), 1u);
|
||||
|
||||
creature->SetHealth(0);
|
||||
EXPECT_EQ(creature->GetHealth(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(ProcUnitIntegrationTest, FactionHostilityIsSymmetric)
|
||||
{
|
||||
auto* c1 = CreateTestCreature(100, 99001, TEST_FACTION_HOSTILE_TO_MONSTERS);
|
||||
auto* c2 = CreateTestCreature(101, 99002, TEST_FACTION_HOSTILE_TO_ALL);
|
||||
|
||||
// c2 (monster mask=8) hostile to c1's mask (player mask=1): yes
|
||||
// c1 (player mask=1) hostile to c2's mask (monster mask=8): yes
|
||||
ReputationRank r1 = c1->GetReactionTo(c2);
|
||||
ReputationRank r2 = c2->GetReactionTo(c1);
|
||||
|
||||
EXPECT_EQ(r1, REP_HOSTILE);
|
||||
EXPECT_EQ(r2, REP_HOSTILE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user