diff --git a/Source/Framework/Constants/ConditionConst.cs b/Source/Framework/Constants/ConditionConst.cs index 450be5d59..07a899abc 100644 --- a/Source/Framework/Constants/ConditionConst.cs +++ b/Source/Framework/Constants/ConditionConst.cs @@ -105,6 +105,7 @@ namespace Framework.Constants ObjectIdVisibility = 32, SpawnGroup = 33, PlayerCondition = 34, + SkillLineAbility = 35, MaxDbAllowed, ReferenceCondition = MaxDbAllowed, diff --git a/Source/Game/Achievements/AchievementManager.cs b/Source/Game/Achievements/AchievementManager.cs index e32979f4f..0a4b9a9f6 100644 --- a/Source/Game/Achievements/AchievementManager.cs +++ b/Source/Game/Achievements/AchievementManager.cs @@ -1156,10 +1156,14 @@ namespace Game.Achievements ++count; } + DB2HotfixGenerator hotfixes = new(CliDB.AchievementStorage); + // Once Bitten, Twice Shy (10 player) - Icecrown Citadel - AchievementRecord achievement1 = CliDB.AchievementStorage.LookupByKey(4539); - if (achievement1 != null) - achievement1.InstanceID = 631; // Correct map requirement (currently has Ulduar); 6.0.3 note - it STILL has ulduar requirement + // Correct map requirement (currently has Ulduar); 6.0.3 note - it STILL has ulduar requirement + hotfixes.ApplyHotfix(4539, achievement => + { + achievement.InstanceID = 631; + }); Log.outInfo(LogFilter.ServerLoading, "Loaded {0} achievement references in {1} ms.", count, Time.GetMSTimeDiffToNow(oldMSTime)); } diff --git a/Source/Game/Conditions/Condition.cs b/Source/Game/Conditions/Condition.cs index 38398bee1..0f9d7d669 100644 --- a/Source/Game/Conditions/Condition.cs +++ b/Source/Game/Conditions/Condition.cs @@ -739,30 +739,22 @@ namespace Game.Conditions public class ConditionSourceInfo { - public ConditionSourceInfo(WorldObject target0, WorldObject target1 = null, WorldObject target2 = null) - { - mConditionTargets[0] = target0; - mConditionTargets[1] = target1; - mConditionTargets[2] = target2; - if (target0 != null) - mConditionMap = target0.GetMap(); - else if (target1 != null) - mConditionMap = target1.GetMap(); - else if (target2 != null) - mConditionMap = target2.GetMap(); - else - mConditionMap = null; - mLastFailedCondition = null; - } - - public ConditionSourceInfo(Map map) - { - mConditionMap = map; - mLastFailedCondition = null; - } - public WorldObject[] mConditionTargets = new WorldObject[SharedConst.MaxConditionTargets]; // an array of targets available for conditions public Map mConditionMap; public Condition mLastFailedCondition; + + public ConditionSourceInfo(WorldObject target0, WorldObject target1 = null, WorldObject target2 = null) + { + mConditionTargets = [target0, target1, target2]; + + var target = target0 ?? target1 ?? target2; + if (target != null) + mConditionMap = target.GetMap(); + } + + public ConditionSourceInfo(Map map) + { + mConditionMap = map; + } } -} +} \ No newline at end of file diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index dfa161575..3554b198f 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -1120,6 +1120,21 @@ namespace Game } break; } + case ConditionSourceType.SkillLineAbility: + { + var skillLineAbility = CliDB.SkillLineAbilityStorage.LookupByKey(cond.SourceEntry); + if (skillLineAbility == null) + { + Log.outError(LogFilter.Sql, $"{cond} SourceEntry in `condition` table, does not exist in SkillLineAbility.db2, ignoring."); + return false; + } + if (skillLineAbility.AcquireMethod != SkillLineAbilityAcquireMethod.LearnedOrAutomaticCharLevel) + { + Log.outError(LogFilter.Sql, $"{cond} in SkillLineAbility.db2 does not have AcquireMethod = {SkillLineAbilityAcquireMethod.LearnedOrAutomaticCharLevel} (LearnedOrAutomaticCharLevel), ignoring."); + return false; + } + break; + } case ConditionSourceType.GossipMenu: case ConditionSourceType.GossipMenuOption: case ConditionSourceType.SmartEvent: @@ -2712,8 +2727,8 @@ namespace Game } case WorldStateExpressionValueType.WorldState: { - uint worldStateId = buffer.ReadUInt32(); - value = Global.WorldStateMgr.GetValue((int)worldStateId, map); + int worldStateId = buffer.ReadInt32(); + value = Global.WorldStateMgr.GetValue(worldStateId, map); break; } case WorldStateExpressionValueType.Function: diff --git a/Source/Game/DataStorage/DB2HotfixGenerator.cs b/Source/Game/DataStorage/DB2HotfixGenerator.cs new file mode 100644 index 000000000..e739a4aec --- /dev/null +++ b/Source/Game/DataStorage/DB2HotfixGenerator.cs @@ -0,0 +1,52 @@ +// Copyright (c) CypherCore All rights reserved. +// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +namespace Game.DataStorage +{ + class DB2HotfixGenerator where T : new() + { + DB6Storage _storage; + uint _count; + + public DB2HotfixGenerator(DB6Storage storage) + { + _storage = storage; + } + + public void ApplyHotfix(uint id, Action fixer, bool notifyClient = false) { ApplyHotfix([id, 1], fixer, notifyClient); } + + public uint GetAppliedHotfixesCount() { return _count; } + + void ApplyHotfix(Span ids, Action fixer, bool notifyClient) + { + foreach (uint id in ids) + { + T entry = _storage.LookupByKey(id); + if (entry == null) + { + LogMissingRecord(_storage.GetName(), id); + continue; + } + + fixer(entry); + ++_count; + + if (notifyClient) + AddClientHotfix(_storage.GetTableHash(), id); + } + } + + void LogMissingRecord(string storageName, uint recordId) + { + Log.outError(LogFilter.Server, $"Hotfix specified for {storageName} row id {recordId} which does not exist"); + } + + void AddClientHotfix(uint tableHash, uint recordId) + { + Global.DB2Mgr.InsertNewHotfix(tableHash, recordId); + } + } +} diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index ea02c3b26..666c30c72 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -801,6 +801,7 @@ namespace Game.DataStorage push.Records.Add(hotfixRecord); push.AvailableLocalesMask |= hotfixRecord.AvailableLocalesMask; + _maxHotfixId = Math.Max(_maxHotfixId, id); deletedRecords[(tableHash, recordId)] = status == HotfixRecord.Status.RecordRemoved; ++count; @@ -960,6 +961,20 @@ namespace Game.DataStorage return AnimationDataStorage.GetNumRows(); } + public void InsertNewHotfix(uint tableHash, uint recordId) + { + HotfixRecord hotfixRecord = new(); + hotfixRecord.TableHash = tableHash; + hotfixRecord.RecordID = (int)recordId; + hotfixRecord.ID.PushID = (int)++_maxHotfixId; + hotfixRecord.ID.UniqueID = RandomHelper.Rand32(); + hotfixRecord.AvailableLocalesMask = 0xDFF; + + HotfixPush push = _hotfixData[hotfixRecord.ID.PushID]; + push.Records.Add(hotfixRecord); + push.AvailableLocalesMask |= hotfixRecord.AvailableLocalesMask; + } + public List GetAreasForGroup(uint areaGroupId) { return _areaGroupMembers.LookupByKey(areaGroupId); @@ -2479,6 +2494,8 @@ namespace Game.DataStorage public static byte[] HordeTaxiNodesMask; public static byte[] AllianceTaxiNodesMask; public static Dictionary TaxiPathNodesByPath = new(); + + int _maxHotfixId; } class UiMapBounds diff --git a/Source/Game/Entities/Player/Player.Spells.cs b/Source/Game/Entities/Player/Player.Spells.cs index 5178ebc3f..3005e562b 100644 --- a/Source/Game/Entities/Player/Player.Spells.cs +++ b/Source/Game/Entities/Player/Player.Spells.cs @@ -1664,8 +1664,10 @@ namespace Game.Entities case SkillLineAbilityAcquireMethod.AutomaticCharLevel: break; case SkillLineAbilityAcquireMethod.LearnedOrAutomaticCharLevel: - if (!ability.HasFlag(SkillLineAbilityFlags.CanFallbackToLearnedOnSkillLearn) || - !spellInfo.MeetsFutureSpellPlayerCondition(this)) + // Treat as AutomaticCharLevel when conditions are met, otherwise treat it as Learned (trainer or quest) + if (spellInfo.ShowFutureSpellPlayerConditionID != 0 && !ConditionManager.IsPlayerMeetingCondition(this, spellInfo.ShowFutureSpellPlayerConditionID)) + continue; + if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SkillLineAbility, ability.Id, this)) continue; break; default: diff --git a/Source/Game/Spells/SpellManager.cs b/Source/Game/Spells/SpellManager.cs index 6c183eac1..4612aa6d2 100644 --- a/Source/Game/Spells/SpellManager.cs +++ b/Source/Game/Spells/SpellManager.cs @@ -1655,10 +1655,10 @@ namespace Game.Entities mSkillLineAbilityMap.Clear(); - foreach (var skill in CliDB.SkillLineAbilityStorage.Values) - mSkillLineAbilityMap.Add(skill.Spell, skill); + foreach (var (_, skillLineAbility) in CliDB.SkillLineAbilityStorage) + mSkillLineAbilityMap.Add(skillLineAbility.Spell, skillLineAbility); - Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SkillLineAbility MultiMap Data in {1} ms", mSkillLineAbilityMap.Count, Time.GetMSTimeDiffToNow(oldMSTime)); + Log.outInfo(LogFilter.ServerLoading, $"Loaded {mSkillLineAbilityMap.Count} SkillLineAbility MultiMap Data in {Time.GetMSTimeDiffToNow(oldMSTime)} ms"); } public void LoadSpellPetAuras() @@ -4515,15 +4515,19 @@ namespace Game.Entities spellInfo.MaxAffectedTargets = 1; } - SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(121); - if (properties != null) + DB2HotfixGenerator summonProperties = new(CliDB.SummonPropertiesStorage); + summonProperties.ApplyHotfix(121, properties => + { properties.Title = SummonTitle.Totem; - properties = CliDB.SummonPropertiesStorage.LookupByKey(647); // 52893 - if (properties != null) + }); + summonProperties.ApplyHotfix(647, properties => // 52893 + { properties.Title = SummonTitle.Totem; - properties = CliDB.SummonPropertiesStorage.LookupByKey(628); - if (properties != null) // Hungry Plaguehound + }); + summonProperties.ApplyHotfix(628, properties => // Hungry Plaguehound + { properties.Control = SummonCategory.Pet; + }); Log.outInfo(LogFilter.ServerLoading, "Loaded SpellInfo corrections in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime)); }