aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sql/updates/world/master/2017_07_29_00_world.sql9
-rw-r--r--src/server/game/Achievements/CriteriaHandler.cpp4
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp132
-rw-r--r--src/server/game/Entities/Creature/Creature.h12
-rw-r--r--src/server/game/Entities/Creature/CreatureData.h9
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp4
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h2
-rw-r--r--src/server/game/Entities/Object/Object.cpp4
-rw-r--r--src/server/game/Entities/Object/Object.h42
-rw-r--r--src/server/game/Entities/Player/KillRewarder.cpp2
-rw-r--r--src/server/game/Entities/Player/Player.cpp25
-rw-r--r--src/server/game/Entities/Player/Player.h1
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp47
-rw-r--r--src/server/game/Entities/Unit/Unit.h8
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp39
-rw-r--r--src/server/game/Globals/ObjectMgr.h1
-rw-r--r--src/server/game/Handlers/SpellHandler.cpp2
-rw-r--r--src/server/game/Miscellaneous/Formulas.h2
-rw-r--r--src/server/game/Server/Packets/CombatLogPacketsCommon.h19
-rw-r--r--src/server/game/Server/Packets/SpellPackets.cpp88
-rw-r--r--src/server/game/Spells/Spell.cpp10
-rw-r--r--src/server/game/Spells/SpellEffects.cpp4
-rw-r--r--src/server/game/World/World.cpp3
23 files changed, 384 insertions, 85 deletions
diff --git a/sql/updates/world/master/2017_07_29_00_world.sql b/sql/updates/world/master/2017_07_29_00_world.sql
new file mode 100644
index 00000000000..9307960390f
--- /dev/null
+++ b/sql/updates/world/master/2017_07_29_00_world.sql
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS `creature_template_scaling`;
+CREATE TABLE `creature_template_scaling` (
+ `Entry` MEDIUMINT (8) UNSIGNED NOT NULL,
+ `LevelScalingMin` SMALLINT (5) UNSIGNED NOT NULL DEFAULT 0,
+ `LevelScalingMax` SMALLINT (5) UNSIGNED NOT NULL DEFAULT 0,
+ `LevelScalingDelta` SMALLINT (5) NOT NULL DEFAULT 0,
+ `VerifiedBuild` SMALLINT (5) DEFAULT NULL,
+ PRIMARY KEY (`Entry`)
+);
diff --git a/src/server/game/Achievements/CriteriaHandler.cpp b/src/server/game/Achievements/CriteriaHandler.cpp
index 466fcbf8880..cbf296ddb7e 100644
--- a/src/server/game/Achievements/CriteriaHandler.cpp
+++ b/src/server/game/Achievements/CriteriaHandler.cpp
@@ -320,7 +320,7 @@ bool CriteriaData::Meets(uint32 criteriaId, Player const* source, Unit const* ta
case CRITERIA_DATA_TYPE_T_LEVEL:
if (!target)
return false;
- return target->getLevel() >= Level.Min;
+ return target->GetLevelForTarget(source) >= Level.Min;
case CRITERIA_DATA_TYPE_T_GENDER:
if (!target)
return false;
@@ -1683,7 +1683,7 @@ bool CriteriaHandler::AdditionalRequirementsSatisfied(ModifierTreeNode const* tr
return false;
break;
case CRITERIA_ADDITIONAL_CONDITION_TARGET_LEVEL: // 40
- if (!unit || unit->getLevel() != reqValue)
+ if (!unit || unit->GetLevelForTarget(referencePlayer) != reqValue)
return false;
break;
case CRITERIA_ADDITIONAL_CONDITION_TARGET_ZONE: // 41
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 80f775e410a..c7698b706c3 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -437,8 +437,8 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/,
if (updateLevel)
SelectLevel();
-
- UpdateLevelDependantStats();
+ else
+ UpdateLevelDependantStats(); // We still re-initialize level dependant stats on entry update
SetMeleeDamageSchool(SpellSchools(cInfo->dmgschool));
SetModifierValue(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_HOLY]));
@@ -1186,11 +1186,23 @@ void Creature::SelectLevel()
{
CreatureTemplate const* cInfo = GetCreatureTemplate();
- // level
- uint8 minlevel = std::min(cInfo->maxlevel, cInfo->minlevel);
- uint8 maxlevel = std::max(cInfo->maxlevel, cInfo->minlevel);
- uint8 level = minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel);
- SetLevel(level);
+ if (!HasScalableLevels())
+ {
+ // level
+ uint8 minlevel = std::min(cInfo->maxlevel, cInfo->minlevel);
+ uint8 maxlevel = std::max(cInfo->maxlevel, cInfo->minlevel);
+ uint8 level = minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel);
+ SetLevel(level);
+ }
+ else
+ {
+ SetLevel(cInfo->levelScaling->MaxLevel);
+ SetUInt32Value(UNIT_FIELD_SCALING_LEVEL_MIN, cInfo->levelScaling->MinLevel);
+ SetUInt32Value(UNIT_FIELD_SCALING_LEVEL_MAX, cInfo->levelScaling->MaxLevel);
+ SetUInt32Value(UNIT_FIELD_SCALING_LEVEL_DELTA, cInfo->levelScaling->DeltaLevel);
+ }
+
+ UpdateLevelDependantStats();
}
void Creature::UpdateLevelDependantStats()
@@ -1612,7 +1624,7 @@ bool Creature::CanStartAttack(Unit const* who, bool force) const
return false;
// No aggro from gray creatures
- if (CheckNoGrayAggroConfig(who->getLevelForTarget(this), getLevelForTarget(who)))
+ if (CheckNoGrayAggroConfig(who->GetLevelForTarget(this), GetLevelForTarget(who)))
return false;
return IsWithinLOSInMap(who);
@@ -1640,8 +1652,8 @@ float Creature::GetAttackDistance(Unit const* player) const
if (aggroRate == 0)
return 0.0f;
- uint32 playerlevel = player->getLevelForTarget(this);
- uint32 creaturelevel = getLevelForTarget(player);
+ uint32 playerlevel = player->GetLevelForTarget(this);
+ uint32 creaturelevel = GetLevelForTarget(player);
int32 leveldif = int32(playerlevel) - int32(creaturelevel);
@@ -1779,8 +1791,8 @@ void Creature::Respawn(bool force)
loot.clear();
if (m_originalEntry != GetEntry())
UpdateEntry(m_originalEntry);
-
- SelectLevel();
+ else
+ SelectLevel();
setDeathState(JUST_RESPAWNED);
@@ -2440,17 +2452,89 @@ void Creature::AllLootRemovedFromCorpse()
m_respawnTime = m_corpseRemoveTime + m_respawnDelay;
}
-uint8 Creature::getLevelForTarget(WorldObject const* target) const
+bool Creature::HasScalableLevels() const
{
- if (!isWorldBoss() || !target->ToUnit())
- return Unit::getLevelForTarget(target);
+ CreatureTemplate const* cinfo = GetCreatureTemplate();
+ return cinfo->levelScaling.is_initialized();
+}
+
+uint64 Creature::GetMaxHealthByLevel(uint8 level) const
+{
+ CreatureTemplate const* cInfo = GetCreatureTemplate();
+ CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(level, cInfo->unit_class);
+ return stats->GenerateHealth(cInfo);
+}
+
+float Creature::GetHealthMultiplierForTarget(WorldObject const* target) const
+{
+ if (!HasScalableLevels())
+ return 1.0f;
+
+ uint8 levelForTarget = GetLevelForTarget(target);
+ if (getLevel() < levelForTarget)
+ return 1.0f;
+
+ return double(GetMaxHealthByLevel(levelForTarget)) / double(GetCreateHealth());
+}
+
+float Creature::GetBaseDamageForLevel(uint8 level) const
+{
+ CreatureTemplate const* cInfo = GetCreatureTemplate();
+ CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(level, cInfo->unit_class);
+ return stats->GenerateBaseDamage(cInfo);
+}
+
+float Creature::GetDamageMultiplierForTarget(WorldObject const* target) const
+{
+ if (!HasScalableLevels())
+ return 1.0f;
+
+ uint8 levelForTarget = GetLevelForTarget(target);
+
+ return GetBaseDamageForLevel(levelForTarget) / GetBaseDamageForLevel(getLevel());
+}
+
+float Creature::GetBaseArmorForLevel(uint8 level) const
+{
+ CreatureTemplate const* cInfo = GetCreatureTemplate();
+ CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(level, cInfo->unit_class);
+ return stats->GenerateArmor(cInfo);
+}
+
+float Creature::GetArmorMultiplierForTarget(WorldObject const* target) const
+{
+ if (!HasScalableLevels())
+ return 1.0f;
+
+ uint8 levelForTarget = GetLevelForTarget(target);
+
+ return GetBaseArmorForLevel(levelForTarget) / GetBaseArmorForLevel(getLevel());
+}
+
+uint8 Creature::GetLevelForTarget(WorldObject const* target) const
+{
+ if (Unit const* unitTarget = target->ToUnit())
+ {
+ if (isWorldBoss())
+ {
+ uint8 level = unitTarget->getLevel() + sWorld->getIntConfig(CONFIG_WORLD_BOSS_LEVEL_DIFF);
+ return RoundToInterval<uint8>(level, 1u, 255u);
+ }
+
+ // If this creature should scale level, adapt level depending of target level
+ // between UNIT_FIELD_SCALING_LEVEL_MIN and UNIT_FIELD_SCALING_LEVEL_MAX
+ if (HasScalableLevels())
+ {
+ uint8 targetLevelWithDelta = unitTarget->getLevel() + GetCreatureTemplate()->levelScaling->DeltaLevel;
+
+ if (target->IsPlayer())
+ targetLevelWithDelta += target->GetUInt32Value(PLAYER_FIELD_SCALING_PLAYER_LEVEL_DELTA);
+
+ return RoundToInterval<uint8>(targetLevelWithDelta, GetUInt32Value(UNIT_FIELD_SCALING_LEVEL_MIN), GetUInt32Value(UNIT_FIELD_SCALING_LEVEL_MAX));
+ }
+ }
- uint16 level = target->ToUnit()->getLevel() + sWorld->getIntConfig(CONFIG_WORLD_BOSS_LEVEL_DIFF);
- if (level < 1)
- return 1;
- if (level > 255)
- return 255;
- return uint8(level);
+ return Unit::GetLevelForTarget(target);
}
std::string Creature::GetAIName() const
@@ -2601,11 +2685,11 @@ float Creature::GetAggroRange(Unit const* target) const
uint32 targetLevel = 0;
if (target->GetTypeId() == TYPEID_PLAYER)
- targetLevel = target->getLevelForTarget(this);
+ targetLevel = target->GetLevelForTarget(this);
else if (target->GetTypeId() == TYPEID_UNIT)
- targetLevel = target->ToCreature()->getLevelForTarget(this);
+ targetLevel = target->ToCreature()->GetLevelForTarget(this);
- uint32 myLevel = getLevelForTarget(target);
+ uint32 myLevel = GetLevelForTarget(target);
int32 levelDiff = int32(targetLevel) - int32(myLevel);
// The maximum Aggro Radius is capped at 45 yards (25 level difference)
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 320cb16ad12..a1344752b0d 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -112,7 +112,17 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool IsDungeonBoss() const;
- uint8 getLevelForTarget(WorldObject const* target) const override; // overwrite Unit::getLevelForTarget for boss level support
+ bool HasScalableLevels() const;
+ uint8 GetLevelForTarget(WorldObject const* target) const override;
+
+ uint64 GetMaxHealthByLevel(uint8 level) const;
+ float GetHealthMultiplierForTarget(WorldObject const* target) const override;
+
+ float GetBaseDamageForLevel(uint8 level) const;
+ float GetDamageMultiplierForTarget(WorldObject const* target) const override;
+
+ float GetBaseArmorForLevel(uint8 level) const;
+ float GetArmorMultiplierForTarget(WorldObject const* target) const override;
bool IsInEvadeMode() const { return HasUnitState(UNIT_STATE_EVADE); }
bool IsEvadingAttacks() const { return IsInEvadeMode() || CanNotReachTarget(); }
diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h
index 4b62e3ba138..876148a1c1f 100644
--- a/src/server/game/Entities/Creature/CreatureData.h
+++ b/src/server/game/Entities/Creature/CreatureData.h
@@ -19,6 +19,7 @@
#define CreatureData_h__
#include "DBCEnums.h"
+#include "Optional.h"
#include "SharedDefines.h"
#include "UnitDefines.h"
#include <string>
@@ -291,6 +292,13 @@ const uint32 MAX_CREATURE_NAMES = 4;
const uint32 MAX_CREATURE_SPELLS = 8;
const uint32 MAX_CREATURE_DIFFICULTIES = 3;
+struct CreatureLevelScaling
+{
+ uint16 MinLevel;
+ uint16 MaxLevel;
+ int16 DeltaLevel;
+};
+
// from `creature_template` table
struct TC_GAME_API CreatureTemplate
{
@@ -308,6 +316,7 @@ struct TC_GAME_API CreatureTemplate
uint32 GossipMenuId;
int16 minlevel;
int16 maxlevel;
+ Optional<CreatureLevelScaling> levelScaling;
int32 HealthScalingExpansion;
uint32 RequiredExpansion;
uint32 VignetteID; /// @todo Read Vignette.db2
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index c3d5c15e0e6..e5bcf6623c4 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -1113,10 +1113,10 @@ bool GameObject::IsInvisibleDueToDespawn() const
return false;
}
-uint8 GameObject::getLevelForTarget(WorldObject const* target) const
+uint8 GameObject::GetLevelForTarget(WorldObject const* target) const
{
if (Unit* owner = GetOwner())
- return owner->getLevelForTarget(target);
+ return owner->GetLevelForTarget(target);
return 1;
}
diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h
index e55f74c4b2b..f91aa3d4672 100644
--- a/src/server/game/Entities/GameObject/GameObject.h
+++ b/src/server/game/Entities/GameObject/GameObject.h
@@ -234,7 +234,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
bool IsAlwaysVisibleFor(WorldObject const* seer) const override;
bool IsInvisibleDueToDespawn() const override;
- uint8 getLevelForTarget(WorldObject const* target) const override;
+ uint8 GetLevelForTarget(WorldObject const* target) const override;
GameObject* LookupFishingHoleAround(float range);
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp
index 1510ac718d9..8746121ecbc 100644
--- a/src/server/game/Entities/Object/Object.cpp
+++ b/src/server/game/Entities/Object/Object.cpp
@@ -2231,13 +2231,13 @@ bool WorldObject::CanDetectStealthOf(WorldObject const* obj, bool checkAlert) co
// Level difference: 5 point / level, starting from level 1.
// There may be spells for this and the starting points too, but
// not in the DBCs of the client.
- detectionValue += int32(getLevelForTarget(obj) - 1) * 5;
+ detectionValue += int32(GetLevelForTarget(obj) - 1) * 5;
// Apply modifiers
detectionValue += m_stealthDetect.GetValue(StealthType(i));
if (go)
if (Unit* owner = go->GetOwner())
- detectionValue -= int32(owner->getLevelForTarget(this) - 1) * 5;
+ detectionValue -= int32(owner->GetLevelForTarget(this) - 1) * 5;
detectionValue -= obj->m_stealth.GetValue(StealthType(i));
diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h
index 7b21be71a39..942c1792367 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -255,29 +255,37 @@ class TC_GAME_API Object
// FG: some hacky helpers
void ForceValuesUpdateAtIndex(uint32);
- Player* ToPlayer() { if (GetTypeId() == TYPEID_PLAYER) return reinterpret_cast<Player*>(this); else return NULL; }
- Player const* ToPlayer() const { if (GetTypeId() == TYPEID_PLAYER) return reinterpret_cast<Player const*>(this); else return NULL; }
+ inline bool IsPlayer() const { return GetTypeId() == TYPEID_PLAYER; }
+ Player* ToPlayer() { if (IsPlayer()) return reinterpret_cast<Player*>(this); else return nullptr; }
+ Player const* ToPlayer() const { if (IsPlayer()) return reinterpret_cast<Player const*>(this); else return nullptr; }
- Creature* ToCreature() { if (GetTypeId() == TYPEID_UNIT) return reinterpret_cast<Creature*>(this); else return NULL; }
- Creature const* ToCreature() const { if (GetTypeId() == TYPEID_UNIT) return reinterpret_cast<Creature const*>(this); else return NULL; }
+ inline bool IsCreature() const { return GetTypeId() == TYPEID_UNIT; }
+ Creature* ToCreature() { if (IsCreature()) return reinterpret_cast<Creature*>(this); else return nullptr; }
+ Creature const* ToCreature() const { if (IsCreature()) return reinterpret_cast<Creature const*>(this); else return nullptr; }
- Unit* ToUnit() { if (isType(TYPEMASK_UNIT)) return reinterpret_cast<Unit*>(this); else return NULL; }
- Unit const* ToUnit() const { if (isType(TYPEMASK_UNIT)) return reinterpret_cast<Unit const*>(this); else return NULL; }
+ inline bool IsUnit() const { return isType(TYPEMASK_UNIT); }
+ Unit* ToUnit() { if (IsUnit()) return reinterpret_cast<Unit*>(this); else return nullptr; }
+ Unit const* ToUnit() const { if (IsUnit()) return reinterpret_cast<Unit const*>(this); else return nullptr; }
- GameObject* ToGameObject() { if (GetTypeId() == TYPEID_GAMEOBJECT) return reinterpret_cast<GameObject*>(this); else return NULL; }
- GameObject const* ToGameObject() const { if (GetTypeId() == TYPEID_GAMEOBJECT) return reinterpret_cast<GameObject const*>(this); else return NULL; }
+ inline bool IsGameObject() const { return GetTypeId() == TYPEID_GAMEOBJECT; }
+ GameObject* ToGameObject() { if (IsGameObject()) return reinterpret_cast<GameObject*>(this); else return nullptr; }
+ GameObject const* ToGameObject() const { if (IsGameObject()) return reinterpret_cast<GameObject const*>(this); else return nullptr; }
- Corpse* ToCorpse() { if (GetTypeId() == TYPEID_CORPSE) return reinterpret_cast<Corpse*>(this); else return NULL; }
- Corpse const* ToCorpse() const { if (GetTypeId() == TYPEID_CORPSE) return reinterpret_cast<Corpse const*>(this); else return NULL; }
+ inline bool IsCorpse() const { return GetTypeId() == TYPEID_CORPSE; }
+ Corpse* ToCorpse() { if (IsCorpse()) return reinterpret_cast<Corpse*>(this); else return nullptr; }
+ Corpse const* ToCorpse() const { if (IsCorpse()) return reinterpret_cast<Corpse const*>(this); else return nullptr; }
- DynamicObject* ToDynObject() { if (GetTypeId() == TYPEID_DYNAMICOBJECT) return reinterpret_cast<DynamicObject*>(this); else return NULL; }
- DynamicObject const* ToDynObject() const { if (GetTypeId() == TYPEID_DYNAMICOBJECT) return reinterpret_cast<DynamicObject const*>(this); else return NULL; }
+ inline bool IsDynObject() const { return GetTypeId() == TYPEID_DYNAMICOBJECT; }
+ DynamicObject* ToDynObject() { if (IsDynObject()) return reinterpret_cast<DynamicObject*>(this); else return nullptr; }
+ DynamicObject const* ToDynObject() const { if (IsDynObject()) return reinterpret_cast<DynamicObject const*>(this); else return nullptr; }
- AreaTrigger* ToAreaTrigger() { if (GetTypeId() == TYPEID_AREATRIGGER) return reinterpret_cast<AreaTrigger*>(this); else return NULL; }
- AreaTrigger const* ToAreaTrigger() const { if (GetTypeId() == TYPEID_AREATRIGGER) return reinterpret_cast<AreaTrigger const*>(this); else return NULL; }
+ inline bool IsAreaTrigger() const { return GetTypeId() == TYPEID_AREATRIGGER; }
+ AreaTrigger* ToAreaTrigger() { if (IsAreaTrigger()) return reinterpret_cast<AreaTrigger*>(this); else return nullptr; }
+ AreaTrigger const* ToAreaTrigger() const { if (IsAreaTrigger()) return reinterpret_cast<AreaTrigger const*>(this); else return nullptr; }
- Conversation* ToConversation() { if (GetTypeId() == TYPEID_CONVERSATION) return reinterpret_cast<Conversation*>(this); else return NULL; }
- Conversation const* ToConversation() const { if (GetTypeId() == TYPEID_CONVERSATION) return reinterpret_cast<Conversation const*>(this); else return NULL; }
+ inline bool IsConversation() const { return GetTypeId() == TYPEID_CONVERSATION; }
+ Conversation* ToConversation() { if (GetTypeId() == TYPEID_CONVERSATION) return reinterpret_cast<Conversation*>(this); else return nullptr; }
+ Conversation const* ToConversation() const { if (GetTypeId() == TYPEID_CONVERSATION) return reinterpret_cast<Conversation const*>(this); else return nullptr; }
protected:
Object();
@@ -468,7 +476,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
virtual void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const;
virtual void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const;
- virtual uint8 getLevelForTarget(WorldObject const* /*target*/) const { return 1; }
+ virtual uint8 GetLevelForTarget(WorldObject const* /*target*/) const { return 1; }
void PlayDistanceSound(uint32 soundId, Player* target = nullptr);
void PlayDirectSound(uint32 soundId, Player* target = nullptr);
diff --git a/src/server/game/Entities/Player/KillRewarder.cpp b/src/server/game/Entities/Player/KillRewarder.cpp
index 1e89d90cec6..096dcadf61f 100644
--- a/src/server/game/Entities/Player/KillRewarder.cpp
+++ b/src/server/game/Entities/Player/KillRewarder.cpp
@@ -107,7 +107,7 @@ inline void KillRewarder::_InitGroupData()
// 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance,
// for whom victim is not gray;
uint32 grayLevel = Trinity::XP::GetGrayLevel(lvl);
- if (_victim->getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->getLevel() < lvl))
+ if (_victim->GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember->getLevel() < lvl))
_maxNotGrayMember = member;
}
// 2.5. _isFullXP - flag identifying that for all group members victim is not gray,
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index b0938712855..4da32995924 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -2334,6 +2334,19 @@ void Player::RemoveFromGroup(Group* group, ObjectGuid guid, RemoveMethod method
group->RemoveMember(guid, method, kicker, reason);
}
+void Player::SetXP(uint32 xp)
+{
+ SetUInt32Value(PLAYER_XP, xp);
+
+ int32 playerLevelDelta = 0;
+
+ // If XP < 50%, player should see scaling creature with -1 level except for level max
+ if (getLevel() < MAX_LEVEL && xp < (GetUInt32Value(PLAYER_NEXT_LEVEL_XP) / 2))
+ playerLevelDelta = -1;
+
+ SetInt32Value(PLAYER_FIELD_SCALING_PLAYER_LEVEL_DELTA, playerLevelDelta);
+}
+
void Player::GiveXP(uint32 xp, Unit* victim, float group_rate)
{
if (xp < 1)
@@ -2389,7 +2402,7 @@ void Player::GiveXP(uint32 xp, Unit* victim, float group_rate)
nextLvlXP = GetUInt32Value(PLAYER_NEXT_LEVEL_XP);
}
- SetUInt32Value(PLAYER_XP, newXP);
+ SetXP(newXP);
}
// Update player to next level
@@ -6123,7 +6136,7 @@ void Player::RewardReputation(Unit* victim, float rate)
if (Rep->RepFaction1 && (!Rep->TeamDependent || team == ALLIANCE))
{
- int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->getLevel(), Rep->RepValue1, ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
+ int32 donerep1 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevelForTarget(this), Rep->RepValue1, ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
donerep1 = int32(donerep1 * rate);
FactionEntry const* factionEntry1 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction1);
@@ -6134,7 +6147,7 @@ void Player::RewardReputation(Unit* victim, float rate)
if (Rep->RepFaction2 && (!Rep->TeamDependent || team == HORDE))
{
- int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->getLevel(), Rep->RepValue2, ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
+ int32 donerep2 = CalculateReputationGain(REPUTATION_SOURCE_KILL, victim->GetLevelForTarget(this), Rep->RepValue2, ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
donerep2 = int32(donerep2 * rate);
FactionEntry const* factionEntry2 = sFactionStore.LookupEntry(ChampioningFaction ? ChampioningFaction : Rep->RepFaction2);
@@ -6272,7 +6285,7 @@ bool Player::RewardHonor(Unit* victim, uint32 groupsize, int32 honor, bool pvpto
uint8 k_level = getLevel();
uint8 k_grey = Trinity::XP::GetGrayLevel(k_level);
- uint8 v_level = victim->getLevel();
+ uint8 v_level = victim->GetLevelForTarget(this);
if (v_level <= k_grey)
return false;
@@ -17352,7 +17365,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
}
SetUInt32Value(UNIT_FIELD_LEVEL, fields[6].GetUInt8());
- SetUInt32Value(PLAYER_XP, fields[7].GetUInt32());
+ SetXP(fields[7].GetUInt32());
_LoadIntoDataField(fields[65].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE);
_LoadIntoDataField(fields[66].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE * 2);
@@ -24571,7 +24584,7 @@ uint32 Player::GetResurrectionSpellId() const
// Used in triggers for check "Only to targets that grant experience or honor" req
bool Player::isHonorOrXPTarget(Unit const* victim) const
{
- uint8 v_level = victim->getLevel();
+ uint8 v_level = victim->GetLevelForTarget(this);
uint8 k_grey = Trinity::XP::GetGrayLevel(getLevel());
// Victim level less gray level
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index b96a44eb310..385b2ba6917 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1106,6 +1106,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetGMVisible(bool on);
void SetPvPDeath(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_PVP_DEATH; else m_ExtraFlags &= ~PLAYER_EXTRA_PVP_DEATH; }
+ void SetXP(uint32 xp);
void GiveXP(uint32 xp, Unit* victim, float group_rate=1.0f);
void GiveLevel(uint8 level);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 4d1c88b2a8b..17a51e9d5b2 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -691,7 +691,10 @@ void Unit::DealDamageMods(Unit* victim, uint32 &damage, uint32* absorb)
if (absorb)
*absorb += damage;
damage = 0;
+ return;
}
+
+ damage *= GetDamageMultiplierForTarget(victim);
}
uint32 Unit::DealDamage(Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss)
@@ -839,6 +842,8 @@ uint32 Unit::DealDamage(Unit* victim, uint32 damage, CleanDamage const* cleanDam
victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage);
}
+ damage /= victim->GetHealthMultiplierForTarget(this);
+
if (health <= damage)
{
TC_LOG_DEBUG("entities.unit", "DealDamage: victim just died");
@@ -1443,7 +1448,7 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss)
// there is a newbie protection, at level 10 just 7% base chance; assuming linear function
if (victim->getLevel() < 30)
- Probability = 0.65f * victim->getLevel() + 0.5f;
+ Probability = 0.65f * victim->GetLevelForTarget(this) + 0.5f;
uint32 VictimDefense = victim->GetMaxSkillValueForLevel(this);
uint32 AttackerMeleeSkill = GetMaxSkillValueForLevel();
@@ -1554,6 +1559,8 @@ uint32 Unit::CalcArmorReducedDamage(Unit* attacker, Unit* victim, const uint32 d
{
float armor = float(victim->GetArmor());
+ armor *= victim->GetArmorMultiplierForTarget(attacker);
+
// bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER
int32 armorBypassPct = 0;
AuraEffectList const & reductionAuras = victim->GetAuraEffectsByType(SPELL_AURA_BYPASS_ARMOR_FOR_CASTER);
@@ -1580,10 +1587,10 @@ uint32 Unit::CalcArmorReducedDamage(Unit* attacker, Unit* victim, const uint32 d
if (GetTypeId() == TYPEID_PLAYER)
{
float maxArmorPen = 0;
- if (victim->getLevel() < 60)
- maxArmorPen = float(400 + 85 * victim->getLevel());
+ if (victim->GetLevelForTarget(attacker) < 60)
+ maxArmorPen = float(400 + 85 * victim->GetLevelForTarget(attacker));
else
- maxArmorPen = 400 + 85 * victim->getLevel() + 4.5f * 85 * (victim->getLevel() - 59);
+ maxArmorPen = 400 + 85 * victim->GetLevelForTarget(attacker) + 4.5f * 85 * (victim->GetLevelForTarget(attacker) - 59);
// Cap armor penetration to this number
maxArmorPen = std::min((armor + maxArmorPen) / 3, armor);
@@ -1621,7 +1628,7 @@ uint32 Unit::CalcSpellResistance(Unit* victim, SpellSchoolMask schoolMask, Spell
uint8 const bossLevel = 83;
uint32 const bossResistanceConstant = 510;
uint32 resistanceConstant = 0;
- uint8 level = victim->getLevel();
+ uint8 level = victim->GetLevelForTarget(this);
if (level == bossLevel)
resistanceConstant = bossResistanceConstant;
@@ -2066,8 +2073,8 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackTy
int32 sum = 0, tmp = 0;
int32 roll = urand(0, 9999);
- int32 attackerLevel = getLevelForTarget(victim);
- int32 victimLevel = getLevelForTarget(this);
+ int32 attackerLevel = GetLevelForTarget(victim);
+ int32 victimLevel = GetLevelForTarget(this);
// check if attack comes from behind, nobody can parry or block if attacker is behind
bool canParryOrBlock = victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION);
@@ -2470,10 +2477,10 @@ SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo
SpellSchoolMask schoolMask = spellInfo->GetSchoolMask();
// PvP - PvE spell misschances per leveldif > 2
int32 lchance = victim->GetTypeId() == TYPEID_PLAYER ? 7 : 11;
- int32 thisLevel = getLevelForTarget(victim);
+ int32 thisLevel = GetLevelForTarget(victim);
if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsTrigger())
thisLevel = std::max<int32>(thisLevel, spellInfo->SpellLevel);
- int32 leveldif = int32(victim->getLevelForTarget(this)) - thisLevel;
+ int32 leveldif = int32(victim->GetLevelForTarget(this)) - thisLevel;
int32 levelBasedHitDiff = leveldif;
// Base hit chance from attacker and victim levels
@@ -2601,7 +2608,7 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spellInfo, boo
float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) const
{
- int32 const levelDiff = victim->getLevelForTarget(this) - getLevelForTarget(victim);
+ int32 const levelDiff = victim->GetLevelForTarget(this) - GetLevelForTarget(victim);
float chance = 0.0f;
float levelBonus = 0.0f;
@@ -2637,7 +2644,7 @@ float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) con
float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) const
{
- int32 const levelDiff = victim->getLevelForTarget(this) - getLevelForTarget(victim);
+ int32 const levelDiff = victim->GetLevelForTarget(this) - GetLevelForTarget(victim);
float chance = 0.0f;
float levelBonus = 0.0f;
@@ -2686,7 +2693,7 @@ float Unit::GetUnitMissChance(WeaponAttackType attType) const
float Unit::GetUnitBlockChance(WeaponAttackType /*attType*/, Unit const* victim) const
{
- int32 const levelDiff = victim->getLevelForTarget(this) - getLevelForTarget(victim);
+ int32 const levelDiff = victim->GetLevelForTarget(this) - GetLevelForTarget(victim);
float chance = 0.0f;
float levelBonus = 0.0f;
@@ -5052,6 +5059,11 @@ void Unit::SendSpellNonMeleeDamageLog(SpellNonMeleeDamage const* log)
packet.Absorbed = log->absorb;
packet.Periodic = log->periodicLog;
packet.Flags = log->HitInfo;
+
+ WorldPackets::Spells::SandboxScalingData sandboxScalingData;
+ if (sandboxScalingData.GenerateDataForUnits(log->attacker, log->target))
+ packet.SandboxScaling = sandboxScalingData;
+
SendCombatLogMessage(&packet);
}
@@ -5086,6 +5098,11 @@ void Unit::SendPeriodicAuraLog(SpellPeriodicAuraLogInfo* info)
spellLogEffect.Crit = info->critical;
/// @todo: implement debug info
+ WorldPackets::Spells::SandboxScalingData sandboxScalingData;
+ if (Unit* caster = ObjectAccessor::GetUnit(*this, aura->GetCasterGUID()))
+ if (sandboxScalingData.GenerateDataForUnits(caster, this))
+ spellLogEffect.SandboxScaling = sandboxScalingData;
+
data.Effects.push_back(spellLogEffect);
SendCombatLogMessage(&data);
@@ -5141,6 +5158,10 @@ void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo)
packet.LogData.Initialize(damageInfo->attacker);
+ WorldPackets::Spells::SandboxScalingData sandboxScalingData;
+ if (sandboxScalingData.GenerateDataForUnits(damageInfo->attacker, damageInfo->target))
+ packet.SandboxScaling = sandboxScalingData;
+
SendCombatLogMessage(&packet);
}
@@ -11327,7 +11348,7 @@ Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id)
return NULL;
}
- uint8 level = creatureTarget->getLevel() + 5 < getLevel() ? (getLevel() - 5) : creatureTarget->getLevel();
+ uint8 level = creatureTarget->GetLevelForTarget(this) + 5 < getLevel() ? (getLevel() - 5) : creatureTarget->GetLevelForTarget(this);
InitTamedPet(pet, level, spell_id);
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index cd56f883e7a..0bfc51f5c12 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -1086,7 +1086,7 @@ class TC_GAME_API Unit : public WorldObject
bool IsVehicle() const { return (m_unitTypeMask & UNIT_MASK_VEHICLE) != 0; }
uint8 getLevel() const { return uint8(GetUInt32Value(UNIT_FIELD_LEVEL)); }
- uint8 getLevelForTarget(WorldObject const* /*target*/) const override { return getLevel(); }
+ uint8 GetLevelForTarget(WorldObject const* /*target*/) const override { return getLevel(); }
void SetLevel(uint8 lvl);
uint8 getRace() const { return GetByteValue(UNIT_FIELD_BYTES_0, UNIT_BYTES_0_OFFSET_RACE); }
uint32 getRaceMask() const { return 1 << (getRace()-1); }
@@ -1121,6 +1121,10 @@ class TC_GAME_API Unit : public WorldObject
int64 ModifyHealth(int64 val);
int64 GetHealthGain(int64 dVal);
+ virtual float GetHealthMultiplierForTarget(WorldObject const* /*target*/) const { return 1.0f; }
+ virtual float GetDamageMultiplierForTarget(WorldObject const* /*target*/) const { return 1.0f; }
+ virtual float GetArmorMultiplierForTarget(WorldObject const* /*target*/) const { return 1.0f; }
+
Powers getPowerType() const { return Powers(GetUInt32Value(UNIT_FIELD_DISPLAY_POWER)); }
void setPowerType(Powers power);
void UpdateDisplayPower();
@@ -1190,7 +1194,7 @@ class TC_GAME_API Unit : public WorldObject
void SetMeleeAnimKitId(uint16 animKitId);
uint16 GetMeleeAnimKitId() const override { return _meleeAnimKitId; }
- uint16 GetMaxSkillValueForLevel(Unit const* target = NULL) const { return (target ? getLevelForTarget(target) : getLevel()) * 5; }
+ uint16 GetMaxSkillValueForLevel(Unit const* target = NULL) const { return (target ? GetLevelForTarget(target) : getLevel()) * 5; }
void DealDamageMods(Unit* victim, uint32 &damage, uint32* absorb);
uint32 DealDamage(Unit* victim, uint32 damage, CleanDamage const* cleanDamage = NULL, DamageEffectType damagetype = DIRECT_DAMAGE, SpellSchoolMask damageSchoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* spellProto = NULL, bool durabilityLoss = true);
void Kill(Unit* victim, bool durabilityLoss = true);
diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp
index 2efdbfb015b..a506a7676cd 100644
--- a/src/server/game/Globals/ObjectMgr.cpp
+++ b/src/server/game/Globals/ObjectMgr.cpp
@@ -641,6 +641,45 @@ void ObjectMgr::LoadCreatureTemplateAddons()
TC_LOG_INFO("server.loading", ">> Loaded %u creature template addons in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
}
+void ObjectMgr::LoadCreatureScalingData()
+{
+ uint32 oldMSTime = getMSTime();
+
+ // 0 1
+ QueryResult result = WorldDatabase.Query("SELECT Entry, LevelScalingMin, LevelScalingMax, LevelScalingDelta FROM creature_template_scaling");
+
+ if (!result)
+ {
+ TC_LOG_INFO("server.loading", ">> Loaded 0 creature template scaling definitions. DB table `creature_template_scaling` is empty.");
+ return;
+ }
+
+ uint32 count = 0;
+ do
+ {
+ Field* fields = result->Fetch();
+
+ uint32 entry = fields[0].GetUInt32();
+
+ CreatureTemplateContainer::iterator itr = _creatureTemplateStore.find(entry);
+ if (itr == _creatureTemplateStore.end())
+ {
+ TC_LOG_ERROR("sql.sql", "Creature template (Entry: %u) does not exist but has a record in `creature_template_scaling`", entry);
+ continue;
+ }
+
+ CreatureLevelScaling creatureLevelScaling;
+ creatureLevelScaling.MinLevel = fields[1].GetUInt16();
+ creatureLevelScaling.MaxLevel = fields[2].GetUInt16();
+ creatureLevelScaling.DeltaLevel = fields[3].GetInt16();
+ itr->second.levelScaling = creatureLevelScaling;
+
+ ++count;
+ } while (result->NextRow());
+
+ TC_LOG_INFO("server.loading", ">> Loaded %u creature template scaling data in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
+}
+
void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo)
{
if (!cInfo)
diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h
index 678d0df64e6..aad57743e82 100644
--- a/src/server/game/Globals/ObjectMgr.h
+++ b/src/server/game/Globals/ObjectMgr.h
@@ -1099,6 +1099,7 @@ class TC_GAME_API ObjectMgr
void LoadCreatureTemplates();
void LoadCreatureTemplateAddons();
void LoadCreatureTemplate(Field* fields);
+ void LoadCreatureScalingData();
void CheckCreatureTemplate(CreatureTemplate const* cInfo);
void LoadGameObjectQuestItems();
void LoadCreatureQuestItems();
diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp
index 5a98941d0d6..f010d60f291 100644
--- a/src/server/game/Handlers/SpellHandler.cpp
+++ b/src/server/game/Handlers/SpellHandler.cpp
@@ -298,7 +298,7 @@ void WorldSession::HandleCastSpellOpcode(WorldPackets::Spells::CastSpell& cast)
// auto-selection buff level base at target level (in spellInfo)
if (targets.GetUnitTarget())
{
- SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(targets.GetUnitTarget()->getLevel());
+ SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(targets.GetUnitTarget()->GetLevelForTarget(caster));
// if rank not found then function return NULL but in explicit cast case original spell can be cast and later failed with appropriate error message
if (actualSpellInfo)
diff --git a/src/server/game/Miscellaneous/Formulas.h b/src/server/game/Miscellaneous/Formulas.h
index af750a5a3a6..70149995cd5 100644
--- a/src/server/game/Miscellaneous/Formulas.h
+++ b/src/server/game/Miscellaneous/Formulas.h
@@ -177,7 +177,7 @@ namespace Trinity
{
float xpMod = 1.0f;
- gain = BaseGain(player->getLevel(), u->getLevel());
+ gain = BaseGain(player->getLevel(), u->GetLevelForTarget(player));
if (gain && creature)
{
diff --git a/src/server/game/Server/Packets/CombatLogPacketsCommon.h b/src/server/game/Server/Packets/CombatLogPacketsCommon.h
index 7f8d77dd73f..232b570c85e 100644
--- a/src/server/game/Server/Packets/CombatLogPacketsCommon.h
+++ b/src/server/game/Server/Packets/CombatLogPacketsCommon.h
@@ -49,15 +49,26 @@ namespace WorldPackets
struct SandboxScalingData
{
+ enum SandboxScalingDataType : uint32
+ {
+ TYPE_PLAYER_TO_PLAYER = 1, // NYI
+ TYPE_CREATURE_TO_PLAYER_DAMAGE = 2,
+ TYPE_PLAYER_TO_CREATURE_DAMAGE = 3,
+ TYPE_CREATURE_TO_CREATURE_DAMAGE = 4
+ };
+
uint32 Type = 0;
int16 PlayerLevelDelta = 0;
uint16 PlayerItemLevel = 0;
uint8 TargetLevel = 0;
uint8 Expansion = 0;
- uint8 Class = 1;
- uint8 TargetMinScalingLevel = 1;
- uint8 TargetMaxScalingLevel = 1;
- int8 TargetScalingLevelDelta = 1;
+ uint8 Class = 0;
+ uint8 TargetMinScalingLevel = 0;
+ uint8 TargetMaxScalingLevel = 0;
+ int8 TargetScalingLevelDelta = 0;
+
+ template<class T, class U>
+ bool GenerateDataForUnits(T* attacker, U* target);
};
}
diff --git a/src/server/game/Server/Packets/SpellPackets.cpp b/src/server/game/Server/Packets/SpellPackets.cpp
index ffe17bfdffd..20f41f13e95 100644
--- a/src/server/game/Server/Packets/SpellPackets.cpp
+++ b/src/server/game/Server/Packets/SpellPackets.cpp
@@ -15,8 +15,10 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "SpellPackets.h"
+#include "Creature.h"
#include "MovementPackets.h"
+#include "Player.h"
+#include "SpellPackets.h"
void WorldPackets::Spells::CancelAura::Read()
{
@@ -90,6 +92,90 @@ WorldPacket const* WorldPackets::Spells::SendUnlearnSpells::Write()
return &_worldPacket;
}
+template<class T, class U>
+bool WorldPackets::Spells::SandboxScalingData::GenerateDataForUnits(T* /*attacker*/, U* /*target*/)
+{
+ return false;
+}
+
+template<>
+bool WorldPackets::Spells::SandboxScalingData::GenerateDataForUnits<Creature, Player>(Creature* attacker, Player* target)
+{
+ CreatureTemplate const* creatureTemplate = attacker->GetCreatureTemplate();
+
+ Type = TYPE_CREATURE_TO_PLAYER_DAMAGE;
+ PlayerLevelDelta = target->GetInt32Value(PLAYER_FIELD_SCALING_PLAYER_LEVEL_DELTA);
+ PlayerItemLevel = target->GetAverageItemLevel();
+ TargetLevel = target->getLevel();
+ Expansion = creatureTemplate->RequiredExpansion;
+ Class = creatureTemplate->unit_class;
+ TargetMinScalingLevel = uint8(creatureTemplate->levelScaling->MinLevel);
+ TargetMaxScalingLevel = uint8(creatureTemplate->levelScaling->MaxLevel);
+ TargetScalingLevelDelta = int8(creatureTemplate->levelScaling->DeltaLevel);
+ return true;
+}
+
+template<>
+bool WorldPackets::Spells::SandboxScalingData::GenerateDataForUnits<Player, Creature>(Player* attacker, Creature* target)
+{
+ CreatureTemplate const* creatureTemplate = target->GetCreatureTemplate();
+
+ Type = TYPE_PLAYER_TO_CREATURE_DAMAGE;
+ PlayerLevelDelta = attacker->GetInt32Value(PLAYER_FIELD_SCALING_PLAYER_LEVEL_DELTA);
+ PlayerItemLevel = attacker->GetAverageItemLevel();
+ TargetLevel = target->getLevel();
+ Expansion = creatureTemplate->RequiredExpansion;
+ Class = creatureTemplate->unit_class;
+ TargetMinScalingLevel = uint8(creatureTemplate->levelScaling->MinLevel);
+ TargetMaxScalingLevel = uint8(creatureTemplate->levelScaling->MaxLevel);
+ TargetScalingLevelDelta = int8(creatureTemplate->levelScaling->DeltaLevel);
+ return true;
+}
+
+template<>
+bool WorldPackets::Spells::SandboxScalingData::GenerateDataForUnits<Creature, Creature>(Creature* attacker, Creature* target)
+{
+ CreatureTemplate const* creatureTemplate = target->HasScalableLevels() ? target->GetCreatureTemplate() : attacker->GetCreatureTemplate();
+
+ Type = TYPE_CREATURE_TO_CREATURE_DAMAGE;
+ PlayerLevelDelta = 0;
+ PlayerItemLevel = 0;
+ TargetLevel = target->getLevel();
+ Expansion = creatureTemplate->RequiredExpansion;
+ Class = creatureTemplate->unit_class;
+ TargetMinScalingLevel = uint8(creatureTemplate->levelScaling->MinLevel);
+ TargetMaxScalingLevel = uint8(creatureTemplate->levelScaling->MaxLevel);
+ TargetScalingLevelDelta = int8(creatureTemplate->levelScaling->DeltaLevel);
+ return true;
+}
+
+template<>
+bool WorldPackets::Spells::SandboxScalingData::GenerateDataForUnits<Unit, Unit>(Unit* attacker, Unit* target)
+{
+ if (Player* playerAttacker = attacker->ToPlayer())
+ {
+ if (Player* playerTarget = target->ToPlayer())
+ return GenerateDataForUnits(playerAttacker, playerTarget);
+ else if (Creature* creatureTarget = target->ToCreature())
+ {
+ if (creatureTarget->HasScalableLevels())
+ return GenerateDataForUnits(playerAttacker, creatureTarget);
+ }
+ }
+ else if (Creature* creatureAttacker = attacker->ToCreature())
+ {
+ if (Player* playerTarget = target->ToPlayer())
+ return GenerateDataForUnits(creatureAttacker, playerTarget);
+ else if (Creature* creatureTarget = target->ToCreature())
+ {
+ if (creatureAttacker->HasScalableLevels() || creatureTarget->HasScalableLevels())
+ return GenerateDataForUnits(creatureAttacker, creatureTarget);
+ }
+ }
+
+ return false;
+}
+
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::AuraDataInfo const& auraData)
{
data << auraData.CastID;
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index bb5bdc602da..44085cc5025 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -2122,7 +2122,7 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*=
if (m_auraScaleMask && ihit->effectMask == m_auraScaleMask && m_caster != target)
{
SpellInfo const* auraSpell = m_spellInfo->GetFirstRankSpell();
- if (uint32(target->getLevel() + 10) >= auraSpell->SpellLevel)
+ if (uint32(target->GetLevelForTarget(m_caster) + 10) >= auraSpell->SpellLevel)
ihit->scaleAura = true;
}
return;
@@ -2143,7 +2143,7 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*=
if (m_auraScaleMask && targetInfo.effectMask == m_auraScaleMask && m_caster != target)
{
SpellInfo const* auraSpell = m_spellInfo->GetFirstRankSpell();
- if (uint32(target->getLevel() + 10) >= auraSpell->SpellLevel)
+ if (uint32(target->GetLevelForTarget(m_caster) + 10) >= auraSpell->SpellLevel)
targetInfo.scaleAura = true;
}
@@ -5302,7 +5302,7 @@ SpellCastResult Spell::CheckCast(bool strict)
uint32 skill = creature->GetCreatureTemplate()->GetRequiredLootSkill();
int32 skillValue = m_caster->ToPlayer()->GetSkillValue(skill);
- int32 TargetLevel = m_targets.GetUnitTarget()->getLevel();
+ int32 TargetLevel = m_targets.GetUnitTarget()->GetLevelForTarget(m_caster);
int32 ReqValue = (skillValue < 100 ? (TargetLevel-10) * 10 : TargetLevel * 5);
if (ReqValue > skillValue)
return SPELL_FAILED_LOW_CASTLEVEL;
@@ -5614,7 +5614,7 @@ SpellCastResult Spell::CheckCast(bool strict)
return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED;
int32 value = CalculateDamage(effect->EffectIndex, target);
- if (value && int32(target->getLevel()) > value)
+ if (value && int32(target->GetLevelForTarget(m_caster)) > value)
return SPELL_FAILED_HIGHLEVEL;
}
@@ -6773,7 +6773,7 @@ bool Spell::CheckEffectTarget(Unit const* target, SpellEffectInfo const* effect,
if (!target->GetCharmerGUID().IsEmpty())
return false;
if (int32 value = CalculateDamage(effect->EffectIndex, target))
- if ((int32)target->getLevel() > value)
+ if ((int32)target->GetLevelForTarget(m_caster) > value)
return false;
break;
default:
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 3b1297d36c4..57202975230 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -2719,7 +2719,7 @@ void Spell::EffectTameCreature(SpellEffIndex /*effIndex*/)
// "kill" original creature
creatureTarget->DespawnOrUnsummon();
- uint8 level = (creatureTarget->getLevel() < (m_caster->getLevel() - 5)) ? (m_caster->getLevel() - 5) : creatureTarget->getLevel();
+ uint8 level = (creatureTarget->GetLevelForTarget(m_caster) < (m_caster->GetLevelForTarget(creatureTarget) - 5)) ? (m_caster->GetLevelForTarget(creatureTarget) - 5) : creatureTarget->GetLevelForTarget(m_caster);
// prepare visual effect for levelup
pet->SetUInt32Value(UNIT_FIELD_LEVEL, level - 1);
@@ -4279,7 +4279,7 @@ void Spell::EffectSkinning(SpellEffIndex /*effIndex*/)
return;
Creature* creature = unitTarget->ToCreature();
- int32 targetLevel = creature->getLevel();
+ int32 targetLevel = creature->GetLevelForTarget(m_caster);
uint32 skill = creature->GetCreatureTemplate()->GetRequiredLootSkill();
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 78212696331..2692e0bbcff 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -1713,6 +1713,9 @@ void World::SetInitialWorldSettings()
TC_LOG_INFO("server.loading", "Loading Creature template addons...");
sObjectMgr->LoadCreatureTemplateAddons();
+ TC_LOG_INFO("server.loading", "Loading Creature template scaling...");
+ sObjectMgr->LoadCreatureScalingData();
+
TC_LOG_INFO("server.loading", "Loading Reputation Reward Rates...");
sObjectMgr->LoadReputationRewardRate();