diff --git a/Source/Framework/Database/Databases/WorldDatabase.cs b/Source/Framework/Database/Databases/WorldDatabase.cs index f265fc60f..fe8f12fc3 100644 --- a/Source/Framework/Database/Databases/WorldDatabase.cs +++ b/Source/Framework/Database/Databases/WorldDatabase.cs @@ -75,7 +75,7 @@ namespace Framework.Database PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?"); PrepareStatement(WorldStatements.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?"); PrepareStatement(WorldStatements.SEL_COMMANDS, "SELECT name, permission, help FROM command"); - PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, rank, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template WHERE entry = ?"); + PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, gossip_menu_id, minlevel, maxlevel, HealthScalingExpansion, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, `rank`, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, dynamicflags, family, trainer_class, type, type_flags, type_flags2, lootid, pickpocketloot, skinloot, resistance1, resistance2, resistance3, resistance4, resistance5, resistance6, spell1, spell2, spell3, spell4, spell5, spell6, spell7, spell8, VehicleId, mingold, maxgold, AIName, MovementType, InhabitType, HoverHeight, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, FadeRegionRadius, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, mechanic_immune_mask, flags_extra, ScriptName FROM creature_template WHERE entry = ?"); PrepareStatement(WorldStatements.SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?"); PrepareStatement(WorldStatements.SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?"); PrepareStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index ce3251db8..b376ee834 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -1145,10 +1145,11 @@ 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); + var levels = cInfo.GetMinMaxLevel(); + int minlevel = Math.Min(levels[0], levels[1]); + int maxlevel = Math.Max(levels[0], levels[1]); + int level = (minlevel == maxlevel ? minlevel : RandomHelper.IRand(minlevel, maxlevel)); + SetLevel((uint)level); CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); @@ -2362,7 +2363,7 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); - float baseHealth = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, cInfo.HealthScalingExpansion, scaling.ContentTuningID, (Class)cInfo.UnitClass); + float baseHealth = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, cInfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cInfo.UnitClass); return (ulong)(baseHealth * cInfo.ModHealth * cInfo.ModHealthExtra); } @@ -2382,7 +2383,7 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); - return Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureAutoAttackDps, level, cInfo.HealthScalingExpansion, scaling.ContentTuningID, (Class)cInfo.UnitClass); + return Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureAutoAttackDps, level, cInfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cInfo.UnitClass); } public override float GetDamageMultiplierForTarget(WorldObject target) @@ -2399,7 +2400,7 @@ namespace Game.Entities { CreatureTemplate cInfo = GetCreatureTemplate(); CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); - float baseArmor = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureArmor, level, cInfo.HealthScalingExpansion, scaling.ContentTuningID, (Class)cInfo.UnitClass); + float baseArmor = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureArmor, level, cInfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cInfo.UnitClass); return baseArmor * cInfo.ModArmor; } @@ -2428,12 +2429,29 @@ namespace Game.Entities // between UNIT_FIELD_SCALING_LEVEL_MIN and UNIT_FIELD_SCALING_LEVEL_MAX if (HasScalableLevels()) { - int targetLevelWithDelta = (int)unitTarget.GetLevel() + m_unitData.ScalingLevelDelta; + int scalingLevelMin = m_unitData.ScalingLevelMin; + int scalingLevelMax = m_unitData.ScalingLevelMax; + int scalingLevelDelta = m_unitData.ScalingLevelDelta; + int scalingFactionGroup = m_unitData.ScalingFactionGroup; + int targetLevel = unitTarget.m_unitData.EffectiveLevel; + if (targetLevel == 0) + targetLevel = (int)unitTarget.GetLevel(); - if (target.IsPlayer()) - targetLevelWithDelta += target.ToPlayer().m_activePlayerData.ScalingPlayerLevelDelta; + int targetLevelDelta = 0; - return (uint)MathFunctions.RoundToInterval(ref targetLevelWithDelta, m_unitData.ScalingLevelMin, m_unitData.ScalingLevelMax); + Player playerTarget = target.ToPlayer(); + if (playerTarget != null) + { + if (scalingFactionGroup != 0 && CliDB.FactionTemplateStorage.LookupByKey(CliDB.ChrRacesStorage.LookupByKey(playerTarget.GetRace()).FactionID).FactionGroup != scalingFactionGroup) + scalingLevelMin = scalingLevelMax; + + int maxCreatureScalingLevel = playerTarget.m_activePlayerData.MaxCreatureScalingLevel; + targetLevelDelta = Math.Min(maxCreatureScalingLevel > 0 ? maxCreatureScalingLevel - targetLevel : 0, playerTarget.m_activePlayerData.ScalingPlayerLevelDelta); + } + + int levelWithDelta = targetLevel + targetLevelDelta; + int level = MathFunctions.RoundToInterval(ref levelWithDelta, scalingLevelMin, scalingLevelMax) + scalingLevelDelta; + return (uint)MathFunctions.RoundToInterval(ref level, 1, SharedConst.MaxLevel + 3); } } diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index 3a3f5efaf..69eeb7f87 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -157,6 +157,20 @@ namespace Game.Entities return CreatureModel.DefaultVisibleModel; } + public int[] GetMinMaxLevel() + { + return new[] + { + HealthScalingExpansion != (int)Expansion.LevelCurrent ? Minlevel : Minlevel + SharedConst.MaxLevel, + HealthScalingExpansion != (int)Expansion.LevelCurrent ? Maxlevel : Maxlevel + SharedConst.MaxLevel + }; + } + + public int GetHealthScalingExpansion() + { + return HealthScalingExpansion == (int)Expansion.LevelCurrent ? (int)Expansion.WarlordsOfDraenor : HealthScalingExpansion; + } + public SkillType GetRequiredLootSkill() { if (TypeFlags.HasAnyFlag(CreatureTypeFlags.HerbSkinningSkill)) diff --git a/Source/Game/Entities/TemporarySummon.cs b/Source/Game/Entities/TemporarySummon.cs index 783b08378..5ed3ceee9 100644 --- a/Source/Game/Entities/TemporarySummon.cs +++ b/Source/Game/Entities/TemporarySummon.cs @@ -489,7 +489,7 @@ namespace Game.Entities CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(petlevel, cinfo.UnitClass); CreatureLevelScaling scaling = cinfo.GetLevelScaling(GetMap().GetDifficultyID()); - SetCreateHealth((uint)(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, petlevel, cinfo.HealthScalingExpansion, scaling.ContentTuningID, (Class)cinfo.UnitClass) * cinfo.ModHealth * cinfo.ModHealthExtra)); + SetCreateHealth((uint)(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, petlevel, cinfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cinfo.UnitClass) * cinfo.ModHealth * cinfo.ModHealthExtra)); SetCreateMana(stats.BaseMana); diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 544944d1d..8cb68acf1 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -2527,14 +2527,14 @@ namespace Game if (cInfo.DmgSchool != difficultyInfo.DmgSchool) { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `dmgschool`: {1}) has different `dmgschool` in difficulty {2} mode (Entry: {3}, `dmgschool`: {4}).", + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `dmgschool`: {1}) has different `dmgschool` in difficulty {2} mode (Entry: {3}, `dmgschool`: {4}).", cInfo.Entry, cInfo.DmgSchool, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.DmgSchool); Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `dmgschool`={0} WHERE `entry`={1};", cInfo.DmgSchool, cInfo.DifficultyEntry[diff]); } if (cInfo.UnitFlags2 != difficultyInfo.UnitFlags2) { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `unit_flags2`: {1}) has different `unit_flags2` in difficulty {2} mode (Entry: {3}, `unit_flags2`: {4}).", + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, `unit_flags2`: {1}) has different `unit_flags2` in difficulty {2} mode (Entry: {3}, `unit_flags2`: {4}).", cInfo.Entry, cInfo.UnitFlags2, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.UnitFlags2); Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `unit_flags2`=`unit_flags2`^{0} WHERE `entry`={1};", cInfo.UnitFlags2 ^ difficultyInfo.UnitFlags2, cInfo.DifficultyEntry[diff]); } @@ -2565,7 +2565,7 @@ namespace Game if (cInfo.RegenHealth != difficultyInfo.RegenHealth) { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, RegenHealth: {1}) has different `RegenHealth` in difficulty {2} mode (Entry: {3}, RegenHealth: {4}).", + Log.outError(LogFilter.Sql, "Creature (Entry: {0}, RegenHealth: {1}) has different `RegenHealth` in difficulty {2} mode (Entry: {3}, RegenHealth: {4}).", cInfo.Entry, cInfo.RegenHealth, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.RegenHealth); Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `RegenHealth`={0} WHERE `entry`={1};", cInfo.RegenHealth, cInfo.DifficultyEntry[diff]); } @@ -2705,13 +2705,13 @@ namespace Game cInfo.MovementType = (uint)MovementGeneratorType.Idle; } - if (cInfo.HealthScalingExpansion < (int)Expansion.LevelCurrent || cInfo.HealthScalingExpansion > ((int)Expansion.Max - 1)) + if (cInfo.HealthScalingExpansion < (int)Expansion.LevelCurrent || cInfo.HealthScalingExpansion >= (int)Expansion.Max) { Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Id: {0}) with invalid `HealthScalingExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.HealthScalingExpansion); cInfo.HealthScalingExpansion = 0; } - if (cInfo.RequiredExpansion > (int)(Expansion.Max - 1)) + if (cInfo.RequiredExpansion > (int)Expansion.Max) { Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Entry: {0}) with `RequiredExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.RequiredExpansion); cInfo.RequiredExpansion = 0; @@ -2724,26 +2724,17 @@ namespace Game cInfo.FlagsExtra &= CreatureFlagsExtra.DBAllowed; } - // -1, as expansion, is used in CreatureDifficulty.db2 for - // auto-updating the levels of creatures having their expansion - // set to that value to the current expansion's max leveling level - if (cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent) + var levels = cInfo.GetMinMaxLevel(); + if (levels[0] < 1 || levels[0] > SharedConst.StrongMaxLevel) { - cInfo.Minlevel = (short)(WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + cInfo.Minlevel); - cInfo.Maxlevel = (short)(WorldConfig.GetIntValue(WorldCfg.MaxPlayerLevel) + cInfo.Maxlevel); - cInfo.HealthScalingExpansion = (int)Expansion.WarlordsOfDraenor; + Log.outError(LogFilter.Sql, $"Creature (ID: {cInfo.Entry}): Calculated minLevel {cInfo.Minlevel} is not within [1, 255], value has been set to {(cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent ? SharedConst.MaxLevel : 1)}."); + cInfo.Minlevel = (short)(cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent ? 0 : 1); } - if (cInfo.Minlevel < 1 || cInfo.Minlevel > SharedConst.StrongMaxLevel) + if (levels[1] < 1 || levels[1] > SharedConst.StrongMaxLevel) { - Log.outError(LogFilter.Sql, "Creature (ID: {0}): MinLevel {1} is not within [1, 255], value has been set to 1.", cInfo.Entry, cInfo.Minlevel); - cInfo.Minlevel = 1; - } - - if (cInfo.Maxlevel < 1 || cInfo.Maxlevel > SharedConst.StrongMaxLevel) - { - Log.outError(LogFilter.Sql, "Creature (ID: {0}): MaxLevel {1} is not within [1, 255], value has been set to 1.", cInfo.Entry, cInfo.Maxlevel); - cInfo.Maxlevel = 1; + Log.outError(LogFilter.Sql, $"Creature (ID: {cInfo.Entry}): Calculated maxLevel {cInfo.Maxlevel} is not within [1, 255], value has been set to {(cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent ? SharedConst.MaxLevel : 1)}."); + cInfo.Maxlevel = (short)(cInfo.HealthScalingExpansion == (int)Expansion.LevelCurrent ? 0 : 1); } cInfo.ModDamage *= Creature._GetDamageMod(cInfo.Rank); diff --git a/Source/Game/Miscellaneous/Formulas.cs b/Source/Game/Miscellaneous/Formulas.cs index 86e4df1a0..bc59d48f0 100644 --- a/Source/Game/Miscellaneous/Formulas.cs +++ b/Source/Game/Miscellaneous/Formulas.cs @@ -154,7 +154,7 @@ namespace Game if (gain != 0 && creature) { // Players get only 10% xp for killing creatures of lower expansion levels than himself - if ((creature.GetCreatureTemplate().HealthScalingExpansion < (int)GetExpansionForLevel(player.GetLevel()))) + if ((creature.GetCreatureTemplate().GetHealthScalingExpansion() < (int)GetExpansionForLevel(player.GetLevel()))) gain = (uint)Math.Round(gain / 10.0f); if (creature.IsElite()) diff --git a/sql/updates/world/master/2020_05_02_03_world.sql b/sql/updates/world/master/2020_05_02_03_world.sql new file mode 100644 index 000000000..db7552e56 --- /dev/null +++ b/sql/updates/world/master/2020_05_02_03_world.sql @@ -0,0 +1,10 @@ +UPDATE creature_template SET `minlevel`=0 WHERE `minlevel`=1 AND `HealthScalingExpansion`=-1 AND `BaseAttackTime`=0; -- no attack time means we have no data and minlevel value was also defaulted +UPDATE creature_template SET `maxlevel`=0 WHERE `maxlevel`=1 AND `HealthScalingExpansion`=-1 AND `BaseAttackTime`=0; -- no attack time means we have no data and maxlevel value was also defaulted +UPDATE creature_template SET `minlevel`=`minlevel`-90 WHERE `minlevel`>=90 AND `minlevel`<100 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `maxlevel`=`maxlevel`-90 WHERE `maxlevel`>=90 AND `maxlevel`<100 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `minlevel`=`minlevel`-100 WHERE `minlevel`>=100 AND `minlevel`<110 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `maxlevel`=`maxlevel`-100 WHERE `maxlevel`>=100 AND `maxlevel`<110 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `minlevel`=`minlevel`-110 WHERE `minlevel`>=110 AND `minlevel`<120 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `maxlevel`=`maxlevel`-110 WHERE `maxlevel`>=110 AND `maxlevel`<120 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `minlevel`=`minlevel`-120 WHERE `minlevel`>=120 AND `minlevel`<130 AND `HealthScalingExpansion`=-1; +UPDATE creature_template SET `maxlevel`=`maxlevel`-120 WHERE `maxlevel`>=120 AND `maxlevel`<130 AND `HealthScalingExpansion`=-1;