From 473cea0f06808ebb7e101862536275ba8a0bcece Mon Sep 17 00:00:00 2001 From: hondacrx Date: Mon, 31 Jul 2017 14:18:07 -0400 Subject: [PATCH] Implemented Basic Creature Scaling Trainer followup Misc Fixes --- Framework/Constants/SharedConst.cs | 2 +- Framework/Constants/Spells/SpellConst.cs | 2 +- Game/Achievements/CriteriaHandler.cs | 4 +- Game/Entities/Creature/Creature.cs | 120 +++++++++++++++++++---- Game/Entities/Creature/CreatureData.cs | 9 ++ Game/Entities/Creature/Trainer.cs | 13 ++- Game/Entities/Object/WorldObject.cs | 29 ++++-- Game/Entities/Player/KillRewarder.cs | 2 +- Game/Entities/Player/Player.DB.cs | 2 +- Game/Entities/Player/Player.PvP.cs | 2 +- Game/Entities/Player/Player.cs | 30 ++++-- Game/Entities/StatSystem.cs | 4 + Game/Entities/Unit/Unit.Combat.cs | 23 +++-- Game/Entities/Unit/Unit.Pets.cs | 2 +- Game/Entities/Unit/Unit.Spells.cs | 11 +++ Game/Entities/Unit/Unit.cs | 1 + Game/Globals/ObjectManager.cs | 42 +++++++- Game/Handlers/SpellHandler.cs | 2 +- Game/Miscellaneous/Formulas.cs | 2 +- Game/Network/Packets/CombatLogPackets.cs | 6 +- Game/Network/Packets/SpellPackets.cs | 98 +++++++++++++++++- Game/Server/WorldManager.cs | 3 + Game/Spells/Auras/Aura.cs | 1 + Game/Spells/Spell.cs | 22 ++--- Game/Spells/SpellEffects.cs | 32 +++--- Game/Spells/SpellInfo.cs | 2 +- 26 files changed, 370 insertions(+), 96 deletions(-) diff --git a/Framework/Constants/SharedConst.cs b/Framework/Constants/SharedConst.cs index 5e00e987c..f5a8797cd 100644 --- a/Framework/Constants/SharedConst.cs +++ b/Framework/Constants/SharedConst.cs @@ -52,7 +52,7 @@ namespace Framework.Constants /// /// Lfg Const /// - public const uint LFGTimeRolecheck = 45 * Time.InMilliseconds; + public const uint LFGTimeRolecheck = 45; public const uint LFGTimeBoot = 120; public const uint LFGTimeProposal = 45; public const uint LFGQueueUpdateInterval = 15 * Time.InMilliseconds; diff --git a/Framework/Constants/Spells/SpellConst.cs b/Framework/Constants/Spells/SpellConst.cs index 95f315a2f..8b43b7fac 100644 --- a/Framework/Constants/Spells/SpellConst.cs +++ b/Framework/Constants/Spells/SpellConst.cs @@ -1840,7 +1840,7 @@ namespace Framework.Constants Threat = 63, TriggerSpell = 64, ApplyAreaAuraRaid = 65, - CreateManaGem = 66, + RechargeItem = 66, HealMaxHealth = 67, InterruptCast = 68, Distract = 69, diff --git a/Game/Achievements/CriteriaHandler.cs b/Game/Achievements/CriteriaHandler.cs index de5dbbfec..768d32238 100644 --- a/Game/Achievements/CriteriaHandler.cs +++ b/Game/Achievements/CriteriaHandler.cs @@ -1263,7 +1263,7 @@ namespace Game.Achievements return false; break; case CriteriaAdditionalCondition.TargetLevel: // 40 - if (unit == null || unit.getLevel() != reqValue) + if (unit == null || unit.GetLevelForTarget(referencePlayer) != reqValue) return false; break; case CriteriaAdditionalCondition.TargetZone: // 41 @@ -2035,7 +2035,7 @@ namespace Game.Achievements case CriteriaDataType.TLevel: if (target == null) return false; - return target.getLevel() >= Level.Min; + return target.GetLevelForTarget(source) >= Level.Min; case CriteriaDataType.TGender: if (target == null) return false; diff --git a/Game/Entities/Creature/Creature.cs b/Game/Entities/Creature/Creature.cs index ce5cd14df..7e33ae546 100644 --- a/Game/Entities/Creature/Creature.cs +++ b/Game/Entities/Creature/Creature.cs @@ -288,8 +288,8 @@ namespace Game.Entities if (updateLevel) SelectLevel(); - - UpdateLevelDependantStats(); + else + UpdateLevelDependantStats(); // We still re-initialize level dependant stats on entry update SetMeleeDamageSchool((SpellSchools)cInfo.DmgSchool); SetModifierValue(UnitMods.ResistanceHoly, UnitModifierType.BaseValue, cInfo.Resistance[(int)SpellSchools.Holy]); @@ -1090,11 +1090,23 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); - // level - byte minlevel = (byte)Math.Min(cInfo.Maxlevel, cInfo.Minlevel); - byte maxlevel = (byte)Math.Max(cInfo.Maxlevel, cInfo.Minlevel); - byte level = (byte)(minlevel == maxlevel ? minlevel : RandomHelper.URand(minlevel, maxlevel)); - SetLevel(level); + if (!HasScalableLevels()) + { + // level + byte minlevel = (byte)Math.Min(cInfo.Maxlevel, cInfo.Minlevel); + byte maxlevel = (byte)Math.Max(cInfo.Maxlevel, cInfo.Minlevel); + byte level = (byte)(minlevel == maxlevel ? minlevel : RandomHelper.URand(minlevel, maxlevel)); + SetLevel(level); + } + else + { + SetLevel(cInfo.levelScaling.Value.MaxLevel); + SetUInt32Value(UnitFields.ScalingLevelMin, cInfo.levelScaling.Value.MinLevel); + SetUInt32Value(UnitFields.ScalingLevelMax, cInfo.levelScaling.Value.MaxLevel); + SetUInt32Value(UnitFields.ScalingLevelDelta, (uint)cInfo.levelScaling.Value.DeltaLevel); + } + + UpdateLevelDependantStats(); } void UpdateLevelDependantStats() @@ -1663,8 +1675,8 @@ namespace Game.Entities if (m_originalEntry != GetEntry()) UpdateEntry(m_originalEntry); - - SelectLevel(); + else + SelectLevel(); setDeathState(DeathState.JustRespawned); @@ -2344,17 +2356,91 @@ namespace Game.Entities m_respawnTime = m_corpseRemoveTime + m_respawnDelay; } + public bool HasScalableLevels() + { + CreatureTemplate cinfo = GetCreatureTemplate(); + return cinfo.levelScaling.HasValue; + } + + ulong GetMaxHealthByLevel(uint level) + { + CreatureTemplate cInfo = GetCreatureTemplate(); + CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass); + return stats.GenerateHealth(cInfo); + } + + float GetHealthMultiplierForTarget(WorldObject target) + { + if (!HasScalableLevels()) + return 1.0f; + + uint levelForTarget = GetLevelForTarget(target); + if (getLevel() < levelForTarget) + return 1.0f; + + return GetMaxHealthByLevel(levelForTarget) / GetCreateHealth(); + } + + float GetBaseDamageForLevel(uint level) + { + CreatureTemplate cInfo = GetCreatureTemplate(); + CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass); + return stats.GenerateBaseDamage(cInfo); + } + + float GetDamageMultiplierForTarget(WorldObject target) + { + if (!HasScalableLevels()) + return 1.0f; + + uint levelForTarget = GetLevelForTarget(target); + + return GetBaseDamageForLevel(levelForTarget) / GetBaseDamageForLevel(getLevel()); + } + + float GetBaseArmorForLevel(uint level) + { + CreatureTemplate cInfo = GetCreatureTemplate(); + CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(level, cInfo.UnitClass); + return stats.GenerateArmor(cInfo); + } + + float GetArmorMultiplierForTarget(WorldObject target) + { + if (!HasScalableLevels()) + return 1.0f; + + uint levelForTarget = GetLevelForTarget(target); + + return GetBaseArmorForLevel(levelForTarget) / GetBaseArmorForLevel(getLevel()); + } + public override uint GetLevelForTarget(WorldObject target) { - if (!isWorldBoss() || !target.IsTypeId(TypeId.Unit)) - return base.GetLevelForTarget(target); + Unit unitTarget = target.ToUnit(); + if (unitTarget) + { + if (isWorldBoss()) + { + int level = (int)(unitTarget.getLevel() + WorldConfig.GetIntValue(WorldCfg.WorldBossLevelDiff)); + return (uint)MathFunctions.RoundToInterval(ref level, 1u, 255u); + } - uint level = target.ToUnit().getLevel() + WorldConfig.GetUIntValue(WorldCfg.WorldBossLevelDiff); - if (level < 1) - return 1; - if (level > 255) - return 255; - return level; + // 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()) + { + uint targetLevelWithDelta = (uint)(unitTarget.getLevel() + GetCreatureTemplate().levelScaling.Value.DeltaLevel); + + if (target.IsPlayer()) + targetLevelWithDelta += target.GetUInt32Value(PlayerFields.ScalingLevelDelta); + + return MathFunctions.RoundToInterval(ref targetLevelWithDelta, GetUInt32Value(UnitFields.ScalingLevelMin), GetUInt32Value(UnitFields.ScalingLevelMax)); + } + + } + + return base.GetLevelForTarget(target); } public string GetAIName() diff --git a/Game/Entities/Creature/CreatureData.cs b/Game/Entities/Creature/CreatureData.cs index 52f833777..32f7fa91a 100644 --- a/Game/Entities/Creature/CreatureData.cs +++ b/Game/Entities/Creature/CreatureData.cs @@ -19,6 +19,7 @@ using Framework.Collections; using Framework.Constants; using System; using System.Collections.Generic; +using Framework.Dynamic; namespace Game.Entities { @@ -37,6 +38,7 @@ namespace Game.Entities public string IconName; public uint GossipMenuId; public short Minlevel; + public Optional levelScaling; public short Maxlevel; public int HealthScalingExpansion; public uint RequiredExpansion; @@ -384,4 +386,11 @@ namespace Game.Entities m_items.Clear(); } } + + public struct CreatureLevelScaling + { + public ushort MinLevel; + public ushort MaxLevel; + public short DeltaLevel; + } } diff --git a/Game/Entities/Creature/Trainer.cs b/Game/Entities/Creature/Trainer.cs index aaa78270a..8098fdecd 100644 --- a/Game/Entities/Creature/Trainer.cs +++ b/Game/Entities/Creature/Trainer.cs @@ -18,7 +18,8 @@ namespace Game.Entities public Array ReqAbility = new Array(3); public byte ReqLevel; - public uint CastSpellId; + public uint LearnedSpellId; + public bool IsCastable() { return LearnedSpellId != SpellId; } } public class Trainer @@ -84,8 +85,8 @@ namespace Game.Entities player.SendPlaySpellVisualKit(362, 1, 0); // 113 EmoteSalute // learn explicitly or cast explicitly - if (trainerSpell.CastSpellId != 0) - player.CastSpell(player, trainerSpell.CastSpellId, true); + if (trainerSpell.IsCastable()) + player.CastSpell(player, trainerSpell.SpellId, true); else player.LearnSpell(trainerSpell.SpellId, false); } @@ -129,6 +130,12 @@ namespace Game.Entities if (player.getLevel() < trainerSpell.ReqLevel) return TrainerSpellState.Unavailable; + // check ranks + uint previousRankSpellId = Global.SpellMgr.GetPrevSpellInChain(trainerSpell.LearnedSpellId); + if (previousRankSpellId != 0) + if (!player.HasSpell(previousRankSpellId)) + return TrainerSpellState.Unavailable; + // check additional spell requirement foreach (var spellId in Global.SpellMgr.GetSpellsRequiredForSpellBounds(trainerSpell.SpellId)) if (!player.HasSpell(spellId)) diff --git a/Game/Entities/Object/WorldObject.cs b/Game/Entities/Object/WorldObject.cs index 5439a8f27..ff8f23661 100644 --- a/Game/Entities/Object/WorldObject.cs +++ b/Game/Entities/Object/WorldObject.cs @@ -850,7 +850,7 @@ namespace Game.Entities DynamicUpdateMask arrayMask = new DynamicUpdateMask((uint)values.Length); arrayMask.EncodeDynamicFieldChangeType(_dynamicChangesMask[index], updateType); if (updateType == UpdateType.Values && _dynamicChangesMask[index] == DynamicFieldChangeType.ValueAndSizeChanged) - arrayMask.SetCount(values.Length); + arrayMask.SetCount(values.Length); for (var v = 0; v < values.Length; ++v) { @@ -2029,7 +2029,7 @@ namespace Game.Entities Map map = GetMap(); GameObject go = new GameObject(); - if (!go.Create(entry, map, GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready)) + if (!go.Create(entry, map, GetPhaseMask(), pos, rotation, 255, GameObjectState.Ready)) return null; go.CopyPhaseFrom(this); @@ -2498,14 +2498,23 @@ namespace Game.Entities public void SetFieldNotifyFlag(uint flag) { _fieldNotifyFlags |= flag; } public void RemoveFieldNotifyFlag(uint flag) { _fieldNotifyFlags &= ~flag; } - public Creature ToCreature() { return IsTypeId(TypeId.Unit) ? (this as Creature) : null; } - public Player ToPlayer() { return IsTypeId(TypeId.Player) ? (this as Player) : null; } - public GameObject ToGameObject() { return IsTypeId(TypeId.GameObject) ? (this as GameObject) : null; } - public Unit ToUnit() { return IsTypeId(TypeId.Unit) || IsTypeId(TypeId.Player) ? (this as Unit) : null; } - public Corpse ToCorpse() { return IsTypeId(TypeId.Corpse) ? (this as Corpse) : null; } - public DynamicObject ToDynamicObject() { return IsTypeId(TypeId.DynamicObject) ? (this as DynamicObject) : null; } - public AreaTrigger ToAreaTrigger() { return IsTypeId(TypeId.AreaTrigger) ? (this as AreaTrigger) : null; } - public Conversation ToConversation() { return IsTypeId(TypeId.Conversation) ? (this as Conversation) : null; } + bool IsCreature() { return GetTypeId() == TypeId.Unit; } + public bool IsPlayer() { return GetTypeId() == TypeId.Player; } + bool IsGameObject() { return GetTypeId() == TypeId.GameObject; } + bool IsUnit() { return isTypeMask(TypeMask.Unit); } + bool IsCorpse() { return GetTypeId() == TypeId.Corpse; } + bool IsDynObject() { return GetTypeId() == TypeId.DynamicObject; } + bool IsAreaTrigger() { return GetTypeId() == TypeId.AreaTrigger; } + bool IsConversation() { return GetTypeId() == TypeId.Conversation; } + + public Creature ToCreature() { return IsCreature() ? (this as Creature) : null; } + public Player ToPlayer() { return IsPlayer() ? (this as Player) : null; } + public GameObject ToGameObject() { return IsGameObject() ? (this as GameObject) : null; } + public Unit ToUnit() { return IsUnit() ? (this as Unit) : null; } + public Corpse ToCorpse() { return IsCorpse() ? (this as Corpse) : null; } + public DynamicObject ToDynamicObject() { return IsDynObject() ? (this as DynamicObject) : null; } + public AreaTrigger ToAreaTrigger() { return IsAreaTrigger() ? (this as AreaTrigger) : null; } + public Conversation ToConversation() { return IsConversation() ? (this as Conversation) : null; } public virtual void Update(uint diff) { } diff --git a/Game/Entities/Player/KillRewarder.cs b/Game/Entities/Player/KillRewarder.cs index cfff3a236..2d0b665bd 100644 --- a/Game/Entities/Player/KillRewarder.cs +++ b/Game/Entities/Player/KillRewarder.cs @@ -117,7 +117,7 @@ namespace Game.Entities // 2.4. _maxNotGrayMember - maximum level of alive group member within reward distance, // for whom victim is not gray; uint grayLevel = Formulas.GetGrayLevel(lvl); - if (_victim.getLevel() > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl)) + if (_victim.GetLevelForTarget(member) > grayLevel && (!_maxNotGrayMember || _maxNotGrayMember.getLevel() < lvl)) _maxNotGrayMember = member; } } diff --git a/Game/Entities/Player/Player.DB.cs b/Game/Entities/Player/Player.DB.cs index 45b9094e1..5bc3f998e 100644 --- a/Game/Entities/Player/Player.DB.cs +++ b/Game/Entities/Player/Player.DB.cs @@ -2363,7 +2363,7 @@ namespace Game.Entities } SetUInt32Value(UnitFields.Level, result.Read(6)); - SetUInt32Value(PlayerFields.Xp, result.Read(7)); + SetXP(result.Read(7)); _LoadIntoDataField(result.Read(65), (int)PlayerFields.ExploredZones1, PlayerConst.ExploredZonesSize); _LoadIntoDataField(result.Read(66), (int)PlayerFields.KnownTitles, PlayerConst.KnowTitlesSize * 2); diff --git a/Game/Entities/Player/Player.PvP.cs b/Game/Entities/Player/Player.PvP.cs index 21bef7d74..7ad682c0b 100644 --- a/Game/Entities/Player/Player.PvP.cs +++ b/Game/Entities/Player/Player.PvP.cs @@ -104,7 +104,7 @@ namespace Game.Entities byte k_level = (byte)getLevel(); byte k_grey = (byte)Formulas.GetGrayLevel(k_level); - byte v_level = (byte)victim.getLevel(); + byte v_level = (byte)victim.GetLevelForTarget(this); if (v_level <= k_grey) return false; diff --git a/Game/Entities/Player/Player.cs b/Game/Entities/Player/Player.cs index fe40fe148..7974dca6b 100644 --- a/Game/Entities/Player/Player.cs +++ b/Game/Entities/Player/Player.cs @@ -1646,7 +1646,7 @@ namespace Game.Entities if (Rep.RepFaction1 != 0 && (!Rep.TeamDependent || team == Team.Alliance)) { - int donerep1 = CalculateReputationGain(ReputationSource.Kill, victim.getLevel(), Rep.RepValue1, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1)); + int donerep1 = CalculateReputationGain(ReputationSource.Kill, victim.GetLevelForTarget(this), Rep.RepValue1, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1)); donerep1 = (int)(donerep1 * rate); FactionRecord factionEntry1 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction1); @@ -1657,7 +1657,7 @@ namespace Game.Entities if (Rep.RepFaction2 != 0 && (!Rep.TeamDependent || team == Team.Horde)) { - int donerep2 = CalculateReputationGain(ReputationSource.Kill, victim.getLevel(), Rep.RepValue2, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2)); + int donerep2 = CalculateReputationGain(ReputationSource.Kill, victim.GetLevelForTarget(this), Rep.RepValue2, (int)(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2)); donerep2 = (int)(donerep2 * rate); FactionRecord factionEntry2 = CliDB.FactionStorage.LookupByKey(ChampioningFaction != 0 ? ChampioningFaction : Rep.RepFaction2); @@ -4116,9 +4116,12 @@ namespace Game.Entities if (!IsSectionFlagValid(hair, class_, create)) return false; - CharSectionsRecord facialHair = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.Hair, gender, facialHairId, hairColor); - if (facialHair == null) - return false; + if (facialHairId != 0) + { + CharSectionsRecord facialHair = Global.DB2Mgr.GetCharSectionEntry(race, CharSectionType.FacialHair, gender, facialHairId, hairColor); + if (facialHair == null) + return false; + } for (int i = 0; i < PlayerConst.CustomDisplaySize; ++i) { @@ -5064,7 +5067,7 @@ namespace Game.Entities // Used in triggers for check "Only to targets that grant experience or honor" req public bool isHonorOrXPTarget(Unit victim) { - uint v_level = victim.getLevel(); + uint v_level = victim.GetLevelForTarget(this); uint k_grey = Formulas.GetGrayLevel(getLevel()); // Victim level less gray level @@ -6412,6 +6415,19 @@ namespace Game.Entities SendMovementSetCollisionHeight(scale * GetCollisionHeight(IsMounted())); } + void SetXP(uint xp) + { + SetUInt32Value(PlayerFields.Xp, xp); + + int playerLevelDelta = 0; + + // If XP < 50%, player should see scaling creature with -1 level except for level max + if (getLevel() < SharedConst.MaxLevel && xp < (GetUInt32Value(PlayerFields.NextLevelXp) / 2)) + playerLevelDelta = -1; + + SetInt32Value(PlayerFields.ScalingLevelDelta, playerLevelDelta); + } + public void GiveXP(uint xp, Unit victim, float group_rate = 1.0f) { if (xp < 1) @@ -6467,7 +6483,7 @@ namespace Game.Entities nextLvlXP = GetUInt32Value(PlayerFields.NextLevelXp); } - SetUInt32Value(PlayerFields.Xp, newXP); + SetXP(newXP); } public void HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply) diff --git a/Game/Entities/StatSystem.cs b/Game/Entities/StatSystem.cs index e809fe04d..a92de6606 100644 --- a/Game/Entities/StatSystem.cs +++ b/Game/Entities/StatSystem.cs @@ -420,6 +420,10 @@ namespace Game.Entities public ulong CountPctFromMaxHealth(int pct) { return MathFunctions.CalculatePct(GetMaxHealth(), pct); } ulong CountPctFromCurHealth(int pct) { return MathFunctions.CalculatePct(GetHealth(), pct); } + public virtual float GetHealthMultiplierForTarget(WorldObject target) { return 1.0f; } + public virtual float GetDamageMultiplierForTarget(WorldObject target) { return 1.0f; } + public virtual float GetArmorMultiplierForTarget(WorldObject target) { return 1.0f; } + //Powers public PowerType getPowerType() { diff --git a/Game/Entities/Unit/Unit.Combat.cs b/Game/Entities/Unit/Unit.Combat.cs index be8775a93..84079ff86 100644 --- a/Game/Entities/Unit/Unit.Combat.cs +++ b/Game/Entities/Unit/Unit.Combat.cs @@ -724,7 +724,10 @@ namespace Game.Entities { absorb += damage; damage = 0; + return; } + + damage *= (uint)GetDamageMultiplierForTarget(victim); } void DealMeleeDamage(CalcDamageInfo damageInfo, bool durabilityLoss) { @@ -787,7 +790,7 @@ namespace Game.Entities // 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; uint VictimDefense = victim.GetMaxSkillValueForLevel(this); uint AttackerMeleeSkill = GetMaxSkillValueForLevel(); @@ -1018,6 +1021,8 @@ namespace Game.Entities victim.ToCreature().LowerPlayerDamageReq(health < damage ? health : damage); } + damage /= (uint)victim.GetHealthMultiplierForTarget(this); + if (health <= damage) { Log.outDebug(LogFilter.Unit, "DealDamage: victim just died"); @@ -1221,6 +1226,10 @@ namespace Game.Entities packet.BlockAmount = (int)damageInfo.blocked_amount; packet.LogData.Initialize(damageInfo.attacker); + SandboxScalingData sandboxScalingData = new SandboxScalingData(); + if (sandboxScalingData.GenerateDataForUnits(damageInfo.attacker, damageInfo.target)) + packet.SandboxScaling = sandboxScalingData; + SendCombatLogMessage(packet); } public void CombatStart(Unit target, bool initialAggro = true) @@ -2289,7 +2298,7 @@ namespace Game.Entities byte bossLevel = 83; uint bossResistanceConstant = 510; uint resistanceConstant = 0; - uint level = victim.getLevel(); + uint level = victim.GetLevelForTarget(this); if (level == bossLevel) resistanceConstant = bossResistanceConstant; @@ -2626,6 +2635,8 @@ namespace Game.Entities { float armor = victim.GetArmor(); + armor *= victim.GetArmorMultiplierForTarget(attacker); + // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER int armorBypassPct = 0; var reductionAuras = victim.GetAuraEffectsByType(AuraType.BypassArmorForCaster); @@ -2655,10 +2666,10 @@ namespace Game.Entities if (IsTypeId(TypeId.Player)) { float maxArmorPen = 0; - if (victim.getLevel() < 60) - maxArmorPen = 400 + 85 * victim.getLevel(); + if (victim.GetLevelForTarget(attacker) < 60) + maxArmorPen = 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 = Math.Min((armor + maxArmorPen) / 3, armor); @@ -2671,7 +2682,7 @@ namespace Game.Entities if (MathFunctions.fuzzyLe(armor, 0.0f)) return damage; - uint attackerLevel = attacker.getLevel(); + uint attackerLevel = attacker.GetLevelForTarget(victim); if (attackerLevel > CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount()) attackerLevel = (uint)CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount(); diff --git a/Game/Entities/Unit/Unit.Pets.cs b/Game/Entities/Unit/Unit.Pets.cs index fd2233fbb..e4b298826 100644 --- a/Game/Entities/Unit/Unit.Pets.cs +++ b/Game/Entities/Unit/Unit.Pets.cs @@ -745,7 +745,7 @@ namespace Game.Entities if (!pet.CreateBaseAtCreature(creatureTarget)) return null; - uint level = creatureTarget.getLevel() + 5 < getLevel() ? (getLevel() - 5) : creatureTarget.getLevel(); + uint level = creatureTarget.GetLevelForTarget(this) + 5 < getLevel() ? (getLevel() - 5) : creatureTarget.GetLevelForTarget(this); InitTamedPet(pet, level, spell_id); diff --git a/Game/Entities/Unit/Unit.Spells.cs b/Game/Entities/Unit/Unit.Spells.cs index 8944dd25d..9f1ee9f9e 100644 --- a/Game/Entities/Unit/Unit.Spells.cs +++ b/Game/Entities/Unit/Unit.Spells.cs @@ -3079,6 +3079,11 @@ namespace Game.Entities packet.Absorbed = (int)log.absorb; packet.Periodic = log.periodicLog; packet.Flags = (int)log.HitInfo; + + SandboxScalingData sandboxScalingData = new SandboxScalingData(); + if (sandboxScalingData.GenerateDataForUnits(log.attacker, log.target)) + packet.SandboxScaling.Set(sandboxScalingData); + SendCombatLogMessage(packet); } @@ -3102,6 +3107,12 @@ namespace Game.Entities spellLogEffect.Crit = info.critical; /// @todo: implement debug info + SandboxScalingData sandboxScalingData = new SandboxScalingData(); + Unit caster = Global.ObjAccessor.GetUnit(this, aura.GetCasterGUID()); + if (caster) + if (sandboxScalingData.GenerateDataForUnits(caster, this)) + spellLogEffect.SandboxScaling.Set(sandboxScalingData); + data.Effects.Add(spellLogEffect); SendCombatLogMessage(data); diff --git a/Game/Entities/Unit/Unit.cs b/Game/Entities/Unit/Unit.cs index 713196532..17ff7fb61 100644 --- a/Game/Entities/Unit/Unit.cs +++ b/Game/Entities/Unit/Unit.cs @@ -2127,6 +2127,7 @@ namespace Game.Entities { return GetUInt32Value(UnitFields.Level); } + public override uint GetLevelForTarget(WorldObject target) { return getLevel(); diff --git a/Game/Globals/ObjectManager.cs b/Game/Globals/ObjectManager.cs index 8318c6cdc..167ea456a 100644 --- a/Game/Globals/ObjectManager.cs +++ b/Game/Globals/ObjectManager.cs @@ -2280,6 +2280,41 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature model based info in {1} ms", count, Time.GetMSTimeDiffToNow(time)); } + public void LoadCreatureScalingData() + { + uint oldMSTime = Time.GetMSTime(); + + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT Entry, LevelScalingMin, LevelScalingMax, LevelScalingDelta FROM creature_template_scaling"); + if (result.IsEmpty()) + { + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature template scaling definitions. DB table `creature_template_scaling` is empty."); + return; + } + + uint count = 0; + do + { + uint entry = result.Read(0); + + var template = creatureTemplateStorage.LookupByKey(entry); + if (template == null) + { + Log.outError(LogFilter.Sql, $"Creature template (Entry: {entry}) does not exist but has a record in `creature_template_scaling`"); + continue; + } + + CreatureLevelScaling creatureLevelScaling; + creatureLevelScaling.MinLevel = result.Read(1); + creatureLevelScaling.MaxLevel = result.Read(2); + creatureLevelScaling.DeltaLevel = result.Read(3); + template.levelScaling.Set(creatureLevelScaling); + + ++count; + } while (result.NextRow()); + + Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template scaling data in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + } public void CheckCreatureTemplate(CreatureTemplate cInfo) { if (cInfo == null) @@ -2952,12 +2987,13 @@ namespace Game if (!allReqValid) continue; + spell.LearnedSpellId = spell.SpellId; foreach (SpellEffectInfo spellEffect in spellInfo.GetEffectsForDifficulty(Difficulty.None)) { - if (spellEffect.IsEffect(SpellEffectName.LearnSpell)) + if (spellEffect != null && spellEffect.IsEffect(SpellEffectName.LearnSpell)) { - spell.CastSpellId = spell.SpellId; - spell.SpellId = spellEffect.TriggerSpell; + Contract.Assert(spell.LearnedSpellId == spell.SpellId, $"Only one learned spell is currently supported - spell {spell.SpellId} already teaches {spell.LearnedSpellId} but it tried to overwrite it with {spellEffect.TriggerSpell}"); + spell.LearnedSpellId = spellEffect.TriggerSpell; break; } } diff --git a/Game/Handlers/SpellHandler.cs b/Game/Handlers/SpellHandler.cs index e1d721685..d9396f70c 100644 --- a/Game/Handlers/SpellHandler.cs +++ b/Game/Handlers/SpellHandler.cs @@ -292,7 +292,7 @@ namespace Game // auto-selection buff level base at target level (in spellInfo) if (targets.GetUnitTarget() != null) { - SpellInfo actualSpellInfo = spellInfo.GetAuraRankForLevel(targets.GetUnitTarget().getLevel()); + SpellInfo actualSpellInfo = spellInfo.GetAuraRankForLevel(targets.GetUnitTarget().GetLevelForTarget(caster)); // if rank not found then function return NULL but in explicit cast case original spell can be casted and later failed with appropriate error message if (actualSpellInfo != null) diff --git a/Game/Miscellaneous/Formulas.cs b/Game/Miscellaneous/Formulas.cs index 74dd17f9b..18b418742 100644 --- a/Game/Miscellaneous/Formulas.cs +++ b/Game/Miscellaneous/Formulas.cs @@ -149,7 +149,7 @@ namespace Game { float xpMod = 1.0f; - gain = BaseGain(player.getLevel(), u.getLevel()); + gain = BaseGain(player.getLevel(), u.GetLevelForTarget(player)); if (gain != 0 && creature) { diff --git a/Game/Network/Packets/CombatLogPackets.cs b/Game/Network/Packets/CombatLogPackets.cs index 51b1a39eb..f1b0a4c9f 100644 --- a/Game/Network/Packets/CombatLogPackets.cs +++ b/Game/Network/Packets/CombatLogPackets.cs @@ -99,7 +99,7 @@ namespace Game.Network.Packets public int Absorbed; public int Flags; // Optional DebugInfo; - Optional SandboxScaling = new Optional(); + public Optional SandboxScaling = new Optional(); } class EnvironmentalDamageLog : CombatLogServerPacket @@ -310,7 +310,7 @@ namespace Game.Network.Packets public uint Resisted; public bool Crit; public Optional DebugInfo; - Optional SandboxScaling = new Optional(); + public Optional SandboxScaling = new Optional(); } } @@ -593,7 +593,7 @@ namespace Game.Network.Packets public int RageGained; public UnkAttackerState UnkState; public float Unk; - SandboxScalingData SandboxScaling = new SandboxScalingData(); + public SandboxScalingData SandboxScaling = new SandboxScalingData(); } //Structs diff --git a/Game/Network/Packets/SpellPackets.cs b/Game/Network/Packets/SpellPackets.cs index 4dfe2b04f..1b63db147 100644 --- a/Game/Network/Packets/SpellPackets.cs +++ b/Game/Network/Packets/SpellPackets.cs @@ -1146,6 +1146,86 @@ namespace Game.Network.Packets class SandboxScalingData { + public bool GenerateDataForUnits(Creature attacker, Player target) + { + CreatureTemplate creatureTemplate = attacker.GetCreatureTemplate(); + + Type = SandboxScalingDataType.CreatureToPlayerDamage; + PlayerLevelDelta = (short)target.GetInt32Value(PlayerFields.ScalingLevelDelta); + PlayerItemLevel = (ushort)target.GetAverageItemLevel(); + TargetLevel = (byte)target.getLevel(); + Expansion = (byte)creatureTemplate.RequiredExpansion; + Class = (byte)creatureTemplate.UnitClass; + TargetMinScalingLevel = (byte)creatureTemplate.levelScaling.Value.MinLevel; + TargetMaxScalingLevel = (byte)creatureTemplate.levelScaling.Value.MaxLevel; + TargetScalingLevelDelta = (sbyte)creatureTemplate.levelScaling.Value.DeltaLevel; + return true; + } + + public bool GenerateDataForUnits(Player attacker, Creature target) + { + CreatureTemplate creatureTemplate = target.GetCreatureTemplate(); + + Type = SandboxScalingDataType.PlayerToCreatureDamage; + PlayerLevelDelta = (short)attacker.GetInt32Value(PlayerFields.ScalingLevelDelta); + PlayerItemLevel = (ushort)attacker.GetAverageItemLevel(); + TargetLevel = (byte)target.getLevel(); + Expansion = (byte)creatureTemplate.RequiredExpansion; + Class = (byte)creatureTemplate.UnitClass; + TargetMinScalingLevel = (byte)creatureTemplate.levelScaling.Value.MinLevel; + TargetMaxScalingLevel = (byte)creatureTemplate.levelScaling.Value.MaxLevel; + TargetScalingLevelDelta = (sbyte)creatureTemplate.levelScaling.Value.DeltaLevel; + return true; + } + + public bool GenerateDataForUnits(Creature attacker, Creature target) + { + CreatureTemplate creatureTemplate = target.HasScalableLevels() ? target.GetCreatureTemplate() : attacker.GetCreatureTemplate(); + + Type = SandboxScalingDataType.CreatureToCreatureDamage; + PlayerLevelDelta = 0; + PlayerItemLevel = 0; + TargetLevel = (byte)target.getLevel(); + Expansion = (byte)creatureTemplate.RequiredExpansion; + Class = (byte)creatureTemplate.UnitClass; + TargetMinScalingLevel = (byte)creatureTemplate.levelScaling.Value.MinLevel; + TargetMaxScalingLevel = (byte)creatureTemplate.levelScaling.Value.MaxLevel; + TargetScalingLevelDelta = (sbyte)creatureTemplate.levelScaling.Value.DeltaLevel; + return true; + } + + public bool GenerateDataForUnits(Unit attacker, Unit target) + { + Player playerAttacker = attacker.ToPlayer(); + Creature creatureAttacker = attacker.ToCreature(); + if (playerAttacker) + { + Player playerTarget = target.ToPlayer(); + Creature creatureTarget = target.ToCreature(); + if (playerTarget) + return GenerateDataForUnits(playerAttacker, playerTarget); + else if (creatureTarget) + { + if (creatureTarget.HasScalableLevels()) + return GenerateDataForUnits(playerAttacker, creatureTarget); + } + } + else if (creatureAttacker) + { + Player playerTarget = target.ToPlayer(); + Creature creatureTarget = target.ToCreature(); + if (playerTarget) + return GenerateDataForUnits(creatureAttacker, playerTarget); + else if (creatureTarget) + { + if (creatureAttacker.HasScalableLevels() || creatureTarget.HasScalableLevels()) + return GenerateDataForUnits(creatureAttacker, creatureTarget); + } + } + + return false; + } + public void Write(WorldPacket data) { data.WriteBits(Type, 3); @@ -1159,15 +1239,23 @@ namespace Game.Network.Packets data.WriteInt8(TargetScalingLevelDelta); } - public uint Type; + public SandboxScalingDataType Type; public short PlayerLevelDelta; public ushort PlayerItemLevel; public byte TargetLevel; public byte Expansion; - public byte Class = 1; - public byte TargetMinScalingLevel = 1; - public byte TargetMaxScalingLevel = 1; - public sbyte TargetScalingLevelDelta = 1; + public byte Class; + public byte TargetMinScalingLevel; + public byte TargetMaxScalingLevel; + public sbyte TargetScalingLevelDelta; + + public enum SandboxScalingDataType + { + PlayerToPlayer = 1, // NYI + CreatureToPlayerDamage = 2, + PlayerToCreatureDamage = 3, + CreatureToCreatureDamage = 4 + } } public class AuraDataInfo diff --git a/Game/Server/WorldManager.cs b/Game/Server/WorldManager.cs index e53c91cbf..b1f702cb7 100644 --- a/Game/Server/WorldManager.cs +++ b/Game/Server/WorldManager.cs @@ -478,6 +478,9 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Creature template addons..."); Global.ObjectMgr.LoadCreatureTemplateAddons(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature template scaling..."); + Global.ObjectMgr.LoadCreatureScalingData(); + Log.outInfo(LogFilter.ServerLoading, "Loading Reputation Reward Rates..."); Global.ObjectMgr.LoadReputationRewardRate(); diff --git a/Game/Spells/Auras/Aura.cs b/Game/Spells/Auras/Aura.cs index c43be84e2..786f2c6a9 100644 --- a/Game/Spells/Auras/Aura.cs +++ b/Game/Spells/Auras/Aura.cs @@ -150,6 +150,7 @@ namespace Game.Spells } if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) || + GetBase().HasEffectType(AuraType.ModSpellCategoryCooldown) || GetBase().HasEffectType(AuraType.ModMaxCharges) || GetBase().HasEffectType(AuraType.ChargeRecoveryMod) || GetBase().HasEffectType(AuraType.ChargeRecoveryMultiplier)) diff --git a/Game/Spells/Spell.cs b/Game/Spells/Spell.cs index 7ef22ffea..89aae4d39 100644 --- a/Game/Spells/Spell.cs +++ b/Game/Spells/Spell.cs @@ -1625,7 +1625,7 @@ namespace Game.Spells if (m_auraScaleMask != 0 && ihit.effectMask == m_auraScaleMask && m_caster != target) { SpellInfo auraSpell = Global.SpellMgr.GetSpellInfo(Global.SpellMgr.GetFirstSpellInChain(m_spellInfo.Id)); - if (target.getLevel() + 10 >= auraSpell.SpellLevel) + if (target.GetLevelForTarget(m_caster) + 10 >= auraSpell.SpellLevel) ihit.scaleAura = true; } return; @@ -1646,7 +1646,7 @@ namespace Game.Spells if (m_auraScaleMask != 0 && targetInfo.effectMask == m_auraScaleMask && m_caster != target) { SpellInfo auraSpell = Global.SpellMgr.GetSpellInfo(Global.SpellMgr.GetFirstSpellInChain(m_spellInfo.Id)); - if (target.getLevel() + 10 >= auraSpell.SpellLevel) + if (target.GetLevelForTarget(m_caster) + 10 >= auraSpell.SpellLevel) targetInfo.scaleAura = true; } @@ -4874,7 +4874,7 @@ namespace Game.Spells SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); ushort skillValue = m_caster.ToPlayer().GetSkillValue(skill); - uint TargetLevel = m_targets.GetUnitTarget().getLevel(); + uint TargetLevel = m_targets.GetUnitTarget().GetLevelForTarget(m_caster); int ReqValue = (int)(skillValue < 100 ? (TargetLevel - 10) * 10 : TargetLevel * 5); if (ReqValue > skillValue) return SpellCastResult.LowCastlevel; @@ -5199,7 +5199,7 @@ namespace Game.Spells return SpellCastResult.TargetIsPlayerControlled; int damage = CalculateDamage(effect.EffectIndex, target1); - if (damage != 0 && target1.getLevel() > damage) + if (damage != 0 && target1.GetLevelForTarget(m_caster) > damage) return SpellCastResult.Highlevel; } @@ -6118,19 +6118,19 @@ namespace Game.Spells return SpellCastResult.EquippedItem; break; } - case SpellEffectName.CreateManaGem: + case SpellEffectName.RechargeItem: { - uint item_id = effect.ItemType; - ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item_id); + uint itemId = effect.ItemType; + ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemId); if (proto == null) return SpellCastResult.ItemAtMaxCharges; - Item pitem = player.GetItemByEntry(item_id); - if (pitem != null) + Item item = player.GetItemByEntry(itemId); + if (item != null) { for (int x = 0; x < proto.Effects.Count && x < 5; ++x) - if (proto.Effects[x].Charges != 0 && pitem.GetSpellCharges(x) == proto.Effects[x].Charges) + if (proto.Effects[x].Charges != 0 && item.GetSpellCharges(x) == proto.Effects[x].Charges) return SpellCastResult.ItemAtMaxCharges; } break; @@ -6343,7 +6343,7 @@ namespace Game.Spells return false; int damage = CalculateDamage(effect.EffectIndex, target); if (damage != 0) - if (target.getLevel() > damage) + if (target.GetLevelForTarget(m_caster) > damage) return false; break; default: diff --git a/Game/Spells/SpellEffects.cs b/Game/Spells/SpellEffects.cs index 8c93b61c5..af763db1d 100644 --- a/Game/Spells/SpellEffects.cs +++ b/Game/Spells/SpellEffects.cs @@ -2464,7 +2464,7 @@ namespace Game.Spells // "kill" original creature creatureTarget.DespawnOrUnsummon(); - uint level = (creatureTarget.getLevel() < (m_caster.getLevel() - 5)) ? (m_caster.getLevel() - 5) : creatureTarget.getLevel(); + uint 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(UnitFields.Level, level - 1); @@ -4169,7 +4169,7 @@ namespace Game.Spells return; Creature creature = unitTarget.ToCreature(); - int targetLevel = (int)creature.getLevel(); + int targetLevel = (int)creature.GetLevelForTarget(m_caster); SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); @@ -5376,35 +5376,27 @@ namespace Game.Spells } } - [SpellEffectHandler(SpellEffectName.CreateManaGem)] - void EffectRechargeManaGem(uint effIndex) + [SpellEffectHandler(SpellEffectName.RechargeItem)] + void EffectRechargeItem(uint effIndex) { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; - if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) + if (unitTarget == null) return; - Player player = m_caster.ToPlayer(); - + Player player = unitTarget.ToPlayer(); if (player == null) return; - uint item_id = GetEffect(0).ItemType; - - ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item_id); - if (proto == null) - { - player.SendEquipError(InventoryResult.ItemNotFound); - return; - } - - Item pItem = player.GetItemByEntry(item_id); - if (pItem != null) + Item item = player.GetItemByEntry(effectInfo.ItemType); + if (item != null) { + ItemTemplate proto = item.GetTemplate(); for (int x = 0; x < proto.Effects.Count && x < 5; ++x) - pItem.SetSpellCharges(x, proto.Effects[x].Charges); - pItem.SetState(ItemUpdateState.Changed, player); + item.SetSpellCharges(x, proto.Effects[x].Charges); + + item.SetState(ItemUpdateState.Changed, player); } } diff --git a/Game/Spells/SpellInfo.cs b/Game/Spells/SpellInfo.cs index c63397073..d9b0d0c30 100644 --- a/Game/Spells/SpellInfo.cs +++ b/Game/Spells/SpellInfo.cs @@ -3124,7 +3124,7 @@ namespace Game.Spells new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 63 SPELL_EFFECT_THREAT new StaticData(SpellEffectImplicitTargetTypes.None, SpellTargetObjectTypes.None), // 64 SPELL_EFFECT_TRIGGER_SPELL new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 65 SPELL_EFFECT_APPLY_AREA_AURA_RAID - new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 66 SPELL_EFFECT_CREATE_MANA_GEM + new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 66 SPELL_EFFECT_RECHARGE_ITEM new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 67 SPELL_EFFECT_HEAL_MAX_HEALTH new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.Unit), // 68 SPELL_EFFECT_INTERRUPT_CAST new StaticData(SpellEffectImplicitTargetTypes.Explicit, SpellTargetObjectTypes.UnitAndDest), // 69 SPELL_EFFECT_DISTRACT