From 068ccd990f22f417c7ba55b1fd21b4ea186d73d6 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Tue, 30 May 2023 08:16:40 -0400 Subject: [PATCH] Core/Creatures: Move creature difficulty specific data from creature_template table to creature_template_difficulty Port From (https://github.com/TrinityCore/TrinityCore/commit/06d0b16f158e8793860d9edd11b990f20b1d0dac) --- .../Database/Databases/WorldDatabase.cs | 2 +- Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs | 2 +- .../Game/AI/ScriptedAI/ScriptedFollowerAI.cs | 2 +- Source/Game/AI/SmartScripts/SmartAI.cs | 2 +- Source/Game/Chat/Commands/NPCCommands.cs | 6 +- .../Game/Entities/Creature/Creature.Fields.cs | 1 + Source/Game/Entities/Creature/Creature.cs | 98 ++-- Source/Game/Entities/Creature/CreatureData.cs | 184 +++---- Source/Game/Entities/Object/WorldObject.cs | 2 +- Source/Game/Entities/Pet.cs | 2 +- Source/Game/Entities/Player/Player.cs | 4 +- Source/Game/Entities/StatSystem.cs | 2 +- Source/Game/Entities/TemporarySummon.cs | 3 +- Source/Game/Entities/Unit/Unit.Combat.cs | 8 +- Source/Game/Globals/ObjectManager.cs | 484 +++++++----------- Source/Game/Handlers/NPCHandler.cs | 4 +- Source/Game/Handlers/QueryHandler.cs | 29 +- Source/Game/Loot/LootManager.cs | 54 +- Source/Game/Miscellaneous/Formulas.cs | 2 +- .../Game/Networking/Packets/SpellPackets.cs | 18 +- Source/Game/Spells/Spell.cs | 7 +- Source/Game/Spells/SpellEffects.cs | 6 +- Source/Game/Spells/SpellInfo.cs | 2 +- Source/Game/World/WorldManager.cs | 6 +- Source/Scripts/Spells/Hunter.cs | 2 +- Source/Scripts/Spells/Items.cs | 6 +- 26 files changed, 399 insertions(+), 539 deletions(-) diff --git a/Source/Framework/Database/Databases/WorldDatabase.cs b/Source/Framework/Database/Databases/WorldDatabase.cs index 28232a6a5..12e6751ff 100644 --- a/Source/Framework/Database/Databases/WorldDatabase.cs +++ b/Source/Framework/Database/Databases/WorldDatabase.cs @@ -60,7 +60,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, 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, 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, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HealthModifier, HealthModifierExtra, ManaModifier, ManaModifierExtra, ArmorModifier, DamageModifier, ExperienceModifier, RacialLeader, movementId, CreatureDifficultyID, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ? OR 1 = ?"); + PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, 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, VehicleId, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, ExperienceModifier, RacialLeader, movementId, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ? OR 1 = ?"); 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/AI/ScriptedAI/ScriptedEscortAI.cs b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs index d5a9d664d..ec74dc286 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedEscortAI.cs @@ -40,7 +40,7 @@ namespace Game.AI return false; //experimental (unknown) flag not present - if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) + if (!me.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.CanAssist)) return false; //not a player diff --git a/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs b/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs index 591ef956e..c0a52250f 100644 --- a/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs +++ b/Source/Game/AI/ScriptedAI/ScriptedFollowerAI.cs @@ -283,7 +283,7 @@ namespace Game.AI return false; //experimental (unknown) flag not present - if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) + if (!me.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.CanAssist)) return false; if (!who.IsInAccessiblePlaceFor(me)) diff --git a/Source/Game/AI/SmartScripts/SmartAI.cs b/Source/Game/AI/SmartScripts/SmartAI.cs index 7e60ff267..fca753050 100644 --- a/Source/Game/AI/SmartScripts/SmartAI.cs +++ b/Source/Game/AI/SmartScripts/SmartAI.cs @@ -496,7 +496,7 @@ namespace Game.AI return false; //experimental (unknown) flag not present - if (!me.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist)) + if (!me.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.CanAssist)) return false; //not a player diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index 34032590b..3386437c1 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -124,7 +124,9 @@ namespace Game.Chat handler.SendSysMessage(CypherStrings.NpcinfoDynamicFlags, target.GetDynamicFlags()); handler.SendSysMessage(CypherStrings.CommandRawpawntimes, defRespawnDelayStr, curRespawnDelayStr); - handler.SendSysMessage(CypherStrings.NpcinfoLoot, cInfo.LootId, cInfo.PickPocketId, cInfo.SkinLootId); + + CreatureDifficulty creatureDifficulty = target.GetCreatureDifficulty(); + handler.SendSysMessage(CypherStrings.NpcinfoLoot, creatureDifficulty.LootID, creatureDifficulty.PickPocketLootID, creatureDifficulty.SkinLootID); handler.SendSysMessage(CypherStrings.NpcinfoDungeonId, target.GetInstanceId()); CreatureData data = Global.ObjectMgr.GetCreatureData(target.GetSpawnId()); @@ -406,7 +408,7 @@ namespace Game.Chat CreatureTemplate cInfo = creatureTarget.GetCreatureTemplate(); - if (!cInfo.IsTameable(player.CanTameExoticPets())) + if (!cInfo.IsTameable(player.CanTameExoticPets(), creatureTarget.GetCreatureDifficulty())) { handler.SendSysMessage(CypherStrings.CreatureNonTameable, cInfo.Entry); return false; diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index 2197ca4c3..38696e3b7 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -12,6 +12,7 @@ namespace Game.Entities { CreatureTemplate m_creatureInfo; CreatureData m_creatureData; + CreatureDifficulty m_creatureDifficulty; string[] m_stringIds = new string[3]; string m_scriptStringId; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index ec02664c0..e02cd6c76 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -211,53 +211,30 @@ namespace Game.Entities public bool InitEntry(uint entry, CreatureData data = null) { - CreatureTemplate normalInfo = Global.ObjectMgr.GetCreatureTemplate(entry); - if (normalInfo == null) + CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(entry); + if (creatureInfo == null) { Log.outError(LogFilter.Sql, "Creature.InitEntry creature entry {0} does not exist.", entry); return false; } - // get difficulty 1 mode entry - CreatureTemplate cInfo = null; - DifficultyRecord difficultyEntry = CliDB.DifficultyStorage.LookupByKey(GetMap().GetDifficultyID()); - while (cInfo == null && difficultyEntry != null) - { - int idx = CreatureTemplate.DifficultyIDToDifficultyEntryIndex(difficultyEntry.Id); - if (idx == -1) - break; - - if (normalInfo.DifficultyEntry[idx] != 0) - { - cInfo = Global.ObjectMgr.GetCreatureTemplate(normalInfo.DifficultyEntry[idx]); - break; - } - - if (difficultyEntry.FallbackDifficultyID == 0) - break; - - difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficultyEntry.FallbackDifficultyID); - } - - if (cInfo == null) - cInfo = normalInfo; - - SetEntry(entry); // normal entry always - m_creatureInfo = cInfo; // map mode related always + m_creatureInfo = creatureInfo; + SetEntry(entry); + m_creatureDifficulty = creatureInfo.GetDifficulty(GetMap().GetDifficultyID()); // equal to player Race field, but creature does not have race SetRace(0); - SetClass((Class)cInfo.UnitClass); + SetClass((Class)creatureInfo.UnitClass); // Cancel load if no model defined - if (cInfo.GetFirstValidModel() == null) + if (creatureInfo.GetFirstValidModel() == null) { Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has no model defined in table `creature_template`, can't load. ", entry); return false; } - CreatureModel model = ObjectManager.ChooseDisplayId(cInfo, data); - CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelRandomGender(ref model, cInfo); + CreatureModel model = ObjectManager.ChooseDisplayId(creatureInfo, data); + CreatureModelInfo minfo = Global.ObjectMgr.GetCreatureModelRandomGender(ref model, creatureInfo); if (minfo == null) // Cancel load if no model defined { Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has invalid model {1} defined in table `creature_template`, can't load.", entry, model.CreatureDisplayID); @@ -277,7 +254,7 @@ namespace Game.Entities LoadEquipment(data.equipmentId); } - SetName(normalInfo.Name); // at normal entry always + SetName(creatureInfo.Name); // at normal entry always SetModCastingSpeed(1.0f); SetModSpellHaste(1.0f); @@ -286,25 +263,25 @@ namespace Game.Entities SetModHasteRegen(1.0f); SetModTimeRate(1.0f); - SetSpeedRate(UnitMoveType.Walk, cInfo.SpeedWalk); - SetSpeedRate(UnitMoveType.Run, cInfo.SpeedRun); + SetSpeedRate(UnitMoveType.Walk, creatureInfo.SpeedWalk); + SetSpeedRate(UnitMoveType.Run, creatureInfo.SpeedRun); SetSpeedRate(UnitMoveType.Swim, 1.0f); // using 1.0 rate SetSpeedRate(UnitMoveType.Flight, 1.0f); // using 1.0 rate SetObjectScale(GetNativeObjectScale()); - SetCanDualWield(cInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.UseOffhandAttack)); + SetCanDualWield(creatureInfo.FlagsExtra.HasAnyFlag(CreatureFlagsExtra.UseOffhandAttack)); // checked at loading - DefaultMovementType = (MovementGeneratorType)(data != null ? data.movementType : cInfo.MovementType); + DefaultMovementType = (MovementGeneratorType)(data != null ? data.movementType : creatureInfo.MovementType); if (m_wanderDistance == 0 && DefaultMovementType == MovementGeneratorType.Random) DefaultMovementType = MovementGeneratorType.Idle; for (byte i = 0; i < SharedConst.MaxCreatureSpells; ++i) m_spells[i] = GetCreatureTemplate().Spells[i]; - _staticFlags.ApplyFlag(CreatureStaticFlags.NoXp, cInfo.CreatureType == CreatureType.Critter || IsPet() || IsTotem() || cInfo.FlagsExtra.HasFlag(CreatureFlagsExtra.NoXP)); - _staticFlags.ApplyFlag(CreatureStaticFlags4.TreatAsRaidUnitForHelpfulSpells, cInfo.TypeFlags.HasFlag(CreatureTypeFlags.TreatAsRaidUnit)); + _staticFlags.ApplyFlag(CreatureStaticFlags.NoXp, creatureInfo.CreatureType == CreatureType.Critter || IsPet() || IsTotem() || creatureInfo.FlagsExtra.HasFlag(CreatureFlagsExtra.NoXP)); + _staticFlags.ApplyFlag(CreatureStaticFlags4.TreatAsRaidUnitForHelpfulSpells, GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.TreatAsRaidUnit)); return true; } @@ -808,6 +785,8 @@ namespace Game.Entities return false; } + CreatureDifficulty creatureDifficulty = cinfo.GetDifficulty(GetMap().GetDifficultyID()); + //! Relocate before CreateFromProto, to initialize coords and allow //! returning correct zone id for selecting OutdoorPvP/Battlefield script Relocate(pos); @@ -828,7 +807,7 @@ namespace Game.Entities } // Allow players to see those units while dead, do it here (mayby altered by addon auras) - if (cinfo.TypeFlags.HasAnyFlag(CreatureTypeFlags.VisibleToGhosts)) + if (creatureDifficulty.TypeFlags.HasAnyFlag(CreatureTypeFlags.VisibleToGhosts)) m_serverSideVisibility.SetValue(ServerSideVisibilityType.Ghost, GhostVisibilityType.Alive | GhostVisibilityType.Ghost); if (!CreateFromProto(guidlow, entry, data, vehId)) @@ -1050,7 +1029,7 @@ namespace Game.Entities { base.AtEngage(target); - if (!GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.AllowMountedCombat)) + if (!GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.AllowMountedCombat)) Dismount(); RefreshCanSwimFlag(); @@ -1199,7 +1178,7 @@ namespace Game.Entities if (_lootId.HasValue) return _lootId.Value; - return GetCreatureTemplate().LootId; + return GetCreatureDifficulty().LootID; } public void SetLootId(uint? lootId) @@ -1470,7 +1449,7 @@ namespace Game.Entities // mana PowerType powerType = CalculateDisplayPowerType(); SetCreateMana(stats.BaseMana); - SetStatPctModifier(UnitMods.PowerStart + (int)powerType, UnitModifierPctType.Base, cInfo.ModMana * cInfo.ModManaExtra); + SetStatPctModifier(UnitMods.PowerStart + (int)powerType, UnitModifierPctType.Base, GetCreatureDifficulty().ManaModifier); SetPowerType(powerType); PowerTypeRecord powerTypeEntry = Global.DB2Mgr.GetPowerTypeEntry(powerType); @@ -2208,7 +2187,7 @@ namespace Game.Entities if (IsPet()) return false; - return Convert.ToBoolean(GetCreatureTemplate().TypeFlags & CreatureTypeFlags.BossMob); + return GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.BossMob); } // select nearest hostile unit within the given distance (regardless of threat list). @@ -2462,7 +2441,7 @@ namespace Game.Entities } // dependent from difficulty mode entry - return Global.ObjectMgr.GetCreatureTemplateAddon(GetCreatureTemplate().Entry); + return Global.ObjectMgr.GetCreatureTemplateAddon(GetEntry()); } public bool LoadCreaturesAddon() @@ -2716,29 +2695,29 @@ namespace Game.Entities public void ApplyLevelScaling() { - CreatureLevelScaling scaling = GetCreatureTemplate().GetLevelScaling(GetMap().GetDifficultyID()); - var levels = Global.DB2Mgr.GetContentTuningData(scaling.ContentTuningID, 0); + CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); + var levels = Global.DB2Mgr.GetContentTuningData(creatureDifficulty.ContentTuningID, 0); if (levels.HasValue) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ScalingLevelMin), levels.Value.MinLevel); SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ScalingLevelMax), levels.Value.MaxLevel); } - int mindelta = Math.Min(scaling.DeltaLevelMax, scaling.DeltaLevelMin); - int maxdelta = Math.Max(scaling.DeltaLevelMax, scaling.DeltaLevelMin); + int mindelta = Math.Min(creatureDifficulty.DeltaLevelMax, creatureDifficulty.DeltaLevelMin); + int maxdelta = Math.Max(creatureDifficulty.DeltaLevelMax, creatureDifficulty.DeltaLevelMin); int delta = mindelta == maxdelta ? mindelta : RandomHelper.IRand(mindelta, maxdelta); SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ScalingLevelDelta), delta); - SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ContentTuningID), scaling.ContentTuningID); + SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ContentTuningID), creatureDifficulty.ContentTuningID); } ulong GetMaxHealthByLevel(uint level) { CreatureTemplate cInfo = GetCreatureTemplate(); - CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); - float baseHealth = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, cInfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cInfo.UnitClass); + CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); + float baseHealth = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass); - return (ulong)Math.Max(baseHealth * cInfo.ModHealth * cInfo.ModHealthExtra, 1.0f); + return (ulong)Math.Max(baseHealth * creatureDifficulty.HealthModifier, 1.0f); } public override float GetHealthMultiplierForTarget(WorldObject target) @@ -2756,8 +2735,8 @@ namespace Game.Entities public float GetBaseDamageForLevel(uint level) { CreatureTemplate cInfo = GetCreatureTemplate(); - CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); - return Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureAutoAttackDps, level, cInfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cInfo.UnitClass); + CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); + return Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureAutoAttackDps, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass); } public override float GetDamageMultiplierForTarget(WorldObject target) @@ -2773,9 +2752,9 @@ namespace Game.Entities float GetBaseArmorForLevel(uint level) { CreatureTemplate cInfo = GetCreatureTemplate(); - CreatureLevelScaling scaling = cInfo.GetLevelScaling(GetMap().GetDifficultyID()); - float baseArmor = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureArmor, level, cInfo.GetHealthScalingExpansion(), scaling.ContentTuningID, (Class)cInfo.UnitClass); - return baseArmor * cInfo.ModArmor; + CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); + float baseArmor = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureArmor, level, creatureDifficulty.GetHealthScalingExpansion(), creatureDifficulty.ContentTuningID, (Class)cInfo.UnitClass); + return baseArmor * creatureDifficulty.ArmorModifier; } public override float GetArmorMultiplierForTarget(WorldObject target) @@ -3317,7 +3296,8 @@ namespace Game.Entities public CreatureTemplate GetCreatureTemplate() { return m_creatureInfo; } public CreatureData GetCreatureData() { return m_creatureData; } - + public CreatureDifficulty GetCreatureDifficulty() { return m_creatureDifficulty; } + public override bool LoadFromDB(ulong spawnId, Map map, bool addToMap, bool allowDuplicate) { if (!allowDuplicate) diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index 977e6fef6..8025141fd 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -7,13 +7,13 @@ using System; using System.Collections.Generic; using Game.Networking.Packets; using Game.Maps; +using Game.DataStorage; namespace Game.Entities { public class CreatureTemplate { public uint Entry; - public uint[] DifficultyEntry = new uint[SharedConst.MaxCreatureDifficulties]; public uint[] KillCredit = new uint[SharedConst.MaxCreatureKillCredit]; public List Models = new(); public string Name; @@ -22,8 +22,7 @@ namespace Game.Entities public string TitleAlt; public string IconName; public List GossipMenuIds = new(); - public Dictionary scalingStorage = new(); - public int HealthScalingExpansion; + public Dictionary difficultyStorage = new(); public uint RequiredExpansion; public uint VignetteID; // @todo Read Vignette.db2 public uint Faction; @@ -45,29 +44,15 @@ namespace Game.Entities public CreatureFamily Family; public Class TrainerClass; public CreatureType CreatureType; - public CreatureTypeFlags TypeFlags; - public uint TypeFlags2; - public uint LootId; - public uint PickPocketId; - public uint SkinLootId; public int[] Resistance = new int[7]; public uint[] Spells = new uint[8]; public uint VehicleId; - public uint MinGold; - public uint MaxGold; public string AIName; public uint MovementType; public CreatureMovementData Movement = new(); - public float ModHealth; - public float ModHealthExtra; - public float ModMana; - public float ModManaExtra; - public float ModArmor; - public float ModDamage; public float ModExperience; public bool RacialLeader; public uint MovementId; - public int CreatureDifficultyID; public int WidgetSetID; public int WidgetSetUnitConditionID; public bool RegenHealth; @@ -77,7 +62,7 @@ namespace Game.Entities public uint ScriptID; public string StringId; - public QueryCreatureResponse QueryData; + public QueryCreatureResponse[] QueryData = new QueryCreatureResponse[(int)Locale.Total]; public CreatureModel GetModelByIdx(int idx) { @@ -142,74 +127,33 @@ namespace Game.Entities return CreatureModel.DefaultVisibleModel; } - public int GetHealthScalingExpansion() + public bool IsExotic(CreatureDifficulty creatureDifficulty) { - return HealthScalingExpansion == (int)Expansion.LevelCurrent ? (int)Expansion.WarlordsOfDraenor : HealthScalingExpansion; + return creatureDifficulty.TypeFlags.HasFlag(CreatureTypeFlags.TameableExotic); } - - public SkillType GetRequiredLootSkill() + public bool IsTameable(bool canTameExotic, CreatureDifficulty creatureDifficulty) { - if (TypeFlags.HasAnyFlag(CreatureTypeFlags.SkinWithHerbalism)) - return SkillType.Herbalism; - else if (TypeFlags.HasAnyFlag(CreatureTypeFlags.SkinWithMining)) - return SkillType.Mining; - else if (TypeFlags.HasAnyFlag(CreatureTypeFlags.SkinWithEngineering)) - return SkillType.Engineering; - else - return SkillType.Skinning; // normal case - } - - public bool IsExotic() - { - return (TypeFlags & CreatureTypeFlags.TameableExotic) != 0; - } - public bool IsTameable(bool canTameExotic) - { - if (CreatureType != CreatureType.Beast || Family == CreatureFamily.None || !TypeFlags.HasAnyFlag(CreatureTypeFlags.Tameable)) + if (CreatureType != CreatureType.Beast || Family == CreatureFamily.None || !creatureDifficulty.TypeFlags.HasFlag(CreatureTypeFlags.Tameable)) return false; // if can tame exotic then can tame any tameable - return canTameExotic || !IsExotic(); - } - - public static int DifficultyIDToDifficultyEntryIndex(uint difficulty) - { - switch ((Difficulty)difficulty) - { - case Difficulty.None: - case Difficulty.Normal: - case Difficulty.Raid10N: - case Difficulty.Raid40: - case Difficulty.Scenario3ManN: - case Difficulty.NormalRaid: - return -1; - case Difficulty.Heroic: - case Difficulty.Raid25N: - case Difficulty.Scenario3ManHC: - case Difficulty.HeroicRaid: - return 0; - case Difficulty.Raid10HC: - case Difficulty.MythicKeystone: - case Difficulty.MythicRaid: - return 1; - case Difficulty.Raid25HC: - return 2; - case Difficulty.LFR: - case Difficulty.LFRNew: - case Difficulty.EventRaid: - case Difficulty.EventDungeon: - case Difficulty.EventScenario: - default: - return -1; - } + return canTameExotic || !IsExotic(creatureDifficulty); } public void InitializeQueryData() { - QueryData = new QueryCreatureResponse(); + for (var loc = Locale.enUS; loc < Locale.Total; ++loc) + QueryData[(int)loc] = BuildQueryData(loc, Difficulty.None); + } - QueryData.CreatureID = Entry; - QueryData.Allow = true; + public QueryCreatureResponse BuildQueryData(Locale locale, Difficulty difficulty) + { + CreatureDifficulty creatureDifficulty = GetDifficulty(difficulty); + + var queryTemp = new QueryCreatureResponse(); + + queryTemp.CreatureID = Entry; + queryTemp.Allow = true; CreatureStats stats = new(); stats.Leader = RacialLeader; @@ -217,8 +161,8 @@ namespace Game.Entities stats.Name[0] = Name; stats.NameAlt[0] = FemaleName; - stats.Flags[0] = (uint)TypeFlags; - stats.Flags[1] = TypeFlags2; + stats.Flags[0] = (uint)creatureDifficulty.TypeFlags; + stats.Flags[1] = creatureDifficulty.TypeFlags2; stats.CreatureType = (int)CreatureType; stats.CreatureFamily = (int)Family; @@ -233,15 +177,15 @@ namespace Game.Entities stats.Display.CreatureDisplay.Add(new CreatureXDisplay(model.CreatureDisplayID, model.DisplayScale, model.Probability)); } - stats.HpMulti = ModHealth; - stats.EnergyMulti = ModMana; + stats.HpMulti = creatureDifficulty.HealthModifier; + stats.EnergyMulti = creatureDifficulty.ManaModifier; stats.CreatureMovementInfoID = MovementId; stats.RequiredExpansion = RequiredExpansion; - stats.HealthScalingExpansion = HealthScalingExpansion; + stats.HealthScalingExpansion = creatureDifficulty.HealthScalingExpansion; stats.VignetteID = VignetteID; stats.Class = (int)UnitClass; - stats.CreatureDifficultyID = CreatureDifficultyID; + stats.CreatureDifficultyID = creatureDifficulty.CreatureDifficultyID; stats.WidgetSetID = WidgetSetID; stats.WidgetSetUnitConditionID = WidgetSetUnitConditionID; @@ -249,20 +193,41 @@ namespace Game.Entities stats.TitleAlt = TitleAlt; stats.CursorName = IconName; - var items = Global.ObjectMgr.GetCreatureQuestItemList(Entry); + var items = Global.ObjectMgr.GetCreatureQuestItemList(Entry, difficulty); if (items != null) stats.QuestItems.AddRange(items); - QueryData.Stats = stats; + if (locale != Locale.enUS) + { + CreatureLocale creatureLocale = Global.ObjectMgr.GetCreatureLocale(Entry); + if (creatureLocale != null) + { + string name = stats.Name[0]; + string nameAlt = stats.NameAlt[0]; + + ObjectManager.GetLocaleString(creatureLocale.Name, locale, ref name); + ObjectManager.GetLocaleString(creatureLocale.NameAlt, locale, ref nameAlt); + ObjectManager.GetLocaleString(creatureLocale.Title, locale, ref stats.Title); + ObjectManager.GetLocaleString(creatureLocale.TitleAlt, locale, ref stats.TitleAlt); + } + } + + queryTemp.Stats = stats; + return queryTemp; } - public CreatureLevelScaling GetLevelScaling(Difficulty difficulty) + public CreatureDifficulty GetDifficulty(Difficulty difficulty) { - var creatureLevelScaling = scalingStorage.LookupByKey(difficulty); - if (creatureLevelScaling != null) - return creatureLevelScaling; + var creatureDifficulty = difficultyStorage.LookupByKey(difficulty); + if (creatureDifficulty != null) + return creatureDifficulty; - return new CreatureLevelScaling(); + // If there is no data for the difficulty, try to get data for the fallback difficulty + var difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (difficultyEntry != null) + return GetDifficulty((Difficulty)difficultyEntry.FallbackDifficultyID); + + return new CreatureDifficulty(); } } @@ -347,7 +312,7 @@ namespace Game.Entities return $"Ground: {Ground}, Swim: {Swim}, Flight: {Flight} {(Rooted ? ", Rooted" : "")}, Chase: {Chase}, Random: {Random}, InteractionPauseTimer: {InteractionPauseTimer}"; } } - + public class CreatureModelInfo { public float BoundingRadius; @@ -462,10 +427,49 @@ namespace Game.Entities } } - public class CreatureLevelScaling + public class CreatureDifficulty { public short DeltaLevelMin; public short DeltaLevelMax; public uint ContentTuningID; + public int HealthScalingExpansion; + public float HealthModifier; + public float ManaModifier; + public float ArmorModifier; + public float DamageModifier; + public int CreatureDifficultyID; + public CreatureTypeFlags TypeFlags; + public uint TypeFlags2; + public uint LootID; + public uint PickPocketLootID; + public uint SkinLootID; + public uint GoldMin; + public uint GoldMax; + + public CreatureDifficulty() + { + HealthModifier = 1.0f; + ManaModifier = 1.0f; + ArmorModifier = 1.0f; + DamageModifier = 1.0f; + } + + // Helpers + public int GetHealthScalingExpansion() + { + return HealthScalingExpansion == (int)Expansion.LevelCurrent ? (int)PlayerConst.CurrentExpansion : HealthScalingExpansion; + } + + public SkillType GetRequiredLootSkill() + { + if (TypeFlags.HasFlag(CreatureTypeFlags.SkinWithHerbalism)) + return SkillType.Herbalism; + else if (TypeFlags.HasFlag(CreatureTypeFlags.SkinWithMining)) + return SkillType.Mining; + else if (TypeFlags.HasFlag(CreatureTypeFlags.SkinWithEngineering)) + return SkillType.Engineering; + else + return SkillType.Skinning; // Default case + } } } diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index 905f36998..35f1fa684 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -2773,7 +2773,7 @@ namespace Game.Entities { Creature creatureTarget = target.ToCreature(); if (creatureTarget != null) - return creatureTarget.HasFlag(CreatureStaticFlags4.TreatAsRaidUnitForHelpfulSpells) || creatureTarget.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanAssist); + return creatureTarget.HasFlag(CreatureStaticFlags4.TreatAsRaidUnitForHelpfulSpells) || creatureTarget.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.CanAssist); } } } diff --git a/Source/Game/Entities/Pet.cs b/Source/Game/Entities/Pet.cs index c765c4b2c..d7a50a912 100644 --- a/Source/Game/Entities/Pet.cs +++ b/Source/Game/Entities/Pet.cs @@ -162,7 +162,7 @@ namespace Game.Entities if (petInfo.Type == PetType.Hunter) { CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(petInfo.CreatureId); - if (creatureInfo == null || !creatureInfo.IsTameable(owner.CanTameExoticPets())) + if (creatureInfo == null || !creatureInfo.IsTameable(owner.CanTameExoticPets(), GetCreatureDifficulty())) return false; } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 3794dd079..1ee50e42a 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -5169,11 +5169,11 @@ namespace Game.Entities return null; // Deathstate checks - if (!IsAlive() && !Convert.ToBoolean(creature.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.VisibleToGhosts)) + if (!IsAlive() && !creature.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.VisibleToGhosts)) return null; // alive or spirit healer - if (!creature.IsAlive() && !Convert.ToBoolean(creature.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.InteractWhileDead)) + if (!creature.IsAlive() && !creature.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.InteractWhileDead)) return null; // appropriate npc type diff --git a/Source/Game/Entities/StatSystem.cs b/Source/Game/Entities/StatSystem.cs index 30ba68696..198b41c9a 100644 --- a/Source/Game/Entities/StatSystem.cs +++ b/Source/Game/Entities/StatSystem.cs @@ -2254,7 +2254,7 @@ namespace Game.Entities float basePct = GetPctModifierValue(unitMod, UnitModifierPctType.Base) * attackSpeedMulti; float totalValue = GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); float totalPct = addTotalPct ? GetPctModifierValue(unitMod, UnitModifierPctType.Total) : 1.0f; - float dmgMultiplier = GetCreatureTemplate().ModDamage; // = ModDamage * _GetDamageMod(rank); + float dmgMultiplier = GetCreatureDifficulty().DamageModifier; // = DamageModifier * _GetDamageMod(rank); minDamage = ((weaponMinDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; maxDamage = ((weaponMaxDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; diff --git a/Source/Game/Entities/TemporarySummon.cs b/Source/Game/Entities/TemporarySummon.cs index 311c3e37b..be475c71e 100644 --- a/Source/Game/Entities/TemporarySummon.cs +++ b/Source/Game/Entities/TemporarySummon.cs @@ -584,7 +584,8 @@ namespace Game.Entities CreatureBaseStats stats = Global.ObjectMgr.GetCreatureBaseStats(petlevel, cinfo.UnitClass); ApplyLevelScaling(); - SetCreateHealth((uint)Math.Max(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, petlevel, cinfo.GetHealthScalingExpansion(), m_unitData.ContentTuningID, (Class)cinfo.UnitClass) * cinfo.ModHealth * cinfo.ModHealthExtra * GetHealthMod(cinfo.Rank), 1.0f)); + CreatureDifficulty creatureDifficulty = GetCreatureDifficulty(); + SetCreateHealth((uint)Math.Max(Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.CreatureHealth, petlevel, creatureDifficulty.GetHealthScalingExpansion(), m_unitData.ContentTuningID, (Class)cinfo.UnitClass) * creatureDifficulty.HealthModifier * GetHealthMod(cinfo.Rank), 1.0f)); SetCreateMana(stats.BaseMana); SetCreateStat(Stats.Strength, 22); diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 66e40666a..14766a252 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -772,7 +772,7 @@ namespace Game.Entities if (dungeonEncounter != null) { creature.m_personalLoot = LootManager.GenerateDungeonEncounterPersonalLoot(dungeonEncounter.Id, creature.GetLootId(), - LootStorage.Creature, LootType.Corpse, creature, creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold, + LootStorage.Creature, LootType.Corpse, creature, creature.GetCreatureDifficulty().GoldMin, creature.GetCreatureDifficulty().GoldMax, (ushort)creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext(), tappers); } else if (!tappers.Empty()) @@ -787,7 +787,7 @@ namespace Game.Entities loot.FillLoot(lootid, LootStorage.Creature, looter, dungeonEncounter != null, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); if (creature.GetLootMode() > 0) - loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); + loot.GenerateMoneyLoot(creature.GetCreatureDifficulty().GoldMin, creature.GetCreatureDifficulty().GoldMax); if (group) loot.NotifyLootList(creature.GetMap()); @@ -814,7 +814,7 @@ namespace Game.Entities loot.FillLoot(lootid, LootStorage.Creature, tapper, true, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); if (creature.GetLootMode() > 0) - loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); + loot.GenerateMoneyLoot(creature.GetCreatureDifficulty().GoldMin, creature.GetCreatureDifficulty().GoldMax); creature.m_personalLoot[tapper.GetGUID()] = loot; } @@ -918,7 +918,7 @@ namespace Game.Entities else creature.AllLootRemovedFromCorpse(); - if (creature.CanHaveLoot() && LootStorage.Skinning.HaveLootFor(creature.GetCreatureTemplate().SkinLootId)) + if (creature.CanHaveLoot() && LootStorage.Skinning.HaveLootFor(creature.GetCreatureDifficulty().SkinLootID)) { creature.SetDynamicFlag(UnitDynFlags.CanSkin); creature.SetUnitFlag(UnitFlags.Skinnable); diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index b5d2c52f3..d02d81828 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -25,14 +25,7 @@ namespace Game { public sealed class ObjectManager : Singleton { - ObjectManager() - { - for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i) - { - difficultyEntries[i] = new List(); - hasDifficultyEntries[i] = new List(); - } - } + ObjectManager() { } //Static Methods public static bool NormalizePlayerName(ref string name) @@ -1792,44 +1785,35 @@ namespace Game CreatureTemplate creature = new(); creature.Entry = entry; - for (var i = 0; i < SharedConst.MaxCreatureDifficulties; ++i) - creature.DifficultyEntry[i] = fields.Read(1 + i); - for (var i = 0; i < 2; ++i) - creature.KillCredit[i] = fields.Read(4 + i); + creature.KillCredit[i] = fields.Read(1 + i); - creature.Name = fields.Read(6); - creature.FemaleName = fields.Read(7); - creature.SubName = fields.Read(8); - creature.TitleAlt = fields.Read(9); - creature.IconName = fields.Read(10); - creature.HealthScalingExpansion = fields.Read(11); - creature.RequiredExpansion = fields.Read(12); - creature.VignetteID = fields.Read(13); - creature.Faction = fields.Read(14); - creature.Npcflag = fields.Read(15); - creature.SpeedWalk = fields.Read(16); - creature.SpeedRun = fields.Read(17); - creature.Scale = fields.Read(18); - creature.Rank = (CreatureEliteType)fields.Read(19); - creature.DmgSchool = fields.Read(20); - creature.BaseAttackTime = fields.Read(21); - creature.RangeAttackTime = fields.Read(22); - creature.BaseVariance = fields.Read(23); - creature.RangeVariance = fields.Read(24); - creature.UnitClass = fields.Read(25); - creature.UnitFlags = (UnitFlags)fields.Read(26); - creature.UnitFlags2 = fields.Read(27); - creature.UnitFlags3 = fields.Read(28); - creature.DynamicFlags = fields.Read(29); - creature.Family = (CreatureFamily)fields.Read(30); - creature.TrainerClass = (Class)fields.Read(31); - creature.CreatureType = (CreatureType)fields.Read(32); - creature.TypeFlags = (CreatureTypeFlags)fields.Read(33); - creature.TypeFlags2 = fields.Read(34); - creature.LootId = fields.Read(35); - creature.PickPocketId = fields.Read(36); - creature.SkinLootId = fields.Read(37); + creature.Name = fields.Read(3); + creature.FemaleName = fields.Read(4); + creature.SubName = fields.Read(5); + creature.TitleAlt = fields.Read(6); + creature.IconName = fields.Read(7); + creature.RequiredExpansion = fields.Read(8); + creature.VignetteID = fields.Read(9); + creature.Faction = fields.Read(10); + creature.Npcflag = fields.Read(11); + creature.SpeedWalk = fields.Read(12); + creature.SpeedRun = fields.Read(13); + creature.Scale = fields.Read(14); + creature.Rank = (CreatureEliteType)fields.Read(15); + creature.DmgSchool = fields.Read(16); + creature.BaseAttackTime = fields.Read(17); + creature.RangeAttackTime = fields.Read(18); + creature.BaseVariance = fields.Read(19); + creature.RangeVariance = fields.Read(20); + creature.UnitClass = fields.Read(21); + creature.UnitFlags = (UnitFlags)fields.Read(22); + creature.UnitFlags2 = fields.Read(23); + creature.UnitFlags3 = fields.Read(24); + creature.DynamicFlags = fields.Read(25); + creature.Family = (CreatureFamily)fields.Read(26); + creature.TrainerClass = (Class)fields.Read(27); + creature.CreatureType = (CreatureType)fields.Read(28); for (var i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) creature.Resistance[i] = 0; @@ -1837,51 +1821,42 @@ namespace Game for (var i = 0; i < SharedConst.MaxCreatureSpells; ++i) creature.Spells[i] = 0; - creature.VehicleId = fields.Read(38); - creature.MinGold = fields.Read(39); - creature.MaxGold = fields.Read(40); - creature.AIName = fields.Read(41); - creature.MovementType = fields.Read(42); + creature.VehicleId = fields.Read(29); + creature.AIName = fields.Read(30); + creature.MovementType = fields.Read(31); - if (!fields.IsNull(43)) - creature.Movement.Ground = (CreatureGroundMovementType)fields.Read(43); + if (!fields.IsNull(32)) + creature.Movement.Ground = (CreatureGroundMovementType)fields.Read(32); - if (!fields.IsNull(44)) - creature.Movement.Swim = fields.Read(44); + if (!fields.IsNull(33)) + creature.Movement.Swim = fields.Read(33); - if (!fields.IsNull(45)) - creature.Movement.Flight = (CreatureFlightMovementType)fields.Read(45); + if (!fields.IsNull(34)) + creature.Movement.Flight = (CreatureFlightMovementType)fields.Read(34); - if (!fields.IsNull(46)) - creature.Movement.Rooted = fields.Read(46); + if (!fields.IsNull(35)) + creature.Movement.Rooted = fields.Read(35); - if (!fields.IsNull(47)) - creature.Movement.Chase = (CreatureChaseMovementType)fields.Read(47); + if (!fields.IsNull(36)) + creature.Movement.Chase = (CreatureChaseMovementType)fields.Read(36); - if (!fields.IsNull(48)) - creature.Movement.Random = (CreatureRandomMovementType)fields.Read(48); + if (!fields.IsNull(37)) + creature.Movement.Random = (CreatureRandomMovementType)fields.Read(37); - if (!fields.IsNull(49)) - creature.Movement.InteractionPauseTimer = fields.Read(49); + if (!fields.IsNull(38)) + creature.Movement.InteractionPauseTimer = fields.Read(38); - creature.ModHealth = fields.Read(50); - creature.ModHealthExtra = fields.Read(51); - creature.ModMana = fields.Read(52); - creature.ModManaExtra = fields.Read(53); - creature.ModArmor = fields.Read(54); - creature.ModDamage = fields.Read(55); - creature.ModExperience = fields.Read(56); - creature.RacialLeader = fields.Read(57); - creature.MovementId = fields.Read(58); - creature.CreatureDifficultyID = fields.Read(59); - creature.WidgetSetID = fields.Read(60); - creature.WidgetSetUnitConditionID = fields.Read(61); - creature.RegenHealth = fields.Read(62); - creature.MechanicImmuneMask = fields.Read(63); - creature.SpellSchoolImmuneMask = fields.Read(64); - creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(65); - creature.ScriptID = GetScriptId(fields.Read(66)); - creature.StringId = fields.Read(67); + creature.ModExperience = fields.Read(39); + creature.RacialLeader = fields.Read(40); + creature.MovementId = fields.Read(41); + creature.WidgetSetID = fields.Read(42); + creature.WidgetSetUnitConditionID = fields.Read(43); + creature.RegenHealth = fields.Read(44); + creature.MechanicImmuneMask = fields.Read(45); + creature.SpellSchoolImmuneMask = fields.Read(46); + creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(47); + creature.ScriptID = GetScriptId(fields.Read(48)); + creature.StringId = fields.Read(49); creatureTemplateStorage[entry] = creature; } @@ -2392,8 +2367,8 @@ namespace Game { uint oldMSTime = Time.GetMSTime(); - // 0 1 2 - SQLResult result = DB.World.Query("SELECT CreatureEntry, ItemId, Idx FROM creature_questitem ORDER BY Idx ASC"); + // 0 1 2 3 + SQLResult result = DB.World.Query("SELECT CreatureEntry, DifficultyID, ItemId, Idx FROM creature_questitem ORDER BY Idx ASC"); if (result.IsEmpty()) { Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature quest items. DB table `creature_questitem` is empty."); @@ -2404,22 +2379,23 @@ namespace Game do { uint entry = result.Read(0); - uint item = result.Read(1); - uint idx = result.Read(2); + Difficulty difficulty = (Difficulty)result.Read(1); + uint item = result.Read(2); + uint idx = result.Read(3); if (!creatureTemplateStorage.ContainsKey(entry)) { - Log.outError(LogFilter.Sql, "Table `creature_questitem` has data for nonexistent creature (entry: {0}, idx: {1}), skipped", entry, idx); + Log.outError(LogFilter.Sql, $"Table `creature_questitem` has data for nonexistent creature (entry: {entry}, difficulty: {difficulty} idx: {idx}), skipped"); continue; } if (!CliDB.ItemStorage.ContainsKey(item)) { - Log.outError(LogFilter.Sql, "Table `creature_questitem` has nonexistent item (ID: {0}) in creature (entry: {1}, idx: {2}), skipped", item, entry, idx); + Log.outError(LogFilter.Sql, $"Table `creature_questitem` has nonexistent item (ID: {item}) in creature (entry: {entry}, difficulty: {difficulty} idx: {idx}), skipped"); continue; } - creatureQuestItemStorage.Add(entry, item); + creatureQuestItemStorage.Add((entry, difficulty), item); ++count; } @@ -2709,15 +2685,15 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template sparring rows in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } - public void LoadCreatureScalingData() + public void LoadCreatureTemplateDifficulty() { uint oldMSTime = Time.GetMSTime(); - // 0 1 2 3 4 - var result = DB.World.Query("SELECT Entry, DifficultyID, LevelScalingDeltaMin, LevelScalingDeltaMax, ContentTuningID FROM creature_template_scaling ORDER BY Entry"); + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + SQLResult result = DB.World.Query("SELECT Entry, DifficultyID, LevelScalingDeltaMin, LevelScalingDeltaMax, ContentTuningID, HealthScalingExpansion, HealthModifier, ManaModifier, ArmorModifier, DamageModifier, CreatureDifficultyID, TypeFlags, TypeFlags2, LootID, PickPocketLootID, SkinLootID, GoldMin, GoldMax FROM creature_template_difficulty ORDER BY Entry"); if (result.IsEmpty()) { - Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature template scaling definitions. DB table `creature_template_scaling` is empty."); + Log.outInfo(LogFilter.ServerLoading, "Loaded 0 creature template difficulty definitions. DB table `creature_template_difficulty` is empty."); return; } @@ -2730,196 +2706,55 @@ namespace Game 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`"); + Log.outError(LogFilter.Sql, $"Creature template (Entry: {entry}) does not exist but has a record in `creature_template_difficulty`"); continue; } - CreatureLevelScaling creatureLevelScaling = new(); - creatureLevelScaling.DeltaLevelMin = result.Read(2); - creatureLevelScaling.DeltaLevelMax = result.Read(3); - creatureLevelScaling.ContentTuningID = result.Read(4); + CreatureDifficulty creatureDifficulty = new(); + creatureDifficulty.DeltaLevelMin = result.Read(2); + creatureDifficulty.DeltaLevelMax = result.Read(3); + creatureDifficulty.ContentTuningID = result.Read(4); + creatureDifficulty.HealthScalingExpansion = result.Read(5); + creatureDifficulty.HealthModifier = result.Read(6); + creatureDifficulty.ManaModifier = result.Read(7); + creatureDifficulty.ArmorModifier = result.Read(8); + creatureDifficulty.DamageModifier = result.Read(9); + creatureDifficulty.CreatureDifficultyID = result.Read(10); + creatureDifficulty.TypeFlags = (CreatureTypeFlags)result.Read(11); + creatureDifficulty.TypeFlags2 = result.Read(12); + creatureDifficulty.LootID = result.Read(13); + creatureDifficulty.PickPocketLootID = result.Read(14); + creatureDifficulty.SkinLootID = result.Read(15); + creatureDifficulty.GoldMin = result.Read(16); + creatureDifficulty.GoldMax = result.Read(17); - template.scalingStorage[difficulty] = creatureLevelScaling; + // TODO: Check if this still applies + creatureDifficulty.DamageModifier *= Creature._GetDamageMod(template.Rank); - // Assign creature level scaling to creature difficulty entry (if any) - // TODO: Drop the use of creature difficulties - int difficultyIndex = CreatureTemplate.DifficultyIDToDifficultyEntryIndex((uint)difficulty); - if (difficultyIndex != -1) + if (creatureDifficulty.HealthScalingExpansion < (int)Expansion.LevelCurrent || creatureDifficulty.HealthScalingExpansion >= (int)Expansion.Max) { - uint difficultyEntry = template.DifficultyEntry[difficultyIndex]; - if (difficultyEntry != 0) - { - var difficultyTemplate = creatureTemplateStorage.LookupByKey(difficultyEntry); - if (difficultyTemplate != null) - difficultyTemplate.scalingStorage[difficulty] = creatureLevelScaling; - } + Log.outError(LogFilter.Sql, $"Table `creature_template_difficulty` lists creature (ID: {entry}) with invalid `HealthScalingExpansion` {creatureDifficulty.HealthScalingExpansion}. Ignored and set to 0."); + creatureDifficulty.HealthScalingExpansion = 0; } + if (creatureDifficulty.GoldMin > creatureDifficulty.GoldMax) + { + Log.outError(LogFilter.Sql, $"Table `creature_template_difficulty` lists creature (ID: {entry}) with `GoldMin` {creatureDifficulty.GoldMin} greater than `GoldMax` {creatureDifficulty.GoldMax}, setting `GoldMax` to {creatureDifficulty.GoldMin}."); + creatureDifficulty.GoldMax = creatureDifficulty.GoldMin; + } + + template.difficultyStorage[difficulty] = creatureDifficulty; + ++count; } while (result.NextRow()); - Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template scaling data in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); + Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} creature template difficulty data in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } public void CheckCreatureTemplate(CreatureTemplate cInfo) { if (cInfo == null) return; - bool ok = true; // bool to allow continue outside this loop - for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff) - { - if (cInfo.DifficultyEntry[diff] == 0) - continue; - ok = false; // will be set to true at the end of this loop again - - CreatureTemplate difficultyInfo = GetCreatureTemplate(cInfo.DifficultyEntry[diff]); - if (difficultyInfo == null) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has `difficulty_entry_{1}`={2} but creature entry {3} does not exist.", - cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff], cInfo.DifficultyEntry[diff]); - continue; - } - - bool ok2 = true; - for (uint diff2 = 0; diff2 < SharedConst.MaxCreatureDifficulties && ok2; ++diff2) - { - ok2 = false; - if (difficultyEntries[diff2].Contains(cInfo.Entry)) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) is listed as `difficulty_entry_{1}` of another creature, but itself lists {2} in `difficulty_entry_{3}`.", - cInfo.Entry, diff2 + 1, cInfo.DifficultyEntry[diff], diff + 1); - continue; - } - - if (difficultyEntries[diff2].Contains(cInfo.DifficultyEntry[diff])) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) already listed as `difficulty_entry_{1}` for another entry.", cInfo.DifficultyEntry[diff], diff2 + 1); - continue; - } - - if (hasDifficultyEntries[diff2].Contains(cInfo.DifficultyEntry[diff])) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has `difficulty_entry_{1}`={2} but creature entry {3} has itself a value in `difficulty_entry_{4}`.", - cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff], cInfo.DifficultyEntry[diff], diff2 + 1); - continue; - } - ok2 = true; - } - if (!ok2) - continue; - - if (cInfo.HealthScalingExpansion > difficultyInfo.HealthScalingExpansion) - { - Log.outError(LogFilter.Sql, "Creature (Id: {0}, Expansion {1}) has different `HealthScalingExpansion` in difficulty {2} mode (Id: {3}, Expansion: {4}).", - cInfo.Entry, cInfo.HealthScalingExpansion, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.HealthScalingExpansion); - } - - if (cInfo.Faction != difficultyInfo.Faction) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, faction: {1}) has different `faction` in difficulty {2} mode (Entry: {3}, faction: {4}).", - cInfo.Entry, cInfo.Faction, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Faction); - } - - if (cInfo.UnitClass != difficultyInfo.UnitClass) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, class: {1}) has different `unit_class` in difficulty {2} mode (Entry: {3}, class: {4}).", - cInfo.Entry, cInfo.UnitClass, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.UnitClass); - continue; - } - - if (cInfo.Npcflag != difficultyInfo.Npcflag) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `npcflag` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); - Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `npcflag`=`npcflag`^{0} WHERE `entry`={1};", cInfo.Npcflag ^ difficultyInfo.Npcflag, cInfo.DifficultyEntry[diff]); - continue; - } - - 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}).", - 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}).", - 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]); - } - - if (cInfo.Family != difficultyInfo.Family) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, family: {1}) has different `family` in difficulty {2} mode (Entry: {3}, family: {4}).", - cInfo.Entry, cInfo.Family, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.Family); - } - - if (cInfo.TrainerClass != difficultyInfo.TrainerClass) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has different `trainer_class` in difficulty {1} mode (Entry: {2}).", cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); - continue; - } - - if (cInfo.CreatureType != difficultyInfo.CreatureType) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, type: {1}) has different `type` in difficulty {2} mode (Entry: {3}, type: {4}).", - cInfo.Entry, cInfo.CreatureType, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.CreatureType); - } - - if (cInfo.VehicleId == 0 && difficultyInfo.VehicleId != 0) - { - Log.outError(LogFilter.Sql, "Non-vehicle Creature (Entry: {0}, VehicleId: {1}) has `VehicleId` set in difficulty {2} mode (Entry: {3}, VehicleId: {4}).", - cInfo.Entry, cInfo.VehicleId, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.VehicleId); - } - - 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}).", - 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]); - } - - ulong differenceMask = cInfo.MechanicImmuneMask & (~difficultyInfo.MechanicImmuneMask); - if (differenceMask != 0) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, mechanic_immune_mask: {1}) has weaker immunities in difficulty {2} mode (Entry: {3}, mechanic_immune_mask: {4}).", - cInfo.Entry, cInfo.MechanicImmuneMask, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.MechanicImmuneMask); - Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `mechanic_immune_mask`=`mechanic_immune_mask`|{0} WHERE `entry`={1};", differenceMask, cInfo.DifficultyEntry[diff]); - } - - differenceMask = (uint)((cInfo.FlagsExtra ^ difficultyInfo.FlagsExtra) & (~CreatureFlagsExtra.InstanceBind)); - if (differenceMask != 0) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}, flags_extra: {1}) has different `flags_extra` in difficulty {2} mode (Entry: {3}, flags_extra: {4}).", - cInfo.Entry, cInfo.FlagsExtra, diff + 1, cInfo.DifficultyEntry[diff], difficultyInfo.FlagsExtra); - Log.outError(LogFilter.Sql, "Possible FIX: UPDATE `creature_template` SET `flags_extra`=`flags_extra`^{0} WHERE `entry`={1};", differenceMask, cInfo.DifficultyEntry[diff]); - } - - if (difficultyInfo.AIName.IsEmpty()) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists difficulty {1} mode entry {2} with `AIName` filled in. `AIName` of difficulty 0 mode creature is always used instead.", - cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); - continue; - } - - if (difficultyInfo.ScriptID != 0) - { - Log.outError(LogFilter.Sql, "Creature (Entry: {0}) lists difficulty {1} mode entry {2} with `ScriptName` filled in. `ScriptName` of difficulty 0 mode creature is always used instead.", - cInfo.Entry, diff + 1, cInfo.DifficultyEntry[diff]); - continue; - } - - hasDifficultyEntries[diff].Add(cInfo.Entry); - difficultyEntries[diff].Add(cInfo.DifficultyEntry[diff]); - ok = true; - } - - if (cInfo.MinGold > cInfo.MaxGold) - { - Log.outError(LogFilter.Sql, $"Creature (Entry: {cInfo.Entry}) has `mingold` {cInfo.MinGold} which is greater than `maxgold` {cInfo.MaxGold}, setting `maxgold` to {cInfo.MinGold}."); - cInfo.MaxGold = cInfo.MinGold; - } - if (!CliDB.FactionTemplateStorage.ContainsKey(cInfo.Faction)) { Log.outError(LogFilter.Sql, "Creature (Entry: {0}) has non-existing faction template ({1}). This can lead to crashes, set to faction 35", cInfo.Entry, cInfo.Faction); @@ -3009,12 +2844,6 @@ namespace Game cInfo.MovementType = (uint)MovementGeneratorType.Idle; } - 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) { Log.outError(LogFilter.Sql, "Table `creature_template` lists creature (Entry: {0}) with `RequiredExpansion` {1}. Ignored and set to 0.", cInfo.Entry, cInfo.RequiredExpansion); @@ -3055,8 +2884,6 @@ namespace Game cInfo.DynamicFlags = 0; } - cInfo.ModDamage *= Creature._GetDamageMod(cInfo.Rank); - if (!cInfo.GossipMenuIds.Empty() && !cInfo.Npcflag.HasAnyFlag((uint)NPCFlags.Gossip)) Log.outInfo(LogFilter.Sql, $"Creature (Entry: {cInfo.Entry}) has assigned gossip menu, but npcflag does not include UNIT_NPC_FLAG_GOSSIP."); else if (cInfo.GossipMenuIds.Empty() && cInfo.Npcflag.HasAnyFlag((uint)NPCFlags.Gossip)) @@ -3694,18 +3521,6 @@ namespace Game continue; } - bool ok = true; - for (uint diff = 0; diff < SharedConst.MaxCreatureDifficulties && ok; ++diff) - { - if (difficultyEntries[diff].Contains(data.Id)) - { - Log.outError(LogFilter.Sql, "Table `creature` have creature (GUID: {0}) that listed as difficulty {1} template (entry: {2}) in `creaturetemplate`, skipped.", guid, diff + 1, data.Id); - ok = false; - } - } - if (!ok) - continue; - // -1 random, 0 no equipment, if (data.equipmentId != 0) { @@ -3934,39 +3749,43 @@ namespace Game RemoveSpawnDataFromGrid(data); } - public List GetCreatureQuestItemList(uint id) - { - return creatureQuestItemStorage.LookupByKey(id); - } public CreatureAddon GetCreatureAddon(ulong lowguid) { return creatureAddonStorage.LookupByKey(lowguid); } + public CreatureTemplate GetCreatureTemplate(uint entry) { return creatureTemplateStorage.LookupByKey(entry); } + public CreatureAddon GetCreatureTemplateAddon(uint entry) { return creatureTemplateAddonStorage.LookupByKey(entry); } + public uint GetCreatureDefaultTrainer(uint creatureId) { return GetCreatureTrainerForGossipOption(creatureId, 0, 0); } + public uint GetCreatureTrainerForGossipOption(uint creatureId, uint gossipMenuId, uint gossipOptionIndex) { return _creatureDefaultTrainers.LookupByKey((creatureId, gossipMenuId, gossipOptionIndex)); } + public Dictionary GetCreatureTemplates() { return creatureTemplateStorage; } + public Dictionary GetAllCreatureData() { return creatureDataStorage; } + public CreatureData GetCreatureData(ulong spawnId) { return creatureDataStorage.LookupByKey(spawnId); } + public ObjectGuid GetLinkedRespawnGuid(ObjectGuid spawnId) { var retGuid = linkedRespawnStorage.LookupByKey(spawnId); @@ -3974,6 +3793,7 @@ namespace Game return ObjectGuid.Empty; return retGuid; } + public bool SetCreatureLinkedRespawn(ulong guidLow, ulong linkedGuidLow) { if (guidLow == 0) @@ -4024,12 +3844,14 @@ namespace Game DB.World.Execute(stmt); return true; } + public CreatureData NewOrExistCreatureData(ulong spawnId) { if (!creatureDataStorage.ContainsKey(spawnId)) creatureDataStorage[spawnId] = new CreatureData(); return creatureDataStorage[spawnId]; } + public void DeleteCreatureData(ulong spawnId) { CreatureData data = GetCreatureData(spawnId); @@ -4041,6 +3863,7 @@ namespace Game creatureDataStorage.Remove(spawnId); } + public CreatureBaseStats GetCreatureBaseStats(uint level, uint unitClass) { var stats = creatureBaseStatsStorage.LookupByKey(MathFunctions.MakePair16(level, unitClass)); @@ -4049,6 +3872,7 @@ namespace Game return new DefaultCreatureBaseStats(); } + public CreatureModelInfo GetCreatureModelRandomGender(ref CreatureModel model, CreatureTemplate creatureTemplate) { CreatureModelInfo modelInfo = GetCreatureModelInfo(model.CreatureDisplayID); @@ -4081,14 +3905,17 @@ namespace Game return modelInfo; } + public CreatureModelInfo GetCreatureModelInfo(uint modelId) { return creatureModelStorage.LookupByKey(modelId); } + public CreatureSummonedData GetCreatureSummonedData(uint entryId) { return creatureSummonedDataStorage.LookupByKey(entryId); } + public NpcText GetNpcText(uint textId) { return npcTextStorage.LookupByKey(textId); @@ -4289,6 +4116,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object templates in {1} ms", _gameObjectTemplateStorage.Count, Time.GetMSTimeDiffToNow(time)); } } + public void LoadGameObjectTemplateAddons() { uint oldMSTime = Time.GetMSTime(); @@ -4372,6 +4200,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} game object template addons in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadGameObjectOverrides() { uint oldMSTime = Time.GetMSTime(); @@ -4409,6 +4238,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} gameobject faction and flags overrides in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } + public void LoadGameObjects() { var time = Time.GetMSTime(); @@ -4639,6 +4469,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobjects in {1} ms", count, Time.GetMSTimeDiffToNow(time)); } + public void LoadGameObjectAddons() { uint oldMSTime = Time.GetMSTime(); @@ -4710,6 +4541,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, $"Loaded {count} gameobject addons in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } + public void LoadGameObjectQuestItems() { uint oldMSTime = Time.GetMSTime(); @@ -4749,6 +4581,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject quest items in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadGameObjectForQuests() { uint oldMSTime = Time.GetMSTime(); @@ -4814,10 +4647,12 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} GameObjects for quests in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void AddGameObjectToGrid(GameObjectData data) { AddSpawnDataToGrid(data); } + public void RemoveGameObjectFromGrid(GameObjectData data) { RemoveSpawnDataFromGrid(data); @@ -4827,16 +4662,21 @@ namespace Game { return _gameObjectAddonStorage.LookupByKey(lowguid); } + public List GetGameObjectQuestItemList(uint id) { return _gameObjectQuestItemStorage.LookupByKey(id); } + MultiMap GetGameObjectQuestItemMap() { return _gameObjectQuestItemStorage; } + public Dictionary GetAllGameObjectData() { return gameObjectDataStorage; } + public GameObjectData GetGameObjectData(ulong spawnId) { return gameObjectDataStorage.LookupByKey(spawnId); } + public void DeleteGameObjectData(ulong spawnId) { GameObjectData data = GetGameObjectData(spawnId); @@ -4848,6 +4688,7 @@ namespace Game gameObjectDataStorage.Remove(spawnId); } + public GameObjectData NewOrExistGameObjectData(ulong spawnId) { if (!gameObjectDataStorage.ContainsKey(spawnId)) @@ -4855,26 +4696,32 @@ namespace Game return gameObjectDataStorage[spawnId]; } + public GameObjectTemplate GetGameObjectTemplate(uint entry) { return _gameObjectTemplateStorage.LookupByKey(entry); } + public GameObjectTemplateAddon GetGameObjectTemplateAddon(uint entry) { return _gameObjectTemplateAddonStorage.LookupByKey(entry); } + public GameObjectOverride GetGameObjectOverride(ulong spawnId) { return _gameObjectOverrideStorage.LookupByKey(spawnId); } + public Dictionary GetGameObjectTemplates() { return _gameObjectTemplateStorage; } + public bool IsGameObjectForQuests(uint entry) { return _gameObjectForQuestStorage.Contains(entry); } + void CheckGOLockId(GameObjectTemplate goInfo, uint dataN, uint N) { if (CliDB.LockStorage.ContainsKey(dataN)) @@ -4882,6 +4729,7 @@ namespace Game Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but lock (Id: {4}) not found.", goInfo.entry, goInfo.type, N, goInfo.Door.open, goInfo.Door.open); } + void CheckGOLinkedTrapId(GameObjectTemplate goInfo, uint dataN, uint N) { GameObjectTemplate trapInfo = GetGameObjectTemplate(dataN); @@ -4891,6 +4739,7 @@ namespace Game Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but GO (Entry {4}) have not GAMEOBJECT_TYPE_TRAP type.", goInfo.entry, goInfo.type, N, dataN, dataN); } } + void CheckGOSpellId(GameObjectTemplate goInfo, uint dataN, uint N) { if (Global.SpellMgr.HasSpellInfo(dataN, Difficulty.None)) @@ -4898,6 +4747,7 @@ namespace Game Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but Spell (Entry {4}) not exist.", goInfo.entry, goInfo.type, N, dataN, dataN); } + void CheckAndFixGOChairHeightId(GameObjectTemplate goInfo, ref uint dataN, uint N) { if (dataN <= (UnitStandStateType.SitHighChair - UnitStandStateType.SitLowChair)) @@ -4908,6 +4758,7 @@ namespace Game // prevent client and server unexpected work dataN = 0; } + void CheckGONoDamageImmuneId(GameObjectTemplate goTemplate, uint dataN, uint N) { // 0/1 correct values @@ -4916,6 +4767,7 @@ namespace Game Log.outError(LogFilter.Sql, "Gameobject (Entry: {0} GoType: {1}) have data{2}={3} but expected boolean (0/1) noDamageImmune field value.", goTemplate.entry, goTemplate.type, N, dataN); } + void CheckGOConsumable(GameObjectTemplate goInfo, uint dataN, uint N) { // 0/1 correct values @@ -5668,16 +5520,6 @@ namespace Game continue; } creatureInfo.FlagsExtra |= CreatureFlagsExtra.DungeonBoss; - for (byte diff = 0; diff < SharedConst.MaxCreatureDifficulties; ++diff) - { - uint diffEntry = creatureInfo.DifficultyEntry[diff]; - if (diffEntry != 0) - { - CreatureTemplate diffInfo = GetCreatureTemplate(diffEntry); - if (diffInfo != null) - diffInfo.FlagsExtra |= CreatureFlagsExtra.DungeonBoss; - } - } break; } case EncounterCreditType.CastSpell: @@ -8087,6 +7929,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quests definitions in {1} ms", _questTemplates.Count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadQuestStartersAndEnders() { Log.outInfo(LogFilter.ServerLoading, "Loading GO Start Quest Data..."); @@ -8098,6 +7941,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Creature End Quest Data..."); LoadCreatureQuestEnders(); } + public void LoadGameobjectQuestStarters() { LoadQuestRelationsHelper(_goQuestRelations, null, "gameobject_queststarter"); @@ -8111,6 +7955,7 @@ namespace Game Log.outError(LogFilter.Sql, "Table `gameobject_queststarter` have data gameobject entry ({0}) for quest {1}, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", pair.Key, pair.Value); } } + public void LoadGameobjectQuestEnders() { LoadQuestRelationsHelper(_goQuestInvolvedRelations, _goQuestInvolvedRelationsReverse, "gameobject_questender"); @@ -8124,6 +7969,7 @@ namespace Game Log.outError(LogFilter.Sql, "Table `gameobject_questender` have data gameobject entry ({0}) for quest {1}, but GO is not GAMEOBJECT_TYPE_QUESTGIVER", pair.Key, pair.Value); } } + public void LoadCreatureQuestStarters() { LoadQuestRelationsHelper(_creatureQuestRelations, null, "creature_queststarter"); @@ -8137,6 +7983,7 @@ namespace Game Log.outError(LogFilter.Sql, "Table `creature_queststarter` has creature entry ({0}) for quest {1}, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", pair.Key, pair.Value); } } + public void LoadCreatureQuestEnders() { LoadQuestRelationsHelper(_creatureQuestInvolvedRelations, _creatureQuestInvolvedRelationsReverse, "creature_questender"); @@ -8150,6 +7997,7 @@ namespace Game Log.outError(LogFilter.Sql, "Table `creature_questender` has creature entry ({0}) for quest {1}, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER", pair.Key, pair.Value); } } + void LoadQuestRelationsHelper(MultiMap map, MultiMap reverseMap, string table) { uint oldMSTime = Time.GetMSTime(); @@ -8186,6 +8034,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest relations from {1} in {2} ms", count, table, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadQuestPOI() { uint oldMSTime = Time.GetMSTime(); @@ -8264,6 +8113,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest POI definitions in {1} ms", _questPOIStorage.Count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadQuestAreaTriggers() { uint oldMSTime = Time.GetMSTime(); @@ -8324,6 +8174,7 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loaded {0} quest trigger points in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime)); } + public void LoadQuestGreetings() { uint oldMSTime = Time.GetMSTime(); @@ -8381,34 +8232,48 @@ namespace Game { return _questTemplates.LookupByKey(questId); } + public Dictionary GetQuestTemplates() { return _questTemplates; } + public List GetQuestTemplatesAutoPush() { return _questTemplatesAutoPush; } + public MultiMap GetGOQuestRelationMapHACK() { return _goQuestRelations; } + public QuestRelationResult GetGOQuestRelations(uint entry) { return GetQuestRelationsFrom(_goQuestRelations, entry, true); } + public QuestRelationResult GetGOQuestInvolvedRelations(uint entry) { return GetQuestRelationsFrom(_goQuestInvolvedRelations, entry, false); } + public List GetGOQuestInvolvedRelationReverseBounds(uint questId) { return _goQuestInvolvedRelationsReverse.LookupByKey(questId); } + public MultiMap GetCreatureQuestRelationMapHACK() { return _creatureQuestRelations; } + public QuestRelationResult GetCreatureQuestRelations(uint entry) { return GetQuestRelationsFrom(_creatureQuestRelations, entry, true); } + public QuestRelationResult GetCreatureQuestInvolvedRelations(uint entry) { return GetQuestRelationsFrom(_creatureQuestInvolvedRelations, entry, false); } + public List GetCreatureQuestInvolvedRelationReverseBounds(uint questId) { return _creatureQuestInvolvedRelationsReverse.LookupByKey(questId); } + public QuestPOIData GetQuestPOIData(uint questId) { return _questPOIStorage.LookupByKey(questId); } + public QuestObjective GetQuestObjective(uint questObjectiveId) { return _questObjectives.LookupByKey(questObjectiveId); } + public List GetQuestsForAreaTrigger(uint triggerId) { return _questAreaTriggerStorage.LookupByKey(triggerId); } + public QuestGreeting GetQuestGreeting(TypeId type, uint id) { byte typeIndex; @@ -8421,6 +8286,7 @@ namespace Game return _questGreetingStorage[typeIndex].LookupByKey(id); } + public QuestGreetingLocale GetQuestGreetingLocale(TypeId type, uint id) { byte typeIndex; @@ -8433,12 +8299,28 @@ namespace Game return _questGreetingLocaleStorage[typeIndex].LookupByKey(id); } + public List GetExclusiveQuestGroupBounds(int exclusiveGroupId) { return _exclusiveQuestGroups.LookupByKey(exclusiveGroupId); } + QuestRelationResult GetQuestRelationsFrom(MultiMap map, uint key, bool onlyActive) { return new QuestRelationResult(map.LookupByKey(key), onlyActive); } + public List GetCreatureQuestItemList(uint creatureEntry, Difficulty difficulty) + { + var itr = creatureQuestItemStorage.LookupByKey((creatureEntry, difficulty)); + if (itr != null) + return itr; + + // If there is no data for the difficulty, try to get data for the fallback difficulty + var difficultyEntry = CliDB.DifficultyStorage.LookupByKey(difficulty); + if (difficultyEntry != null) + return GetCreatureQuestItemList(creatureEntry, (Difficulty)difficultyEntry.FallbackDifficultyID); + + return null; + } + //Spells /Skills / Phases public void LoadPhases() { @@ -11039,7 +10921,7 @@ namespace Game Dictionary creatureSummonedDataStorage = new(); Dictionary creatureDataStorage = new(); Dictionary creatureAddonStorage = new(); - MultiMap creatureQuestItemStorage = new(); + MultiMap<(uint, Difficulty), uint> creatureQuestItemStorage = new(); Dictionary creatureTemplateAddonStorage = new(); MultiMap _creatureTemplateSparringStorage = new(); Dictionary creatureMovementOverrides = new(); @@ -11049,8 +10931,6 @@ namespace Game Dictionary cacheVendorItemStorage = new(); Dictionary trainers = new(); Dictionary<(uint creatureId, uint gossipMenuId, uint gossipOptionIndex), uint> _creatureDefaultTrainers = new(); - List[] difficultyEntries = new List[SharedConst.MaxCreatureDifficulties]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate - List[] hasDifficultyEntries = new List[SharedConst.MaxCreatureDifficulties]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate Dictionary npcTextStorage = new(); //GameObject diff --git a/Source/Game/Handlers/NPCHandler.cs b/Source/Game/Handlers/NPCHandler.cs index 248232838..53403ee55 100644 --- a/Source/Game/Handlers/NPCHandler.cs +++ b/Source/Game/Handlers/NPCHandler.cs @@ -458,7 +458,7 @@ namespace Game if (dstPet != null) { CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(dstPet.CreatureId); - if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets())) + if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets(), creatureInfo.GetDifficulty(Difficulty.None))) { SendPetStableResult(StableResult.CantControlExotic); return; @@ -484,7 +484,7 @@ namespace Game } CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(srcPet.CreatureId); - if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets())) + if (creatureInfo == null || !creatureInfo.IsTameable(_player.CanTameExoticPets(), creatureInfo.GetDifficulty(Difficulty.None))) { SendPetStableResult(StableResult.CantControlExotic); return; diff --git a/Source/Game/Handlers/QueryHandler.cs b/Source/Game/Handlers/QueryHandler.cs index 52049f82d..5b21b912a 100644 --- a/Source/Game/Handlers/QueryHandler.cs +++ b/Source/Game/Handlers/QueryHandler.cs @@ -99,31 +99,16 @@ namespace Game CreatureTemplate ci = Global.ObjectMgr.GetCreatureTemplate(packet.CreatureID); if (ci != null) { - if (!WorldConfig.GetBoolValue(WorldCfg.CacheDataQueries)) - ci.InitializeQueryData(); + Difficulty difficulty = _player.GetMap().GetDifficultyID(); - QueryCreatureResponse queryCreatureResponse = ci.QueryData; - - Locale loc = GetSessionDbLocaleIndex(); - if (loc != Locale.enUS) + // Cache only exists for difficulty base + if (!WorldConfig.GetBoolValue(WorldCfg.CacheDataQueries) && difficulty == Difficulty.None) + SendPacket(ci.QueryData[(int)GetSessionDbLocaleIndex()]); + else { - CreatureLocale creatureLocale = Global.ObjectMgr.GetCreatureLocale(ci.Entry); - if (creatureLocale != null) - { - string name = queryCreatureResponse.Stats.Name[0]; - string nameAlt = queryCreatureResponse.Stats.NameAlt[0]; - - ObjectManager.GetLocaleString(creatureLocale.Name, loc, ref name); - ObjectManager.GetLocaleString(creatureLocale.NameAlt, loc, ref nameAlt); - ObjectManager.GetLocaleString(creatureLocale.Title, loc, ref queryCreatureResponse.Stats.Title); - ObjectManager.GetLocaleString(creatureLocale.TitleAlt, loc, ref queryCreatureResponse.Stats.TitleAlt); - - queryCreatureResponse.Stats.Name[0] = name; - queryCreatureResponse.Stats.NameAlt[0] = nameAlt; - } + var response = ci.BuildQueryData(GetSessionDbLocaleIndex(), difficulty); + SendPacket(response); } - - SendPacket(queryCreatureResponse); } else { diff --git a/Source/Game/Loot/LootManager.cs b/Source/Game/Loot/LootManager.cs index 5c194a0e2..952fa84f8 100644 --- a/Source/Game/Loot/LootManager.cs +++ b/Source/Game/Loot/LootManager.cs @@ -97,16 +97,18 @@ namespace Game.Loots uint count = Creature.LoadAndCollectLootIds(out lootIdSet); // Remove real entries and check loot existence - var ctc = Global.ObjectMgr.GetCreatureTemplates(); - foreach (var pair in ctc) + var templates = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var creatureTemplate in templates.Values) { - uint lootid = pair.Value.LootId; - if (lootid != 0) + foreach (var (_, creatureDifficulty) in creatureTemplate.difficultyStorage) { - if (!lootIdSet.Contains(lootid)) - Creature.ReportNonExistingId(lootid, pair.Value.Entry); - else - lootIdSetUsed.Add(lootid); + if (creatureDifficulty.LootID != 0) + { + if (!lootIdSet.Contains(creatureDifficulty.LootID)) + Creature.ReportNonExistingId(creatureDifficulty.LootID, creatureTemplate.Entry); + else + lootIdSetUsed.Add(creatureDifficulty.LootID); + } } } @@ -289,16 +291,18 @@ namespace Game.Loots uint count = Pickpocketing.LoadAndCollectLootIds(out lootIdSet); // Remove real entries and check loot existence - var ctc = Global.ObjectMgr.GetCreatureTemplates(); - foreach (var pair in ctc) + var templates = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var creatureTemplate in templates.Values) { - uint lootid = pair.Value.PickPocketId; - if (lootid != 0) + foreach (var (_, creatureDifficulty) in creatureTemplate.difficultyStorage) { - if (!lootIdSet.Contains(lootid)) - Pickpocketing.ReportNonExistingId(lootid, pair.Value.Entry); - else - lootIdSetUsed.Add(lootid); + if (creatureDifficulty.PickPocketLootID != 0) + { + if (!lootIdSet.Contains(creatureDifficulty.PickPocketLootID)) + Pickpocketing.ReportNonExistingId(creatureDifficulty.PickPocketLootID, creatureTemplate.Entry); + else + lootIdSetUsed.Add(creatureDifficulty.PickPocketLootID); + } } } @@ -377,16 +381,18 @@ namespace Game.Loots uint count = Skinning.LoadAndCollectLootIds(out lootIdSet); // remove real entries and check existence loot - var ctc = Global.ObjectMgr.GetCreatureTemplates(); - foreach (var pair in ctc) + var templates = Global.ObjectMgr.GetCreatureTemplates(); + foreach (var creatureTemplate in templates.Values) { - uint lootid = pair.Value.SkinLootId; - if (lootid != 0) + foreach (var (_, creatureDifficulty) in creatureTemplate.difficultyStorage) { - if (!lootIdSet.Contains(lootid)) - Skinning.ReportNonExistingId(lootid, pair.Value.Entry); - else - lootIdSetUsed.Add(lootid); + if (creatureDifficulty.SkinLootID != 0) + { + if (!lootIdSet.Contains(creatureDifficulty.SkinLootID)) + Skinning.ReportNonExistingId(creatureDifficulty.SkinLootID, creatureTemplate.Entry); + else + lootIdSetUsed.Add(creatureDifficulty.SkinLootID); + } } } diff --git a/Source/Game/Miscellaneous/Formulas.cs b/Source/Game/Miscellaneous/Formulas.cs index a806f52cf..1bb1cd8bd 100644 --- a/Source/Game/Miscellaneous/Formulas.cs +++ b/Source/Game/Miscellaneous/Formulas.cs @@ -147,7 +147,7 @@ namespace Game if (gain != 0 && creature) { // Players get only 10% xp for killing creatures of lower expansion levels than himself - if ((creature.GetCreatureTemplate().GetHealthScalingExpansion() < (int)GetExpansionForLevel(player.GetLevel()))) + if ((creature.GetCreatureDifficulty().GetHealthScalingExpansion() < (int)GetExpansionForLevel(player.GetLevel()))) gain = (uint)Math.Round(gain / 10.0f); if (creature.IsElite()) diff --git a/Source/Game/Networking/Packets/SpellPackets.cs b/Source/Game/Networking/Packets/SpellPackets.cs index e23986ec7..c65ae7621 100644 --- a/Source/Game/Networking/Packets/SpellPackets.cs +++ b/Source/Game/Networking/Packets/SpellPackets.cs @@ -1245,32 +1245,32 @@ namespace Game.Networking.Packets bool GenerateDataCreatureToPlayer(Creature attacker, Player target) { CreatureTemplate creatureTemplate = attacker.GetCreatureTemplate(); - CreatureLevelScaling creatureScaling = creatureTemplate.GetLevelScaling(attacker.GetMap().GetDifficultyID()); + CreatureDifficulty creatureDifficulty = creatureTemplate.GetDifficulty(attacker.GetMap().GetDifficultyID()); TuningType = ContentTuningType.CreatureToPlayerDamage; PlayerLevelDelta = (short)target.m_activePlayerData.ScalingPlayerLevelDelta; PlayerItemLevel = (ushort)target.GetAverageItemLevel(); ScalingHealthItemLevelCurveID = (ushort)target.m_unitData.ScalingHealthItemLevelCurveID; TargetLevel = (byte)target.GetLevel(); - Expansion = (byte)creatureTemplate.HealthScalingExpansion; + Expansion = (byte)creatureDifficulty.HealthScalingExpansion; TargetScalingLevelDelta = (sbyte)attacker.m_unitData.ScalingLevelDelta; - TargetContentTuningID = creatureScaling.ContentTuningID; + TargetContentTuningID = creatureDifficulty.ContentTuningID; return true; } bool GenerateDataPlayerToCreature(Player attacker, Creature target) { CreatureTemplate creatureTemplate = target.GetCreatureTemplate(); - CreatureLevelScaling creatureScaling = creatureTemplate.GetLevelScaling(target.GetMap().GetDifficultyID()); + CreatureDifficulty creatureDifficulty = creatureTemplate.GetDifficulty(target.GetMap().GetDifficultyID()); TuningType = ContentTuningType.PlayerToCreatureDamage; PlayerLevelDelta = (short)attacker.m_activePlayerData.ScalingPlayerLevelDelta; PlayerItemLevel = (ushort)attacker.GetAverageItemLevel(); ScalingHealthItemLevelCurveID = (ushort)target.m_unitData.ScalingHealthItemLevelCurveID; TargetLevel = (byte)target.GetLevel(); - Expansion = (byte)creatureTemplate.HealthScalingExpansion; + Expansion = (byte)creatureDifficulty.HealthScalingExpansion; TargetScalingLevelDelta = (sbyte)target.m_unitData.ScalingLevelDelta; - TargetContentTuningID = creatureScaling.ContentTuningID; + TargetContentTuningID = creatureDifficulty.ContentTuningID; return true; } @@ -1278,15 +1278,15 @@ namespace Game.Networking.Packets { Creature accessor = target.HasScalableLevels() ? target : attacker; CreatureTemplate creatureTemplate = accessor.GetCreatureTemplate(); - CreatureLevelScaling creatureScaling = creatureTemplate.GetLevelScaling(accessor.GetMap().GetDifficultyID()); + CreatureDifficulty creatureDifficulty = creatureTemplate.GetDifficulty(accessor.GetMap().GetDifficultyID()); TuningType = ContentTuningType.CreatureToCreatureDamage; PlayerLevelDelta = 0; PlayerItemLevel = 0; TargetLevel = (byte)target.GetLevel(); - Expansion = (byte)creatureTemplate.HealthScalingExpansion; + Expansion = (byte)creatureDifficulty.HealthScalingExpansion; TargetScalingLevelDelta = (sbyte)accessor.m_unitData.ScalingLevelDelta; - TargetContentTuningID = creatureScaling.ContentTuningID; + TargetContentTuningID = creatureDifficulty.ContentTuningID; return true; } diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index ed4a71ebc..a7c9f4213 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -1402,7 +1402,7 @@ namespace Game.Spells Creature creatureTarget = unitTarget.ToCreature(); if (creatureTarget) { - if (!creatureTarget.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CollideWithMissiles)) + if (!creatureTarget.GetCreatureDifficulty().TypeFlags.HasFlag(CreatureTypeFlags.CollideWithMissiles)) continue; } } @@ -5405,10 +5405,11 @@ namespace Game.Spells if (info.Item1.Type == PetType.Hunter) { CreatureTemplate creatureInfo = Global.ObjectMgr.GetCreatureTemplate(info.Item1.CreatureId); - if (creatureInfo == null || !creatureInfo.IsTameable(playerCaster.CanTameExoticPets())) + CreatureDifficulty creatureDifficulty = creatureInfo.GetDifficulty(Difficulty.None); + if (creatureInfo == null || !creatureInfo.IsTameable(playerCaster.CanTameExoticPets(), creatureDifficulty)) { // if problem in exotic pet - if (creatureInfo != null && creatureInfo.IsTameable(true)) + if (creatureInfo != null && creatureInfo.IsTameable(true, creatureDifficulty)) playerCaster.SendTameFailure(PetTameResult.CantControlExotic); else playerCaster.SendTameFailure(PetTameResult.NoPetAvailable); diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index aa0190166..a3021cefa 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -1819,7 +1819,7 @@ namespace Game.Spells creature.StartPickPocketRefillTimer(); creature._loot = new Loot(creature.GetMap(), creature.GetGUID(), LootType.Pickpocketing, null); - uint lootid = creature.GetCreatureTemplate().PickPocketId; + uint lootid = creature.GetCreatureDifficulty().PickPocketLootID; if (lootid != 0) creature._loot.FillLoot(lootid, LootStorage.Pickpocketing, player, true); @@ -3442,13 +3442,13 @@ namespace Game.Spells Creature creature = unitTarget.ToCreature(); int targetLevel = (int)creature.GetLevelForTarget(m_caster); - SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); + SkillType skill = creature.GetCreatureDifficulty().GetRequiredLootSkill(); creature.SetUnitFlag3(UnitFlags3.AlreadySkinned); creature.SetDynamicFlag(UnitDynFlags.Lootable); Loot loot = new(creature.GetMap(), creature.GetGUID(), LootType.Skinning, null); creature.m_personalLoot[player.GetGUID()] = loot; - loot.FillLoot(creature.GetCreatureTemplate().SkinLootId, LootStorage.Skinning, player, true); + loot.FillLoot(creature.GetCreatureDifficulty().SkinLootID, LootStorage.Skinning, player, true); player.SendLoot(loot); if (!Global.SpellMgr.IsPartOfSkillLine(skill, m_spellInfo.Id)) diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index b4f3f9f03..b01e2fdc8 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -1032,7 +1032,7 @@ namespace Game.Spells if (targetCreature == null) return SpellCastResult.BadTargets; - if (!targetCreature.CanHaveLoot() || !Loots.LootStorage.Pickpocketing.HaveLootFor(targetCreature.GetCreatureTemplate().PickPocketId)) + if (!targetCreature.CanHaveLoot() || !Loots.LootStorage.Pickpocketing.HaveLootFor(targetCreature.GetCreatureDifficulty().PickPocketLootID)) return SpellCastResult.TargetNoPockets; } diff --git a/Source/Game/World/WorldManager.cs b/Source/Game/World/WorldManager.cs index 1a176d66f..735f1a79e 100644 --- a/Source/Game/World/WorldManager.cs +++ b/Source/Game/World/WorldManager.cs @@ -606,12 +606,12 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Creature template addons..."); Global.ObjectMgr.LoadCreatureTemplateAddons(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature template difficulty..."); + Global.ObjectMgr.LoadCreatureTemplateDifficulty(); + Log.outInfo(LogFilter.ServerLoading, "Loading Creature template sparring..."); Global.ObjectMgr.LoadCreatureTemplateSparring(); - 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/Source/Scripts/Spells/Hunter.cs b/Source/Scripts/Spells/Hunter.cs index 39f9b1a35..cb18c068b 100644 --- a/Source/Scripts/Spells/Hunter.cs +++ b/Source/Scripts/Spells/Hunter.cs @@ -454,7 +454,7 @@ namespace Scripts.Spells.Hunter return SpellCastResult.Highlevel; // use SMSG_PET_TAME_FAILURE? - if (!target.GetCreatureTemplate().IsTameable(caster.CanTameExoticPets())) + if (!target.GetCreatureTemplate().IsTameable(caster.CanTameExoticPets(), target.GetCreatureDifficulty())) return SpellCastResult.BadTargets; PetStable petStable = caster.GetPetStable(); diff --git a/Source/Scripts/Spells/Items.cs b/Source/Scripts/Spells/Items.cs index 3c77db3b6..0a09307ab 100644 --- a/Source/Scripts/Spells/Items.cs +++ b/Source/Scripts/Spells/Items.cs @@ -7,7 +7,6 @@ using Game.BattleGrounds; using Game.DataStorage; using Game.Entities; using Game.Loots; -using Game.Networking.Packets; using Game.Scripting; using Game.Spells; using System; @@ -1322,11 +1321,12 @@ namespace Scripts.Spells.Items { Player player = GetCaster().ToPlayer(); Creature creature = GetTarget().ToCreature(); + CreatureDifficulty creatureDifficulty = creature.GetCreatureDifficulty(); // missing lootid has been reported on startup - just return - if (creature.GetCreatureTemplate().SkinLootId == 0) + if (creatureDifficulty.SkinLootID == 0) return; - player.AutoStoreLoot(creature.GetCreatureTemplate().SkinLootId, LootStorage.Skinning, ItemContext.None, true); + player.AutoStoreLoot(creatureDifficulty.SkinLootID, LootStorage.Skinning, ItemContext.None, true); creature.DespawnOrUnsummon(); } }