From 0d5c0b572dc14779131b51d32426610b3b0fb304 Mon Sep 17 00:00:00 2001 From: Hondacrx Date: Mon, 24 Feb 2025 11:01:12 -0500 Subject: [PATCH] Core/Achievements: Reduce the list of criteria types updated on login * Implemented CriteriaType::ReachMaxLevel * Corrected implementations for CriteriaType::ReachRenownLevel and CriteriaType::LearnTaxiNode Port From (https://github.com/TrinityCore/TrinityCore/commit/6da1679cbfdc56cd5f1cab7148f645916bccecc1) --- .../Framework/Constants/AchievementConst.cs | 4 +- .../Game/Achievements/AchievementManager.cs | 4 +- Source/Game/Achievements/CriteriaHandler.cs | 1268 +++++++++-------- Source/Game/Entities/Player/Player.cs | 2 + .../Quest/QuestObjectiveCriteriaManager.cs | 4 +- 5 files changed, 676 insertions(+), 606 deletions(-) diff --git a/Source/Framework/Constants/AchievementConst.cs b/Source/Framework/Constants/AchievementConst.cs index 7126fbcc2..ccb0d4053 100644 --- a/Source/Framework/Constants/AchievementConst.cs +++ b/Source/Framework/Constants/AchievementConst.cs @@ -613,7 +613,7 @@ namespace Framework.Constants TotalFactionsEncountered = 89, // Total factions encountered LootAnyItem = 90, // Loot any item ObtainAnyItem = 91, // Obtain any item - AnyoneTriggerGameEventScenario = 92, /*NYI*/ // Anyone will Trigger game event "{GameEvents}" (Scenario Only) + AnyoneTriggerGameEventScenario = 92, // Anyone will Trigger game event "{GameEvents}" (Scenario Only) RollAnyNeed = 93, // Roll any number on need RollAnyGreed = 94, // Roll any number on greed ReleasedSpirit = 95, /*NYI*/ // Released Spirit @@ -742,7 +742,7 @@ namespace Framework.Constants CompleteAnyReplayQuest = 218, // Complete Any Replay Quest BuyItemsFromVendors = 219, // Buy items from vendors SellItemsToVendors = 220, // Sell items to vendors - ReachMaxLevel = 221, /*NYI*/ // Reach Max Level + ReachMaxLevel = 221, // Reach Max Level MemorizeSpell = 222, /*NYI*/ // Memorize Spell "{Spell}" LearnTransmogIllusion = 223, /*NYI*/ // Learn Transmog Illusion LearnAnyTransmogIllusion = 224, /*NYI*/ // Learn Any Transmog Illusion diff --git a/Source/Game/Achievements/AchievementManager.cs b/Source/Game/Achievements/AchievementManager.cs index ee7e22f0e..e32979f4f 100644 --- a/Source/Game/Achievements/AchievementManager.cs +++ b/Source/Game/Achievements/AchievementManager.cs @@ -31,8 +31,8 @@ namespace Game.Achievements public void CheckAllAchievementCriteria(Player referencePlayer) { // suppress sending packets - for (CriteriaType i = 0; i < CriteriaType.Count; ++i) - UpdateCriteria(i, 0, 0, 0, null, referencePlayer); + foreach (CriteriaType criteriaType in CriteriaManager.GetRetroactivelyUpdateableCriteriaTypes()) + UpdateCriteria(criteriaType, 0, 0, 0, null, referencePlayer); } public bool HasAchieved(uint achievementId) diff --git a/Source/Game/Achievements/CriteriaHandler.cs b/Source/Game/Achievements/CriteriaHandler.cs index 37c0f7223..4714ed301 100644 --- a/Source/Game/Achievements/CriteriaHandler.cs +++ b/Source/Game/Achievements/CriteriaHandler.cs @@ -144,6 +144,8 @@ namespace Game.Achievements case CriteriaType.CompleteAnyReplayQuest: case CriteriaType.BuyItemsFromVendors: case CriteriaType.SellItemsToVendors: + case CriteriaType.ReachMaxLevel: + case CriteriaType.LearnTaxiNode: SetCriteriaProgress(criteria, 1, referencePlayer, ProgressType.Accumulate); break; // std case: increment at miscValue1 @@ -815,6 +817,8 @@ namespace Game.Achievements case CriteriaType.BuyItemsFromVendors: case CriteriaType.SellItemsToVendors: case CriteriaType.GainLevels: + case CriteriaType.ReachRenownLevel: + case CriteriaType.LearnTaxiNode: return progress.Counter >= requiredAmount; case CriteriaType.EarnAchievement: case CriteriaType.CompleteQuest: @@ -833,6 +837,7 @@ namespace Game.Achievements case CriteriaType.PrestigeLevelIncrease: case CriteriaType.ActivelyReachLevel: case CriteriaType.CollectTransmogSetFromGroup: + case CriteriaType.ReachMaxLevel: case CriteriaType.EnterTopLevelArea: case CriteriaType.LeaveTopLevelArea: return progress.Counter >= 1; @@ -1173,6 +1178,7 @@ namespace Game.Achievements return false; break; case CriteriaType.CurrencyGained: + case CriteriaType.ReachRenownLevel: if (miscValue1 == 0 || miscValue2 == 0 || (long)miscValue2 < 0 || miscValue1 != criteria.Entry.Asset) return false; @@ -1229,6 +1235,10 @@ namespace Game.Achievements if (miscValue1 == 0 || miscValue1 != criteria.Entry.Asset) return false; break; + case CriteriaType.LearnTaxiNode: + if (miscValue1 != criteria.Entry.Asset) + return false; + break; default: break; } @@ -4086,6 +4096,62 @@ namespace Game.Achievements return _criteriaModifiers.LookupByKey(modifierTreeId); } + public static CriteriaType[] GetRetroactivelyUpdateableCriteriaTypes() => + [ + CriteriaType.CompleteResearchProject, + CriteriaType.CompleteAnyResearchProject, + CriteriaType.ReachLevel, + CriteriaType.SkillRaised, + CriteriaType.CompleteQuestsCount, + CriteriaType.CompleteAnyDailyQuestPerDay, + CriteriaType.CompleteQuestsInZone, + CriteriaType.CompleteQuest, + CriteriaType.LearnOrKnowSpell, + CriteriaType.AcquireItem, + CriteriaType.EarnPersonalArenaRating, + CriteriaType.AchieveSkillStep, + CriteriaType.RevealWorldMapOverlay, + //CriteriaType.EarnTitle, /*NYI*/ + CriteriaType.BankSlotsPurchased, + CriteriaType.ReputationGained, + CriteriaType.TotalExaltedFactions, + //CriteriaType.CompleteQuestsInSort, /*NYI*/ + CriteriaType.LearnSpellFromSkillLine, + CriteriaType.MostMoneyOwned, + CriteriaType.TotalReveredFactions, + CriteriaType.TotalHonoredFactions, + CriteriaType.TotalFactionsEncountered, + //CriteriaType.AccountKnownPet, /*NYI*/ + CriteriaType.LearnTradeskillSkillLine, + CriteriaType.HonorableKills, + //CriteriaType.GuildBankTabsPurchased, /*NYI*/ + //CriteriaType.EarnGuildAchievementPoints, /*NYI*/ + //CriteriaType.EarnBattlegroundRating, /*NYI*/ + //CriteriaType.GuildTabardCreated, /*NYI*/ + CriteriaType.LearnedNewPet, + CriteriaType.UniquePetsOwned, + //CriteriaType.UpgradeGarrison, /*NYI*/ + //CriteriaType.AcquireGarrison, /*NYI*/ + //CriteriaType.LearnGarrisonBlueprint, /*NYI*/ + //CriteriaType.LearnGarrisonSpecialization, /*NYI*/ + //CriteriaType.LearnToy, /*NYI*/ // Learn Toy "{Item}" + //CriteriaType.LearnAnyToy, /*NYI*/ // Learn Any Toy + //CriteriaType.LearnTransmog, /*NYI*/ + CriteriaType.HonorLevelIncrease, + //CriteriaType.AccountHonorLevelReached, /*NYI*/ + CriteriaType.ReachMaxLevel, + //CriteriaType.MemorizeSpell, /*NYI*/ + //CriteriaType.LearnTransmogIllusion, /*NYI*/ + //CriteriaType.MythicPlusRatingAttained, /*NYI*/ + //CriteriaType.MythicPlusDisplaySeasonEnded, /*NYI*/ + //CriteriaType.CompleteTrackingQuest, /*NYI*/ + //CriteriaType.WarbandBankTabPurchased, /*NYI*/ + CriteriaType.LearnTaxiNode, + CriteriaType.EarnAchievementPoints, + CriteriaType.BattlePetAchievementPointsEarned, + CriteriaType.EarnAchievement // criteria possibly completed by retroactive scan, must be last + ]; + bool IsCriteriaTypeStoredByAsset(CriteriaType type) { switch (type) @@ -4123,6 +4189,8 @@ namespace Game.Achievements case CriteriaType.LandTargetedSpellOnTarget: case CriteriaType.LearnTradeskillSkillLine: case CriteriaType.DefeatDungeonEncounter: + case CriteriaType.LearnToy: + case CriteriaType.LearnAnyTransmog: return true; default: return false; @@ -4215,627 +4283,627 @@ namespace Game.Achievements } public class ModifierTreeNode +{ + public ModifierTreeRecord Entry; + public List Children = new(); +} + +public class Criteria +{ + public uint Id; + public CriteriaRecord Entry; + public ModifierTreeNode Modifier; + public CriteriaFlagsCu FlagsCu; +} + +public class CriteriaTree +{ + public uint Id; + public CriteriaTreeRecord Entry; + public AchievementRecord Achievement; + public ScenarioStepRecord ScenarioStep; + public QuestObjective QuestObjective; + public Criteria Criteria; + public List Children = new(); +} + +public class CriteriaProgress +{ + public ulong Counter; + public long Date; // latest update time. + public ObjectGuid PlayerGUID; // GUID of the player that completed this criteria (guild achievements) + public bool Changed; +} + +[StructLayout(LayoutKind.Explicit)] +public class CriteriaData +{ + [FieldOffset(0)] + public CriteriaDataType DataType; + + [FieldOffset(4)] + public CreatureStruct Creature; + + [FieldOffset(4)] + public ClassRaceStruct ClassRace; + + [FieldOffset(4)] + public HealthStruct Health; + + [FieldOffset(4)] + public AuraStruct Aura; + + [FieldOffset(4)] + public ValueStruct Value; + + [FieldOffset(4)] + public LevelStruct Level; + + [FieldOffset(4)] + public GenderStruct Gender; + + [FieldOffset(4)] + public MapPlayersStruct MapPlayers; + + [FieldOffset(4)] + public TeamStruct TeamId; + + [FieldOffset(4)] + public DrunkStruct Drunk; + + [FieldOffset(4)] + public HolidayStruct Holiday; + + [FieldOffset(4)] + public BgLossTeamScoreStruct BattlegroundScore; + + [FieldOffset(4)] + public EquippedItemStruct EquippedItem; + + [FieldOffset(4)] + public MapIdStruct MapId; + + [FieldOffset(4)] + public KnownTitleStruct KnownTitle; + + [FieldOffset(4)] + public GameEventStruct GameEvent; + + [FieldOffset(4)] + public ItemQualityStruct itemQuality; + + [FieldOffset(4)] + public RawStruct Raw; + + [FieldOffset(12)] + public uint ScriptId; + + public CriteriaData() { - public ModifierTreeRecord Entry; - public List Children = new(); + DataType = CriteriaDataType.None; + + Raw.Value1 = 0; + Raw.Value2 = 0; + ScriptId = 0; } - public class Criteria + public CriteriaData(CriteriaDataType _dataType, uint _value1, uint _value2, uint _scriptId) { - public uint Id; - public CriteriaRecord Entry; - public ModifierTreeNode Modifier; - public CriteriaFlagsCu FlagsCu; + DataType = _dataType; + + Raw.Value1 = _value1; + Raw.Value2 = _value2; + ScriptId = _scriptId; } - public class CriteriaTree + public bool IsValid(Criteria criteria) { - public uint Id; - public CriteriaTreeRecord Entry; - public AchievementRecord Achievement; - public ScenarioStepRecord ScenarioStep; - public QuestObjective QuestObjective; - public Criteria Criteria; - public List Children = new(); - } - - public class CriteriaProgress - { - public ulong Counter; - public long Date; // latest update time. - public ObjectGuid PlayerGUID; // GUID of the player that completed this criteria (guild achievements) - public bool Changed; - } - - [StructLayout(LayoutKind.Explicit)] - public class CriteriaData - { - [FieldOffset(0)] - public CriteriaDataType DataType; - - [FieldOffset(4)] - public CreatureStruct Creature; - - [FieldOffset(4)] - public ClassRaceStruct ClassRace; - - [FieldOffset(4)] - public HealthStruct Health; - - [FieldOffset(4)] - public AuraStruct Aura; - - [FieldOffset(4)] - public ValueStruct Value; - - [FieldOffset(4)] - public LevelStruct Level; - - [FieldOffset(4)] - public GenderStruct Gender; - - [FieldOffset(4)] - public MapPlayersStruct MapPlayers; - - [FieldOffset(4)] - public TeamStruct TeamId; - - [FieldOffset(4)] - public DrunkStruct Drunk; - - [FieldOffset(4)] - public HolidayStruct Holiday; - - [FieldOffset(4)] - public BgLossTeamScoreStruct BattlegroundScore; - - [FieldOffset(4)] - public EquippedItemStruct EquippedItem; - - [FieldOffset(4)] - public MapIdStruct MapId; - - [FieldOffset(4)] - public KnownTitleStruct KnownTitle; - - [FieldOffset(4)] - public GameEventStruct GameEvent; - - [FieldOffset(4)] - public ItemQualityStruct itemQuality; - - [FieldOffset(4)] - public RawStruct Raw; - - [FieldOffset(12)] - public uint ScriptId; - - public CriteriaData() + if (DataType >= CriteriaDataType.Max) { - DataType = CriteriaDataType.None; - - Raw.Value1 = 0; - Raw.Value2 = 0; - ScriptId = 0; - } - - public CriteriaData(CriteriaDataType _dataType, uint _value1, uint _value2, uint _scriptId) - { - DataType = _dataType; - - Raw.Value1 = _value1; - Raw.Value2 = _value2; - ScriptId = _scriptId; - } - - public bool IsValid(Criteria criteria) - { - if (DataType >= CriteriaDataType.Max) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` for criteria (Entry: {0}) has wrong data type ({1}), ignored.", criteria.Id, DataType); - return false; - } - - switch (criteria.Entry.Type) - { - case CriteriaType.KillCreature: - case CriteriaType.KillAnyCreature: - case CriteriaType.WinBattleground: - case CriteriaType.MaxDistFallenWithoutDying: - case CriteriaType.CompleteQuest: // only hardcoded list - case CriteriaType.CastSpell: - case CriteriaType.WinAnyRankedArena: - case CriteriaType.DoEmote: - case CriteriaType.KillPlayer: - case CriteriaType.WinDuel: - case CriteriaType.GetLootByType: - case CriteriaType.LandTargetedSpellOnTarget: - case CriteriaType.BeSpellTarget: - case CriteriaType.GainAura: - case CriteriaType.EquipItemInSlot: - case CriteriaType.RollNeed: - case CriteriaType.RollGreed: - case CriteriaType.TrackedWorldStateUIModified: - case CriteriaType.EarnHonorableKill: - case CriteriaType.CompleteDailyQuest: // only Children's Week achievements - case CriteriaType.UseItem: // only Children's Week achievements - case CriteriaType.DeliveredKillingBlow: - case CriteriaType.ReachLevel: - case CriteriaType.Login: - case CriteriaType.LootAnyItem: - case CriteriaType.ObtainAnyItem: - break; - default: - if (DataType != CriteriaDataType.Script) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` has data for non-supported criteria type (Entry: {0} Type: {1}), ignored.", criteria.Id, (CriteriaType)criteria.Entry.Type); - return false; - } - break; - } - - switch (DataType) - { - case CriteriaDataType.None: - case CriteriaDataType.InstanceScript: - return true; - case CriteriaDataType.TCreature: - if (Creature.Id == 0 || Global.ObjectMgr.GetCreatureTemplate(Creature.Id) == null) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_CREATURE ({2}) has non-existing creature id in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Creature.Id); - return false; - } - return true; - case CriteriaDataType.TPlayerClassRace: - if (ClassRace.ClassId == 0 && ClassRace.RaceId == 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) must not have 0 in either value field, ignored.", - criteria.Id, criteria.Entry.Type, DataType); - return false; - } - if (ClassRace.ClassId != 0 && ((1 << (int)(ClassRace.ClassId - 1)) & (int)Class.ClassMaskAllPlayable) == 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) has non-existing class in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, ClassRace.ClassId); - return false; - } - if (!RaceMask.AllPlayable.HasRace((Race)ClassRace.RaceId)) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) has non-existing race in value2 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, ClassRace.RaceId); - return false; - } - return true; - case CriteriaDataType.TPlayerLessHealth: - if (Health.Percent < 1 || Health.Percent > 100) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_PLAYER_LESS_HEALTH ({2}) has wrong percent value in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Health.Percent); - return false; - } - return true; - case CriteriaDataType.SAura: - case CriteriaDataType.TAura: - { - SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(Aura.SpellId, Difficulty.None); - if (spellEntry == null) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has wrong spell id in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Aura.SpellId); - return false; - } - if (spellEntry.GetEffects().Count <= Aura.EffectIndex) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has wrong spell effect index in value2 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Aura.EffectIndex); - return false; - } - if (spellEntry.GetEffect(Aura.EffectIndex).ApplyAuraName == 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has non-aura spell effect (ID: {3} Effect: {4}), ignores.", - criteria.Id, criteria.Entry.Type, DataType, Aura.SpellId, Aura.EffectIndex); - return false; - } - return true; - } - case CriteriaDataType.Value: - if (Value.ComparisonType >= (int)ComparisionType.Max) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_VALUE ({2}) has wrong ComparisionType in value2 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Value.ComparisonType); - return false; - } - return true; - case CriteriaDataType.TLevel: - if (Level.Min > SharedConst.GTMaxLevel) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_LEVEL ({2}) has wrong minlevel in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Level.Min); - return false; - } - return true; - case CriteriaDataType.TGender: - if (Gender.Gender > (int)Framework.Constants.Gender.None) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_GENDER ({2}) has wrong gender in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Gender.Gender); - return false; - } - return true; - case CriteriaDataType.Script: - if (ScriptId == 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_SCRIPT ({2}) does not have ScriptName set, ignored.", - criteria.Id, criteria.Entry.Type, DataType); - return false; - } - return true; - case CriteriaDataType.MapPlayerCount: - if (MapPlayers.MaxCount <= 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT ({2}) has wrong max players count in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, MapPlayers.MaxCount); - return false; - } - return true; - case CriteriaDataType.TTeam: - if (TeamId.Team != (int)Team.Alliance && TeamId.Team != (int)Team.Horde) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_TEAM ({2}) has unknown team in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, TeamId.Team); - return false; - } - return true; - case CriteriaDataType.SDrunk: - if (Drunk.State >= 4) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_DRUNK ({2}) has unknown drunken state in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Drunk.State); - return false; - } - return true; - case CriteriaDataType.Holiday: - if (!CliDB.HolidaysStorage.ContainsKey(Holiday.Id)) - { - Log.outError(LogFilter.Sql, "Table `criteria_data`(Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_HOLIDAY ({2}) has unknown holiday in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, Holiday.Id); - return false; - } - return true; - case CriteriaDataType.GameEvent: - { - var events = Global.GameEventMgr.GetEventMap(); - if (GameEvent.Id < 1 || GameEvent.Id >= events.Length) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_GAME_EVENT ({2}) has unknown game_event in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, GameEvent.Id); - return false; - } - return true; - } - case CriteriaDataType.BgLossTeamScore: - return true; // not check correctness node indexes - case CriteriaDataType.SEquippedItem: - if (EquippedItem.ItemQuality >= (uint)ItemQuality.Max) - { - Log.outError(LogFilter.Sql, "Table `achievement_criteria_requirement` (Entry: {0} Type: {1}) for requirement ACHIEVEMENT_CRITERIA_REQUIRE_S_EQUIPED_ITEM ({2}) has unknown quality state in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, EquippedItem.ItemQuality); - return false; - } - return true; - case CriteriaDataType.MapId: - if (!CliDB.MapStorage.ContainsKey(MapId.Id)) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_MAP_ID ({2}) contains an unknown map entry in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, MapId.Id); - return false; - } - return true; - case CriteriaDataType.SPlayerClassRace: - if (ClassRace.ClassId == 0 && ClassRace.RaceId == 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) must not have 0 in either value field, ignored.", - criteria.Id, criteria.Entry.Type, DataType); - return false; - } - if (ClassRace.ClassId != 0 && ((1 << (int)(ClassRace.ClassId - 1)) & (int)Class.ClassMaskAllPlayable) == 0) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) has non-existing class in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, ClassRace.ClassId); - return false; - } - if (ClassRace.RaceId != 0 && !RaceMask.AllPlayable.HasRace((Race)ClassRace.RaceId)) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) has non-existing race in value2 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, ClassRace.RaceId); - return false; - } - return true; - case CriteriaDataType.SKnownTitle: - if (!CliDB.CharTitlesStorage.ContainsKey(KnownTitle.Id)) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_KNOWN_TITLE ({2}) contains an unknown title_id in value1 ({3}), ignore.", - criteria.Id, criteria.Entry.Type, DataType, KnownTitle.Id); - return false; - } - return true; - case CriteriaDataType.SItemQuality: - if (itemQuality.Quality >= (uint)ItemQuality.Max) - { - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_ITEM_QUALITY ({2}) contains an unknown quality state value in value1 ({3}), ignored.", - criteria.Id, criteria.Entry.Type, DataType, itemQuality.Quality); - return false; - } - return true; - default: - Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) contains data of a non-supported data type ({2}), ignored.", criteria.Id, criteria.Entry.Type, DataType); - return false; - } - } - - public bool Meets(uint criteriaId, Player source, WorldObject target, uint miscValue1 = 0, uint miscValue2 = 0) - { - switch (DataType) - { - case CriteriaDataType.None: - return true; - case CriteriaDataType.TCreature: - if (target == null || !target.IsTypeId(TypeId.Unit)) - return false; - return target.GetEntry() == Creature.Id; - case CriteriaDataType.TPlayerClassRace: - if (target == null || !target.IsTypeId(TypeId.Player)) - return false; - if (ClassRace.ClassId != 0 && ClassRace.ClassId != (uint)target.ToPlayer().GetClass()) - return false; - if (ClassRace.RaceId != 0 && ClassRace.RaceId != (uint)target.ToPlayer().GetRace()) - return false; - return true; - case CriteriaDataType.SPlayerClassRace: - if (source == null || !source.IsTypeId(TypeId.Player)) - return false; - if (ClassRace.ClassId != 0 && ClassRace.ClassId != (uint)source.ToPlayer().GetClass()) - return false; - if (ClassRace.RaceId != 0 && ClassRace.RaceId != (uint)source.ToPlayer().GetRace()) - return false; - return true; - case CriteriaDataType.TPlayerLessHealth: - if (target == null || !target.IsTypeId(TypeId.Player)) - return false; - return !target.ToPlayer().HealthAbovePct((int)Health.Percent); - case CriteriaDataType.SAura: - return source.HasAuraEffect(Aura.SpellId, (byte)Aura.EffectIndex); - case CriteriaDataType.TAura: - { - if (target == null) - return false; - Unit unitTarget = target.ToUnit(); - if (unitTarget == null) - return false; - return unitTarget.HasAuraEffect(Aura.SpellId, Aura.EffectIndex); - } - case CriteriaDataType.Value: - return MathFunctions.CompareValues((ComparisionType)Value.ComparisonType, miscValue1, Value.Value); - case CriteriaDataType.TLevel: - if (target == null) - return false; - return target.GetLevelForTarget(source) >= Level.Min; - case CriteriaDataType.TGender: - { - if (target == null) - return false; - Unit unitTarget = target.ToUnit(); - if (unitTarget == null) - return false; - return unitTarget.GetGender() == (Gender)Gender.Gender; - } - case CriteriaDataType.Script: - { - Unit unitTarget = null; - if (target != null) - unitTarget = target.ToUnit(); - return Global.ScriptMgr.OnCriteriaCheck(ScriptId, source.ToPlayer(), unitTarget.ToUnit()); - } - case CriteriaDataType.MapPlayerCount: - return source.GetMap().GetPlayersCountExceptGMs() <= MapPlayers.MaxCount; - case CriteriaDataType.TTeam: - if (target == null || !target.IsTypeId(TypeId.Player)) - return false; - return target.ToPlayer().GetTeam() == (Team)TeamId.Team; - case CriteriaDataType.SDrunk: - return Player.GetDrunkenstateByValue(source.GetDrunkValue()) >= (DrunkenState)Drunk.State; - case CriteriaDataType.Holiday: - return Global.GameEventMgr.IsHolidayActive((HolidayIds)Holiday.Id); - case CriteriaDataType.GameEvent: - return Global.GameEventMgr.IsEventActive((ushort)GameEvent.Id); - case CriteriaDataType.BgLossTeamScore: - { - Battleground bg = source.GetBattleground(); - if (bg == null) - return false; - - int score = (int)bg.GetTeamScore(bg.GetPlayerTeam(source.GetGUID()) == Team.Alliance ? BattleGroundTeamId.Horde : BattleGroundTeamId.Alliance); - return score >= BattlegroundScore.Min && score <= BattlegroundScore.Max; - } - case CriteriaDataType.InstanceScript: - { - if (!source.IsInWorld) - return false; - Map map = source.GetMap(); - if (!map.IsDungeon()) - { - Log.outError(LogFilter.Achievement, "Achievement system call AchievementCriteriaDataType.InstanceScript ({0}) for achievement criteria {1} for non-dungeon/non-raid map {2}", - CriteriaDataType.InstanceScript, criteriaId, map.GetId()); - return false; - } - InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); - if (instance == null) - { - Log.outError(LogFilter.Achievement, "Achievement system call criteria_data_INSTANCE_SCRIPT ({0}) for achievement criteria {1} for map {2} but map does not have a instance script", - CriteriaDataType.InstanceScript, criteriaId, map.GetId()); - return false; - } - - Unit unitTarget = null; - if (target != null) - unitTarget = target.ToUnit(); - return instance.CheckAchievementCriteriaMeet(criteriaId, source, unitTarget, miscValue1); - } - case CriteriaDataType.SEquippedItem: - { - Criteria entry = Global.CriteriaMgr.GetCriteria(criteriaId); - - uint itemId = entry.Entry.Type == CriteriaType.EquipItemInSlot ? miscValue2 : miscValue1; - ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); - if (itemTemplate == null) - return false; - return itemTemplate.GetBaseItemLevel() >= EquippedItem.ItemLevel && (uint)itemTemplate.GetQuality() >= EquippedItem.ItemQuality; - } - case CriteriaDataType.MapId: - return source.GetMapId() == MapId.Id; - case CriteriaDataType.SKnownTitle: - { - CharTitlesRecord titleInfo = CliDB.CharTitlesStorage.LookupByKey(KnownTitle.Id); - if (titleInfo != null) - return source != null && source.HasTitle(titleInfo.MaskID); - - return false; - } - case CriteriaDataType.SItemQuality: - { - ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(miscValue1); - if (pProto == null) - return false; - return (uint)pProto.GetQuality() == itemQuality.Quality; - } - default: - break; - } + Log.outError(LogFilter.Sql, "Table `criteria_data` for criteria (Entry: {0}) has wrong data type ({1}), ignored.", criteria.Id, DataType); return false; } - #region Structs - // criteria_data_TYPE_NONE = 0 (no data) - // criteria_data_TYPE_T_CREATURE = 1 - public struct CreatureStruct + switch (criteria.Entry.Type) { - public uint Id; + case CriteriaType.KillCreature: + case CriteriaType.KillAnyCreature: + case CriteriaType.WinBattleground: + case CriteriaType.MaxDistFallenWithoutDying: + case CriteriaType.CompleteQuest: // only hardcoded list + case CriteriaType.CastSpell: + case CriteriaType.WinAnyRankedArena: + case CriteriaType.DoEmote: + case CriteriaType.KillPlayer: + case CriteriaType.WinDuel: + case CriteriaType.GetLootByType: + case CriteriaType.LandTargetedSpellOnTarget: + case CriteriaType.BeSpellTarget: + case CriteriaType.GainAura: + case CriteriaType.EquipItemInSlot: + case CriteriaType.RollNeed: + case CriteriaType.RollGreed: + case CriteriaType.TrackedWorldStateUIModified: + case CriteriaType.EarnHonorableKill: + case CriteriaType.CompleteDailyQuest: // only Children's Week achievements + case CriteriaType.UseItem: // only Children's Week achievements + case CriteriaType.DeliveredKillingBlow: + case CriteriaType.ReachLevel: + case CriteriaType.Login: + case CriteriaType.LootAnyItem: + case CriteriaType.ObtainAnyItem: + break; + default: + if (DataType != CriteriaDataType.Script) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` has data for non-supported criteria type (Entry: {0} Type: {1}), ignored.", criteria.Id, (CriteriaType)criteria.Entry.Type); + return false; + } + break; } - // criteria_data_TYPE_T_PLAYER_CLASS_RACE = 2 - // criteria_data_TYPE_S_PLAYER_CLASS_RACE = 21 - public struct ClassRaceStruct + + switch (DataType) { - public uint ClassId; - public uint RaceId; + case CriteriaDataType.None: + case CriteriaDataType.InstanceScript: + return true; + case CriteriaDataType.TCreature: + if (Creature.Id == 0 || Global.ObjectMgr.GetCreatureTemplate(Creature.Id) == null) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_CREATURE ({2}) has non-existing creature id in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Creature.Id); + return false; + } + return true; + case CriteriaDataType.TPlayerClassRace: + if (ClassRace.ClassId == 0 && ClassRace.RaceId == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) must not have 0 in either value field, ignored.", + criteria.Id, criteria.Entry.Type, DataType); + return false; + } + if (ClassRace.ClassId != 0 && ((1 << (int)(ClassRace.ClassId - 1)) & (int)Class.ClassMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) has non-existing class in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, ClassRace.ClassId); + return false; + } + if (!RaceMask.AllPlayable.HasRace((Race)ClassRace.RaceId)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE ({2}) has non-existing race in value2 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, ClassRace.RaceId); + return false; + } + return true; + case CriteriaDataType.TPlayerLessHealth: + if (Health.Percent < 1 || Health.Percent > 100) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_PLAYER_LESS_HEALTH ({2}) has wrong percent value in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Health.Percent); + return false; + } + return true; + case CriteriaDataType.SAura: + case CriteriaDataType.TAura: + { + SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(Aura.SpellId, Difficulty.None); + if (spellEntry == null) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has wrong spell id in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Aura.SpellId); + return false; + } + if (spellEntry.GetEffects().Count <= Aura.EffectIndex) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has wrong spell effect index in value2 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Aura.EffectIndex); + return false; + } + if (spellEntry.GetEffect(Aura.EffectIndex).ApplyAuraName == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type {2} has non-aura spell effect (ID: {3} Effect: {4}), ignores.", + criteria.Id, criteria.Entry.Type, DataType, Aura.SpellId, Aura.EffectIndex); + return false; + } + return true; + } + case CriteriaDataType.Value: + if (Value.ComparisonType >= (int)ComparisionType.Max) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_VALUE ({2}) has wrong ComparisionType in value2 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Value.ComparisonType); + return false; + } + return true; + case CriteriaDataType.TLevel: + if (Level.Min > SharedConst.GTMaxLevel) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_LEVEL ({2}) has wrong minlevel in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Level.Min); + return false; + } + return true; + case CriteriaDataType.TGender: + if (Gender.Gender > (int)Framework.Constants.Gender.None) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_GENDER ({2}) has wrong gender in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Gender.Gender); + return false; + } + return true; + case CriteriaDataType.Script: + if (ScriptId == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_SCRIPT ({2}) does not have ScriptName set, ignored.", + criteria.Id, criteria.Entry.Type, DataType); + return false; + } + return true; + case CriteriaDataType.MapPlayerCount: + if (MapPlayers.MaxCount <= 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT ({2}) has wrong max players count in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, MapPlayers.MaxCount); + return false; + } + return true; + case CriteriaDataType.TTeam: + if (TeamId.Team != (int)Team.Alliance && TeamId.Team != (int)Team.Horde) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_T_TEAM ({2}) has unknown team in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, TeamId.Team); + return false; + } + return true; + case CriteriaDataType.SDrunk: + if (Drunk.State >= 4) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_DRUNK ({2}) has unknown drunken state in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Drunk.State); + return false; + } + return true; + case CriteriaDataType.Holiday: + if (!CliDB.HolidaysStorage.ContainsKey(Holiday.Id)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data`(Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_HOLIDAY ({2}) has unknown holiday in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, Holiday.Id); + return false; + } + return true; + case CriteriaDataType.GameEvent: + { + var events = Global.GameEventMgr.GetEventMap(); + if (GameEvent.Id < 1 || GameEvent.Id >= events.Length) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_GAME_EVENT ({2}) has unknown game_event in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, GameEvent.Id); + return false; + } + return true; + } + case CriteriaDataType.BgLossTeamScore: + return true; // not check correctness node indexes + case CriteriaDataType.SEquippedItem: + if (EquippedItem.ItemQuality >= (uint)ItemQuality.Max) + { + Log.outError(LogFilter.Sql, "Table `achievement_criteria_requirement` (Entry: {0} Type: {1}) for requirement ACHIEVEMENT_CRITERIA_REQUIRE_S_EQUIPED_ITEM ({2}) has unknown quality state in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, EquippedItem.ItemQuality); + return false; + } + return true; + case CriteriaDataType.MapId: + if (!CliDB.MapStorage.ContainsKey(MapId.Id)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_MAP_ID ({2}) contains an unknown map entry in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, MapId.Id); + return false; + } + return true; + case CriteriaDataType.SPlayerClassRace: + if (ClassRace.ClassId == 0 && ClassRace.RaceId == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) must not have 0 in either value field, ignored.", + criteria.Id, criteria.Entry.Type, DataType); + return false; + } + if (ClassRace.ClassId != 0 && ((1 << (int)(ClassRace.ClassId - 1)) & (int)Class.ClassMaskAllPlayable) == 0) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) has non-existing class in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, ClassRace.ClassId); + return false; + } + if (ClassRace.RaceId != 0 && !RaceMask.AllPlayable.HasRace((Race)ClassRace.RaceId)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_PLAYER_CLASS_RACE ({2}) has non-existing race in value2 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, ClassRace.RaceId); + return false; + } + return true; + case CriteriaDataType.SKnownTitle: + if (!CliDB.CharTitlesStorage.ContainsKey(KnownTitle.Id)) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_KNOWN_TITLE ({2}) contains an unknown title_id in value1 ({3}), ignore.", + criteria.Id, criteria.Entry.Type, DataType, KnownTitle.Id); + return false; + } + return true; + case CriteriaDataType.SItemQuality: + if (itemQuality.Quality >= (uint)ItemQuality.Max) + { + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) for data type CRITERIA_DATA_TYPE_S_ITEM_QUALITY ({2}) contains an unknown quality state value in value1 ({3}), ignored.", + criteria.Id, criteria.Entry.Type, DataType, itemQuality.Quality); + return false; + } + return true; + default: + Log.outError(LogFilter.Sql, "Table `criteria_data` (Entry: {0} Type: {1}) contains data of a non-supported data type ({2}), ignored.", criteria.Id, criteria.Entry.Type, DataType); + return false; } - // criteria_data_TYPE_T_PLAYER_LESS_HEALTH = 3 - public struct HealthStruct - { - public uint Percent; - } - // criteria_data_TYPE_S_AURA = 5 - // criteria_data_TYPE_T_AURA = 7 - public struct AuraStruct - { - public uint SpellId; - public uint EffectIndex; - } - // criteria_data_TYPE_VALUE = 8 - public struct ValueStruct - { - public uint Value; - public uint ComparisonType; - } - // criteria_data_TYPE_T_LEVEL = 9 - public struct LevelStruct - { - public uint Min; - } - // criteria_data_TYPE_T_GENDER = 10 - public struct GenderStruct - { - public uint Gender; - } - // criteria_data_TYPE_SCRIPT = 11 (no data) - // criteria_data_TYPE_MAP_PLAYER_COUNT = 13 - public struct MapPlayersStruct - { - public uint MaxCount; - } - // criteria_data_TYPE_T_TEAM = 14 - public struct TeamStruct - { - public uint Team; - } - // criteria_data_TYPE_S_DRUNK = 15 - public struct DrunkStruct - { - public uint State; - } - // criteria_data_TYPE_HOLIDAY = 16 - public struct HolidayStruct - { - public uint Id; - } - // criteria_data_TYPE_BG_LOSS_TEAM_SCORE= 17 - public struct BgLossTeamScoreStruct - { - public uint Min; - public uint Max; - } - // criteria_data_INSTANCE_SCRIPT = 18 (no data) - // criteria_data_TYPE_S_EQUIPED_ITEM = 19 - public struct EquippedItemStruct - { - public uint ItemLevel; - public uint ItemQuality; - } - // criteria_data_TYPE_MAP_ID = 20 - public struct MapIdStruct - { - public uint Id; - } - // criteria_data_TYPE_KNOWN_TITLE = 23 - public struct KnownTitleStruct - { - public uint Id; - } - // CRITERIA_DATA_TYPE_S_ITEM_QUALITY = 24 - public struct ItemQualityStruct - { - public uint Quality; - } - // criteria_data_TYPE_GAME_EVENT = 25 - public struct GameEventStruct - { - public uint Id; - } - // raw - public struct RawStruct - { - public uint Value1; - public uint Value2; - } - #endregion } - public class CriteriaDataSet + public bool Meets(uint criteriaId, Player source, WorldObject target, uint miscValue1 = 0, uint miscValue2 = 0) { - uint _criteriaId; - List _storage = new(); - - public void Add(CriteriaData data) { _storage.Add(data); } - - public bool Meets(Player source, WorldObject target, uint miscValue = 0, uint miscValue2 = 0) + switch (DataType) { - foreach (var data in _storage) - if (!data.Meets(_criteriaId, source, target, miscValue, miscValue2)) + case CriteriaDataType.None: + return true; + case CriteriaDataType.TCreature: + if (target == null || !target.IsTypeId(TypeId.Unit)) + return false; + return target.GetEntry() == Creature.Id; + case CriteriaDataType.TPlayerClassRace: + if (target == null || !target.IsTypeId(TypeId.Player)) + return false; + if (ClassRace.ClassId != 0 && ClassRace.ClassId != (uint)target.ToPlayer().GetClass()) + return false; + if (ClassRace.RaceId != 0 && ClassRace.RaceId != (uint)target.ToPlayer().GetRace()) + return false; + return true; + case CriteriaDataType.SPlayerClassRace: + if (source == null || !source.IsTypeId(TypeId.Player)) + return false; + if (ClassRace.ClassId != 0 && ClassRace.ClassId != (uint)source.ToPlayer().GetClass()) + return false; + if (ClassRace.RaceId != 0 && ClassRace.RaceId != (uint)source.ToPlayer().GetRace()) + return false; + return true; + case CriteriaDataType.TPlayerLessHealth: + if (target == null || !target.IsTypeId(TypeId.Player)) + return false; + return !target.ToPlayer().HealthAbovePct((int)Health.Percent); + case CriteriaDataType.SAura: + return source.HasAuraEffect(Aura.SpellId, (byte)Aura.EffectIndex); + case CriteriaDataType.TAura: + { + if (target == null) + return false; + Unit unitTarget = target.ToUnit(); + if (unitTarget == null) + return false; + return unitTarget.HasAuraEffect(Aura.SpellId, Aura.EffectIndex); + } + case CriteriaDataType.Value: + return MathFunctions.CompareValues((ComparisionType)Value.ComparisonType, miscValue1, Value.Value); + case CriteriaDataType.TLevel: + if (target == null) + return false; + return target.GetLevelForTarget(source) >= Level.Min; + case CriteriaDataType.TGender: + { + if (target == null) + return false; + Unit unitTarget = target.ToUnit(); + if (unitTarget == null) + return false; + return unitTarget.GetGender() == (Gender)Gender.Gender; + } + case CriteriaDataType.Script: + { + Unit unitTarget = null; + if (target != null) + unitTarget = target.ToUnit(); + return Global.ScriptMgr.OnCriteriaCheck(ScriptId, source.ToPlayer(), unitTarget.ToUnit()); + } + case CriteriaDataType.MapPlayerCount: + return source.GetMap().GetPlayersCountExceptGMs() <= MapPlayers.MaxCount; + case CriteriaDataType.TTeam: + if (target == null || !target.IsTypeId(TypeId.Player)) + return false; + return target.ToPlayer().GetTeam() == (Team)TeamId.Team; + case CriteriaDataType.SDrunk: + return Player.GetDrunkenstateByValue(source.GetDrunkValue()) >= (DrunkenState)Drunk.State; + case CriteriaDataType.Holiday: + return Global.GameEventMgr.IsHolidayActive((HolidayIds)Holiday.Id); + case CriteriaDataType.GameEvent: + return Global.GameEventMgr.IsEventActive((ushort)GameEvent.Id); + case CriteriaDataType.BgLossTeamScore: + { + Battleground bg = source.GetBattleground(); + if (bg == null) return false; - return true; - } + int score = (int)bg.GetTeamScore(bg.GetPlayerTeam(source.GetGUID()) == Team.Alliance ? BattleGroundTeamId.Horde : BattleGroundTeamId.Alliance); + return score >= BattlegroundScore.Min && score <= BattlegroundScore.Max; + } + case CriteriaDataType.InstanceScript: + { + if (!source.IsInWorld) + return false; + Map map = source.GetMap(); + if (!map.IsDungeon()) + { + Log.outError(LogFilter.Achievement, "Achievement system call AchievementCriteriaDataType.InstanceScript ({0}) for achievement criteria {1} for non-dungeon/non-raid map {2}", + CriteriaDataType.InstanceScript, criteriaId, map.GetId()); + return false; + } + InstanceScript instance = ((InstanceMap)map).GetInstanceScript(); + if (instance == null) + { + Log.outError(LogFilter.Achievement, "Achievement system call criteria_data_INSTANCE_SCRIPT ({0}) for achievement criteria {1} for map {2} but map does not have a instance script", + CriteriaDataType.InstanceScript, criteriaId, map.GetId()); + return false; + } - public void SetCriteriaId(uint id) { _criteriaId = id; } + Unit unitTarget = null; + if (target != null) + unitTarget = target.ToUnit(); + return instance.CheckAchievementCriteriaMeet(criteriaId, source, unitTarget, miscValue1); + } + case CriteriaDataType.SEquippedItem: + { + Criteria entry = Global.CriteriaMgr.GetCriteria(criteriaId); + + uint itemId = entry.Entry.Type == CriteriaType.EquipItemInSlot ? miscValue2 : miscValue1; + ItemTemplate itemTemplate = Global.ObjectMgr.GetItemTemplate(itemId); + if (itemTemplate == null) + return false; + return itemTemplate.GetBaseItemLevel() >= EquippedItem.ItemLevel && (uint)itemTemplate.GetQuality() >= EquippedItem.ItemQuality; + } + case CriteriaDataType.MapId: + return source.GetMapId() == MapId.Id; + case CriteriaDataType.SKnownTitle: + { + CharTitlesRecord titleInfo = CliDB.CharTitlesStorage.LookupByKey(KnownTitle.Id); + if (titleInfo != null) + return source != null && source.HasTitle(titleInfo.MaskID); + + return false; + } + case CriteriaDataType.SItemQuality: + { + ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(miscValue1); + if (pProto == null) + return false; + return (uint)pProto.GetQuality() == itemQuality.Quality; + } + default: + break; + } + return false; } + + #region Structs + // criteria_data_TYPE_NONE = 0 (no data) + // criteria_data_TYPE_T_CREATURE = 1 + public struct CreatureStruct + { + public uint Id; + } + // criteria_data_TYPE_T_PLAYER_CLASS_RACE = 2 + // criteria_data_TYPE_S_PLAYER_CLASS_RACE = 21 + public struct ClassRaceStruct + { + public uint ClassId; + public uint RaceId; + } + // criteria_data_TYPE_T_PLAYER_LESS_HEALTH = 3 + public struct HealthStruct + { + public uint Percent; + } + // criteria_data_TYPE_S_AURA = 5 + // criteria_data_TYPE_T_AURA = 7 + public struct AuraStruct + { + public uint SpellId; + public uint EffectIndex; + } + // criteria_data_TYPE_VALUE = 8 + public struct ValueStruct + { + public uint Value; + public uint ComparisonType; + } + // criteria_data_TYPE_T_LEVEL = 9 + public struct LevelStruct + { + public uint Min; + } + // criteria_data_TYPE_T_GENDER = 10 + public struct GenderStruct + { + public uint Gender; + } + // criteria_data_TYPE_SCRIPT = 11 (no data) + // criteria_data_TYPE_MAP_PLAYER_COUNT = 13 + public struct MapPlayersStruct + { + public uint MaxCount; + } + // criteria_data_TYPE_T_TEAM = 14 + public struct TeamStruct + { + public uint Team; + } + // criteria_data_TYPE_S_DRUNK = 15 + public struct DrunkStruct + { + public uint State; + } + // criteria_data_TYPE_HOLIDAY = 16 + public struct HolidayStruct + { + public uint Id; + } + // criteria_data_TYPE_BG_LOSS_TEAM_SCORE= 17 + public struct BgLossTeamScoreStruct + { + public uint Min; + public uint Max; + } + // criteria_data_INSTANCE_SCRIPT = 18 (no data) + // criteria_data_TYPE_S_EQUIPED_ITEM = 19 + public struct EquippedItemStruct + { + public uint ItemLevel; + public uint ItemQuality; + } + // criteria_data_TYPE_MAP_ID = 20 + public struct MapIdStruct + { + public uint Id; + } + // criteria_data_TYPE_KNOWN_TITLE = 23 + public struct KnownTitleStruct + { + public uint Id; + } + // CRITERIA_DATA_TYPE_S_ITEM_QUALITY = 24 + public struct ItemQualityStruct + { + public uint Quality; + } + // criteria_data_TYPE_GAME_EVENT = 25 + public struct GameEventStruct + { + public uint Id; + } + // raw + public struct RawStruct + { + public uint Value1; + public uint Value2; + } + #endregion +} + +public class CriteriaDataSet +{ + uint _criteriaId; + List _storage = new(); + + public void Add(CriteriaData data) { _storage.Add(data); } + + public bool Meets(Player source, WorldObject target, uint miscValue = 0, uint miscValue2 = 0) + { + foreach (var data in _storage) + if (!data.Meets(_criteriaId, source, target, miscValue, miscValue2)) + return false; + + return true; + } + + public void SetCriteriaId(uint id) { _criteriaId = id; } +} } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 61e6843fe..ab88bf0fa 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -5453,6 +5453,8 @@ namespace Game.Entities UpdateCriteria(CriteriaType.ActivelyReachLevel, level); if (level > oldLevel) UpdateCriteria(CriteriaType.GainLevels, level - oldLevel); + if (IsMaxLevel()) + UpdateCriteria(CriteriaType.ReachMaxLevel); PushQuests(); diff --git a/Source/Game/Quest/QuestObjectiveCriteriaManager.cs b/Source/Game/Quest/QuestObjectiveCriteriaManager.cs index 84f051a96..abab850f7 100644 --- a/Source/Game/Quest/QuestObjectiveCriteriaManager.cs +++ b/Source/Game/Quest/QuestObjectiveCriteriaManager.cs @@ -22,8 +22,8 @@ namespace Game public void CheckAllQuestObjectiveCriteria(Player referencePlayer) { // suppress sending packets - for (CriteriaType i = 0; i < CriteriaType.Count; ++i) - UpdateCriteria(i, 0, 0, 0, null, referencePlayer); + foreach (CriteriaType criteriaType in CriteriaManager.GetRetroactivelyUpdateableCriteriaTypes()) + UpdateCriteria(criteriaType, 0, 0, 0, null, referencePlayer); } public override void Reset()