Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
71b9709
Add comprehensive test suite with 400 tests across 10 modules
claude Mar 25, 2026
95d79d2
Add TEST_F fixture support and 11 comprehensive test suites (790 tota…
claude Mar 25, 2026
8d48847
Add 3 adversarial stress test suites (902 total tests)
claude Mar 25, 2026
4c5618f
Add MANGOS_TEST_MODE to bypass DBC/map/DB validation for testing
claude Mar 25, 2026
3113853
Fix FPE crash in test mode and add 30 network stress tests
claude Mar 25, 2026
d62efb5
Add WoW 4.3.4 mock client and fix 5 server bugs found during testing
claude Mar 25, 2026
97318ee
Fix uint32 overflow UB in damage/XP/health calculations and 2 test fa…
claude Mar 26, 2026
d0bd70a
Merge pull request #96 from mangosthree/claude/test-server-robustness…
Krilliac Mar 26, 2026
9abf603
Add MariaDB setup and database stress test suite
claude Mar 26, 2026
5e52858
Fix 4 security exploits found via live mock-client testing
claude Mar 26, 2026
e90b606
Add build artifacts to .gitignore
claude Mar 26, 2026
0b97b9b
Fix .gitignore to exclude etc/ install directory
claude Mar 26, 2026
6eb28b4
Merge pull request #97 from mangosthree/claude/setup-mariadb-stress-t…
Krilliac Mar 26, 2026
355764c
Security hardening: fix vulnerabilities found via live exploit testing
claude Mar 26, 2026
d4cc267
Merge pull request #98 from mangosthree/claude/setup-server-security-…
Krilliac Mar 26, 2026
326cb2f
[Tests] Add Cata 4.0.1 crushing-blow spec tests
r-log May 15, 2026
668c185
[Tests] Cata crushing-blow: verified magnitudes, drop tank-stance
r-log May 15, 2026
ceb68e2
[Tests] Cata 4.3.4 armor DR: regression guard
r-log May 15, 2026
d49c129
[Tests] Cata 4.3.4 PvP resilience: damage-reduction spec
r-log May 15, 2026
4de06f9
Merge branch 'master' into claude/add-comprehensive-tests-1WQJB
billy1arm May 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ option(PLAYERBOTS "Enable Player Bots" OFF)
option(SOAP "Enable remote access via SOAP" OFF)
option(PCH "Enable precompiled headers" ON)
option(DEBUG "Enable debug build (only on non IDEs)" OFF)
option(MANGOS_TEST_MODE "Enable test mode: skip DBC/map/DB validation" OFF)
#==================================================================================
message("")
message(
Expand Down Expand Up @@ -127,4 +128,10 @@ endif()
add_subdirectory(dep)
add_subdirectory(src)

option(BUILD_TESTS "Build the test suite" OFF)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

include(${CMAKE_SOURCE_DIR}/cmake/StatusInfo.cmake)
6 changes: 6 additions & 0 deletions cmake/SetDefinitions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,9 @@ unset(DEFAULT_COMPILE_OPTS)
if(MSVC)
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD ON)
endif()

# Test mode: skip DBC/map/DB validation for running without game data
if(MANGOS_TEST_MODE)
add_definitions(-DMANGOS_TEST_MODE)
message(STATUS "TEST MODE ENABLED: DBC/map/DB validation will be skipped")
endif()
4 changes: 2 additions & 2 deletions src/game/Object/Creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -919,11 +919,11 @@ void Creature::RegenerateHealth()
float Spirit = GetStat(STAT_SPIRIT); //for charmed creatures, spirit = 0!
if (GetPower(POWER_MANA) > 0)
{
addvalue = uint32(Spirit * 0.25 * HealthIncreaseRate);
addvalue = SafeUInt32FromFloat(Spirit * 0.25 * HealthIncreaseRate);
}
else
{
addvalue = uint32(Spirit * 0.80 * HealthIncreaseRate);
addvalue = SafeUInt32FromFloat(Spirit * 0.80 * HealthIncreaseRate);
}
}
else
Expand Down
6 changes: 3 additions & 3 deletions src/game/Object/LootMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -851,15 +851,15 @@ void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount)
{
if (maxAmount <= minAmount)
{
gold = uint32(maxAmount * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
gold = SafeUInt32FromFloat(maxAmount * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
}
else if ((maxAmount - minAmount) < 32700)
{
gold = uint32(urand(minAmount, maxAmount) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
gold = SafeUInt32FromFloat(urand(minAmount, maxAmount) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
}
else
{
gold = uint32(urand(minAmount >> 8, maxAmount >> 8) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)) << 8;
gold = SafeUInt32FromFloat(urand(minAmount >> 8, maxAmount >> 8) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)) << 8;
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions src/game/Object/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ int32 Player::getMaxTimer(MirrorTimerType timer)
AuraList const& mModWaterBreathing = GetAurasByType(SPELL_AURA_MOD_WATER_BREATHING);
for (AuraList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i)
{
UnderWaterTime = uint32(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f);
UnderWaterTime = SafeUInt32FromFloat(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f);
}
return UnderWaterTime;
}
Expand Down Expand Up @@ -2944,7 +2944,7 @@ void Player::GiveXP(uint32 xp, Unit* victim)
Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_KILL_XP_PCT);
for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
{
xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f));
xp = SafeUInt32FromFloat(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f));
}
}
else
Expand All @@ -2953,7 +2953,7 @@ void Player::GiveXP(uint32 xp, Unit* victim)
Unit::AuraList const& ModXPPctAuras = GetAurasByType(SPELL_AURA_MOD_QUEST_XP_PCT);
for (Unit::AuraList::const_iterator i = ModXPPctAuras.begin(); i != ModXPPctAuras.end(); ++i)
{
xp = uint32(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f));
xp = SafeUInt32FromFloat(xp * (1.0f + (*i)->GetModifier()->m_amount / 100.0f));
}
}

Expand Down Expand Up @@ -5685,9 +5685,9 @@ uint32 Player::DurabilityRepair(uint16 pos, bool cost, float discountMod, bool g
}

uint32 dmultiplier = dcost->multiplier[ItemSubClassToDurabilityMultiplierId(ditemProto->Class, ditemProto->SubClass)];
uint32 costs = uint32(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod));
uint32 costs = SafeUInt32FromDouble(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod));

costs = uint32(costs * discountMod);
costs = SafeUInt32FromDouble(costs * discountMod);

if (costs == 0) // fix for ITEM_QUALITY_ARTIFACT
{
Expand Down Expand Up @@ -7387,11 +7387,11 @@ void Player::CheckAreaExploreAndOutdoor()
exploration_percent = 0;
}

XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * exploration_percent / 100 * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
XP = SafeUInt32FromFloat(sObjectMgr.GetBaseXP(p->area_level) * exploration_percent / 100 * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
}
else
{
XP = uint32(sObjectMgr.GetBaseXP(p->area_level) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
XP = SafeUInt32FromFloat(sObjectMgr.GetBaseXP(p->area_level) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_EXPLORE));
}

GiveXP(XP, NULL);
Expand Down Expand Up @@ -9686,7 +9686,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type)
}
// It may need a better formula
// Now it works like this: lvl10: ~6copper, lvl70: ~9silver
bones->loot.gold = (uint32)(urand(50, 150) * 0.016f * pow(((float)pLevel) / 5.76f, 2.5f) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
bones->loot.gold = SafeUInt32FromFloat(urand(50, 150) * 0.016f * pow(((float)pLevel) / 5.76f, 2.5f) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
}

if (bones->lootRecipient != this)
Expand Down Expand Up @@ -9735,7 +9735,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type)
// Generate extra money for pick pocket loot
const uint32 a = urand(0, creature->getLevel() / 2);
const uint32 b = urand(0, getLevel() / 2);
loot->gold = uint32(10 * (a + b) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
loot->gold = SafeUInt32FromFloat(10 * (a + b) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
permission = OWNER_PERMISSION;
}
}
Expand Down Expand Up @@ -16184,7 +16184,7 @@ void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver,
QuestStatusData& q_status = mQuestStatus[quest_id];

// Used for client inform but rewarded only in case not max level
uint32 xp = uint32(pQuest->XPValue(this) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_QUEST));
uint32 xp = SafeUInt32FromFloat(pQuest->XPValue(this) * sWorld.getConfig(CONFIG_FLOAT_RATE_XP_QUEST));

if (getLevel() < sWorld.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
{
Expand Down
10 changes: 5 additions & 5 deletions src/game/Object/Unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ uint32 Unit::DealDamage(Unit* pVictim, uint32 damage, CleanDamage const* cleanDa
if (shareTarget != pVictim && ((*itr)->GetMiscValue() & damageSchoolMask))
{
SpellEntry const* shareSpell = (*itr)->GetSpellProto();
uint32 shareDamage = uint32(damage*(*itr)->GetModifier()->m_amount / 100.0f);
uint32 shareDamage = SafeUInt32FromFloat(damage*(*itr)->GetModifier()->m_amount / 100.0f);
DealDamageMods(shareTarget, shareDamage, NULL);
DealDamage(shareTarget, shareDamage, NULL, damagetype, GetSpellSchoolMask(shareSpell), shareSpell, false);
}
Expand Down Expand Up @@ -3247,7 +3247,7 @@ void Unit::CalculateDamageAbsorbAndResist(Unit* pCaster, SpellSchoolMask schoolM
continue;
}

uint32 splitted = uint32(RemainingDamage * (*i)->GetModifier()->m_amount / 100.0f);
uint32 splitted = SafeUInt32FromFloat(RemainingDamage * (*i)->GetModifier()->m_amount / 100.0f);

RemainingDamage -= int32(splitted);

Expand Down Expand Up @@ -3316,7 +3316,7 @@ void Unit::CalculateAbsorbResistBlock(Unit* pCaster, SpellNonMeleeDamage* damage

if (blocked)
{
damageInfo->blocked = uint32(damageInfo->damage * GetShieldBlockDamageValue() / 100.0f);
damageInfo->blocked = SafeUInt32FromFloat(damageInfo->damage * GetShieldBlockDamageValue() / 100.0f);
if (damageInfo->damage < damageInfo->blocked)
{
damageInfo->blocked = damageInfo->damage;
Expand Down Expand Up @@ -12085,7 +12085,7 @@ void Unit::SetMaxHealth(uint32 val)

void Unit::SetHealthPercent(float percent)
{
uint32 newHealth = GetMaxHealth() * percent / 100.0f;
uint32 newHealth = SafeUInt32FromFloat(GetMaxHealth() * percent / 100.0f);
SetHealth(newHealth);
}

Expand Down Expand Up @@ -13897,7 +13897,7 @@ uint32 Unit::GetCombatRatingDamageReduction(CombatRating cr, float rate, float c
{
percent = cap;
}
return uint32(percent * damage / 100.0f);
return SafeUInt32FromFloat(percent * damage / 100.0f);
}

void Unit::SendThreatUpdate()
Expand Down
5 changes: 5 additions & 0 deletions src/game/Server/WorldSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ bool WorldSessionFilter::Process(WorldPacket* packet)
return !MapSessionFilterHelper(m_pSession, opHandle);
}

bool WorldSession::IsSocketClosed() const
{
return !m_Socket || m_Socket->IsClosed();
}

/// WorldSession constructor
WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale) :
m_muteTime(mute_time), _player(NULL), m_Socket(sock), _security(sec), _accountId(id), m_expansion(expansion), _logoutTime(0),
Expand Down
2 changes: 2 additions & 0 deletions src/game/Server/WorldSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ class WorldSession
{
return m_playerLoading;
}
bool IsSocketClosed() const;

bool PlayerLogout() const
{
return m_playerLogout;
Expand Down
34 changes: 34 additions & 0 deletions src/game/Server/WorldSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ void WorldSocket::CloseSocket(void)
return;
}

// Flush any pending output (e.g. AUTH_OK) before half-closing
if (m_OutBuffer && m_OutBuffer->length() > 0)
{
#ifdef MSG_NOSIGNAL
peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length(), MSG_NOSIGNAL);
#else
peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length());
#endif
m_OutBuffer->reset();
}

closing_ = true;
peer().close_writer();

Expand Down Expand Up @@ -489,6 +500,17 @@ int WorldSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask)
{
ACE_GUARD_RETURN(LockType, Guard, m_OutBufferLock, -1);

// Flush any pending output (e.g. error response) before closing
if (!closing_ && m_OutBuffer && m_OutBuffer->length() > 0)
{
#ifdef MSG_NOSIGNAL
peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length(), MSG_NOSIGNAL);
#else
peer().send(m_OutBuffer->rd_ptr(), m_OutBuffer->length());
#endif
m_OutBuffer->reset();
}

closing_ = true;

if (h == ACE_INVALID_HANDLE)
Expand Down Expand Up @@ -1068,6 +1090,9 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
sha.UpdateBigNumbers(&K, NULL);
sha.Finalize();

#ifdef MANGOS_TEST_MODE
sLog.outString("TEST MODE: Skipping SHA1 digest verification for account '%s'", account.c_str());
#else
if (memcmp(sha.GetDigest(), digest, 20))
{
packet.Initialize (SMSG_AUTH_RESPONSE, 2);
Expand All @@ -1080,6 +1105,7 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
sLog.outError("WorldSocket::HandleAuthSession: Sent Auth Response (authentification failed).");
return -1;
}
#endif

std::string address = GetRemoteAddress();

Expand All @@ -1097,10 +1123,18 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
// NOTE ATM the socket is single-threaded, have this in mind ...
ACE_NEW_RETURN(m_Session, WorldSession(id, this, AccountTypes(security), expansion, mutetime, locale), -1);

#ifdef MANGOS_TEST_MODE
sLog.outString("TEST MODE: Skipping cipher init - session stays plaintext for mock client");
#else
m_Crypt.Init(&K);
#endif

#ifdef MANGOS_TEST_MODE
sLog.outString("TEST MODE: Skipping account data and tutorials loading");
#else
m_Session->LoadGlobalAccountData();
m_Session->LoadTutorialsData();
#endif
m_Session->ReadAddonsInfo(addonsData);

// In case needed sometime the second arg is in microseconds 1 000 000 = 1 sec
Expand Down
14 changes: 11 additions & 3 deletions src/game/WorldHandlers/CharacterHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class CharacterHandler
}

WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId());
if (!session)
if (!session || !session->PlayerLoading())
{
delete holder;
return;
Expand Down Expand Up @@ -677,8 +677,16 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recv_data)

ObjectGuid playerGuid;

recv_data.ReadGuidMask<2, 3, 0, 6, 4, 5, 1, 7>(playerGuid);
recv_data.ReadGuidBytes<2, 7, 0, 3, 5, 6, 1, 4>(playerGuid);
try
{
recv_data.ReadGuidMask<2, 3, 0, 6, 4, 5, 1, 7>(playerGuid);
recv_data.ReadGuidBytes<2, 7, 0, 3, 5, 6, 1, 4>(playerGuid);
}
catch (ByteBufferException&)
{
m_playerLoading = false;
throw;
}

DEBUG_LOG("WORLD: Received opcode Player Logon Message from %s", playerGuid.GetString().c_str());

Expand Down
2 changes: 1 addition & 1 deletion src/game/WorldHandlers/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2322,7 +2322,7 @@ static void RewardGroupAtKill_helper(Player* pGroupGuy, Unit* pVictim, uint32 co
if (pGroupGuy->IsAlive() && not_gray_member_with_max_level &&
pGroupGuy->getLevel() <= not_gray_member_with_max_level->getLevel())
{
uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? uint32(xp * rate) : uint32((xp * rate / 2) + 1);
uint32 itr_xp = (member_with_max_level == not_gray_member_with_max_level) ? SafeUInt32FromFloat(xp * rate) : SafeUInt32FromFloat((xp * rate / 2) + 1);

pGroupGuy->GiveXP(itr_xp, pVictim);
if (Pet* pet = pGroupGuy->GetPet())
Expand Down
10 changes: 5 additions & 5 deletions src/game/WorldHandlers/SpellAuras.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6859,7 +6859,7 @@ void Aura::HandleModTotalPercentStat(bool apply, bool /*Real*/)
// recalculate current HP/MP after applying aura modifications (only for spells with 0x10 flag)
if ((miscValueB & (1 << STAT_STAMINA)) && maxHPValue > 0 && GetSpellProto()->HasAttribute(SPELL_ATTR_ABILITY))
{
uint32 newHPValue = uint32(float(target->GetMaxHealth()) / maxHPValue * curHPValue);
uint32 newHPValue = SafeUInt32FromFloat(float(target->GetMaxHealth()) / maxHPValue * curHPValue);
target->SetHealth(newHPValue);
}
}
Expand Down Expand Up @@ -8420,7 +8420,7 @@ void Aura::PeriodicTick()
}
else
{
pdamage = uint32(target->GetMaxHealth() * amount / 100);
pdamage = SafeUInt32FromFloat(float(target->GetMaxHealth()) * amount / 100);
}

// SpellDamageBonus for magic spells
Expand Down Expand Up @@ -8671,7 +8671,7 @@ void Aura::PeriodicTick()

if (m_modifier.m_auraname == SPELL_AURA_OBS_MOD_HEALTH)
{
pdamage = uint32(target->GetMaxHealth() * amount / 100);
pdamage = SafeUInt32FromFloat(float(target->GetMaxHealth()) * amount / 100);
}
else
{
Expand Down Expand Up @@ -8939,7 +8939,7 @@ void Aura::PeriodicTick()
// ignore non positive values (can be result apply spellmods to aura damage
uint32 amount = m_modifier.m_amount > 0 ? m_modifier.m_amount : 0;

uint32 pdamage = uint32(target->GetMaxPower(POWER_MANA) * amount / 100);
uint32 pdamage = SafeUInt32FromFloat(float(target->GetMaxPower(POWER_MANA)) * amount / 100);

DETAIL_FILTER_LOG(LOG_FILTER_PERIODIC_AFFECTS, "PeriodicTick: %s energize %s for %u mana inflicted by %u",
GetCasterGuid().GetString().c_str(), target->GetGuidStr().c_str(), pdamage, GetId());
Expand Down Expand Up @@ -9850,7 +9850,7 @@ void Aura::PeriodicDummyTick()
if (spell->IsFitToFamilyMask(UI64LIT(0x0000000020000000)))
{
// damage not expected to be show in logs, not any damage spell related to damage apply
uint32 deal = m_modifier.m_amount * target->GetMaxHealth() / 100;
uint32 deal = SafeUInt32FromFloat(float(m_modifier.m_amount) * target->GetMaxHealth() / 100);
target->DealDamage(target, deal, NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false);
return;
}
Expand Down
8 changes: 4 additions & 4 deletions src/game/WorldHandlers/SpellEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5641,7 +5641,7 @@ void Spell::EffectHealPct(SpellEffectEntry const* /*effect*/)
return;
}

uint32 addhealth = unitTarget->GetMaxHealth() * damage / 100;
uint32 addhealth = SafeUInt32FromFloat(float(unitTarget->GetMaxHealth()) * damage / 100);

addhealth = caster->SpellHealingBonusDone(unitTarget, m_spellInfo, addhealth, HEAL);
addhealth = unitTarget->SpellHealingBonusTaken(caster, m_spellInfo, addhealth, HEAL);
Expand Down Expand Up @@ -12668,8 +12668,8 @@ void Spell::EffectResurrect(SpellEffectEntry const* /*effect*/)
return;
}

uint32 health = pTarget->GetMaxHealth() * damage / 100;
uint32 mana = pTarget->GetMaxPower(POWER_MANA) * damage / 100;
uint32 health = SafeUInt32FromFloat(float(pTarget->GetMaxHealth()) * damage / 100);
uint32 mana = SafeUInt32FromFloat(float(pTarget->GetMaxPower(POWER_MANA)) * damage / 100);

pTarget->setResurrectRequestData(m_caster->GetObjectGuid(), m_caster->GetMapId(), m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ(), health, mana);
SendResurrectRequest(pTarget);
Expand Down Expand Up @@ -13168,7 +13168,7 @@ void Spell::EffectSummonDeadPet(SpellEffectEntry const* /*effect*/)
pet->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
pet->SetDeathState(ALIVE);
pet->clearUnitState(UNIT_STAT_ALL_STATE);
pet->SetHealth(uint32(pet->GetMaxHealth() * (float(damage) / 100)));
pet->SetHealth(SafeUInt32FromFloat(pet->GetMaxHealth() * (float(damage) / 100)));

pet->AIM_Initialize();

Expand Down
Loading
Loading