diff --git a/Source/Framework/Constants/ConditionConst.cs b/Source/Framework/Constants/ConditionConst.cs index 87c7003e9..450be5d59 100644 --- a/Source/Framework/Constants/ConditionConst.cs +++ b/Source/Framework/Constants/ConditionConst.cs @@ -64,6 +64,7 @@ namespace Framework.Constants PlayerCondition = 56, // PlayerConditionId 0 0 true if player satisfies PlayerCondition PrivateObject = 57, // 0 0 0 true if entity is private object StringId = 58, + Label = 59, // Label 0 0 true if creature/gameobject has specified Label in CreatureLabel.db2/GameObjectLabel.db2 Max } diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index 3d995a368..cee885ba8 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -2600,7 +2600,7 @@ namespace Framework.Constants ChangeItemBonuses2 = 313, // MiscValue[0] = ItemBonusTreeID to preserve AddSocketBonus = 314, // MiscValue[0] = required ItemBonusTreeID LearnTransmogAppearanceFromItemModAppearanceGroup = 315, // MiscValue[0] = ItemModAppearanceGroupID (not in db2) - KillCreditLbael1 = 316, + KillCreditLabel1 = 316, KillCreditLabel2 = 317, Unk318 = 318, Unk319 = 319, diff --git a/Source/Framework/Database/Databases/HotfixDatabase.cs b/Source/Framework/Database/Databases/HotfixDatabase.cs index 69a93c6d9..f928c8802 100644 --- a/Source/Framework/Database/Databases/HotfixDatabase.cs +++ b/Source/Framework/Database/Databases/HotfixDatabase.cs @@ -393,6 +393,9 @@ namespace Framework.Database "IconFileID, SkillLine1, SkillLine2 FROM creature_family WHERE (`VerifiedBuild` > 0) = ?"); PrepareStatement(HotfixStatements.SEL_CREATURE_FAMILY_LOCALE, "SELECT ID, Name_lang FROM creature_family_locale WHERE (`VerifiedBuild` > 0) = ? AND locale = ?"); + // CreatureLabel.db2 + PrepareStatement(HotfixStatements.SEL_CREATURE_LABEL, "SELECT ID, LabelID, CreatureDifficultyID FROM creature_label WHERE (`VerifiedBuild` > 0) = ?"); + // CreatureModelData.db2 PrepareStatement(HotfixStatements.SEL_CREATURE_MODEL_DATA, "SELECT ID, GeoBox1, GeoBox2, GeoBox3, GeoBox4, GeoBox5, GeoBox6, Flags, FileDataID, WalkSpeed, " + "RunSpeed, BloodID, FootprintTextureID, FootprintTextureLength, FootprintTextureWidth, FootprintParticleScale, FoleyMaterialID, " + @@ -528,6 +531,9 @@ namespace Framework.Database "FileDataID, ObjectEffectPackageID, OverrideLootEffectScale, OverrideNameScale, AlternateDisplayType, ClientCreatureDisplayInfoID, " + "ClientItemID, Unknown1100 FROM gameobject_display_info WHERE (`VerifiedBuild` > 0) = ?"); + // GameobjectLabel.db2 + PrepareStatement(HotfixStatements.SEL_GAMEOBJECT_LABEL, "SELECT ID, LabelID, GameObjectID FROM gameobject_label WHERE (`VerifiedBuild` > 0) = ?"); + // Gameobjects.db2 PrepareStatement(HotfixStatements.SEL_GAMEOBJECTS, "SELECT Name, PosX, PosY, PosZ, Rot1, Rot2, Rot3, Rot4, ID, OwnerID, DisplayID, Scale, TypeID, " + "PhaseUseFlags, PhaseID, PhaseGroupID, Unknown1100, PropValue1, PropValue2, PropValue3, PropValue4, PropValue5, PropValue6, PropValue7, " + @@ -1828,6 +1834,8 @@ namespace Framework.Database SEL_CREATURE_FAMILY, SEL_CREATURE_FAMILY_LOCALE, + SEL_CREATURE_LABEL, + SEL_CREATURE_MODEL_DATA, SEL_CREATURE_TYPE, @@ -1887,6 +1895,8 @@ namespace Framework.Database SEL_GAMEOBJECT_DISPLAY_INFO, + SEL_GAMEOBJECT_LABEL, + SEL_GAMEOBJECTS, SEL_GAMEOBJECTS_LOCALE, diff --git a/Source/Game/Conditions/Condition.cs b/Source/Game/Conditions/Condition.cs index b6bdaeeab..bebab37c3 100644 --- a/Source/Game/Conditions/Condition.cs +++ b/Source/Game/Conditions/Condition.cs @@ -467,6 +467,19 @@ namespace Game.Conditions } break; } + case ConditionTypes.Label: + { + Creature creature = obj.ToCreature(); + if (creature != null) + condMeets = creature.HasLabel((int)ConditionValue1); + else + { + GameObject go = obj.ToGameObject(); + if (go != null) + condMeets = go.HasLabel((int)ConditionValue1); + } + break; + } default: break; } @@ -594,6 +607,9 @@ namespace Game.Conditions case ConditionTypes.StringId: mask |= GridMapTypeMask.Creature | GridMapTypeMask.GameObject; break; + case ConditionTypes.Label: + mask |= GridMapTypeMask.Creature | GridMapTypeMask.GameObject; + break; default: Cypher.Assert(false, "Condition.GetSearcherTypeMaskForCondition - missing condition handling!"); break; diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index 99ff77ae0..c0c5738bf 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -1650,6 +1650,7 @@ namespace Game case ConditionTypes.Gamemaster: case ConditionTypes.PrivateObject: case ConditionTypes.StringId: + case ConditionTypes.Label: break; case ConditionTypes.DifficultyId: if (!CliDB.DifficultyStorage.ContainsKey(cond.ConditionValue1)) @@ -2624,7 +2625,7 @@ namespace Game case UnitConditionVariable.IsMounted: return unit.GetMountDisplayId() != 0 ? 1 : 0; case UnitConditionVariable.Label: - break; + return unit.IsCreature() && unit.ToCreature().HasLabel(value) ? value : 0; case UnitConditionVariable.IsMySummon: return (otherUnit != null && (otherUnit.GetCharmerGUID() == unit.GetGUID() || otherUnit.GetCreatorGUID() == unit.GetGUID())) ? 1 : 0; case UnitConditionVariable.IsSummoner: @@ -2986,7 +2987,8 @@ namespace Game new ConditionTypeInfo("Scene In Progress", true, false, false, false), new ConditionTypeInfo("Player Condition", true, false, false, false), new ConditionTypeInfo("Private Object", false,false, false, false), - new ConditionTypeInfo("String ID", true, false, false, true) + new ConditionTypeInfo("String ID", true, false, false, true), + new ConditionTypeInfo("Label", true, false, false, false) }; public struct ConditionTypeInfo diff --git a/Source/Game/DataStorage/CliDB.cs b/Source/Game/DataStorage/CliDB.cs index a9eb96670..c7e8fa387 100644 --- a/Source/Game/DataStorage/CliDB.cs +++ b/Source/Game/DataStorage/CliDB.cs @@ -127,6 +127,7 @@ namespace Game.DataStorage CreatureDisplayInfoStorage = ReadDB2("CreatureDisplayInfo.db2", HotfixStatements.SEL_CREATURE_DISPLAY_INFO); CreatureDisplayInfoExtraStorage = ReadDB2("CreatureDisplayInfoExtra.db2", HotfixStatements.SEL_CREATURE_DISPLAY_INFO_EXTRA); CreatureFamilyStorage = ReadDB2("CreatureFamily.db2", HotfixStatements.SEL_CREATURE_FAMILY, HotfixStatements.SEL_CREATURE_FAMILY_LOCALE); + CreatureLabelStorage = ReadDB2("CreatureLabel.db2", HotfixStatements.SEL_CREATURE_LABEL); CreatureModelDataStorage = ReadDB2("CreatureModelData.db2", HotfixStatements.SEL_CREATURE_MODEL_DATA); CreatureTypeStorage = ReadDB2("CreatureType.db2", HotfixStatements.SEL_CREATURE_TYPE, HotfixStatements.SEL_CREATURE_TYPE_LOCALE); CriteriaStorage = ReadDB2("Criteria.db2", HotfixStatements.SEL_CRITERIA); @@ -152,6 +153,7 @@ namespace Game.DataStorage FriendshipReputationStorage = ReadDB2("FriendshipReputation.db2", HotfixStatements.SEL_FRIENDSHIP_REPUTATION, HotfixStatements.SEL_FRIENDSHIP_REPUTATION_LOCALE); GameObjectArtKitStorage = ReadDB2("GameObjectArtKit.db2", HotfixStatements.SEL_GAMEOBJECT_ART_KIT); GameObjectDisplayInfoStorage = ReadDB2("GameObjectDisplayInfo.db2", HotfixStatements.SEL_GAMEOBJECT_DISPLAY_INFO); + GameObjectLabelStorage = ReadDB2("GameObjectLabel.db2", HotfixStatements.SEL_GAMEOBJECT_LABEL); GameObjectsStorage = ReadDB2("GameObjects.db2", HotfixStatements.SEL_GAMEOBJECTS, HotfixStatements.SEL_GAMEOBJECTS_LOCALE); GarrAbilityStorage = ReadDB2("GarrAbility.db2", HotfixStatements.SEL_GARR_ABILITY, HotfixStatements.SEL_GARR_ABILITY_LOCALE); GarrBuildingStorage = ReadDB2("GarrBuilding.db2", HotfixStatements.SEL_GARR_BUILDING, HotfixStatements.SEL_GARR_BUILDING_LOCALE); @@ -577,6 +579,7 @@ namespace Game.DataStorage public static DB6Storage CreatureDisplayInfoStorage; public static DB6Storage CreatureDisplayInfoExtraStorage; public static DB6Storage CreatureFamilyStorage; + public static DB6Storage CreatureLabelStorage; public static DB6Storage CreatureModelDataStorage; public static DB6Storage CreatureTypeStorage; public static DB6Storage CriteriaStorage; @@ -602,6 +605,7 @@ namespace Game.DataStorage public static DB6Storage FriendshipReputationStorage; public static DB6Storage GameObjectArtKitStorage; public static DB6Storage GameObjectDisplayInfoStorage; + public static DB6Storage GameObjectLabelStorage; public static DB6Storage GameObjectsStorage; public static DB6Storage GarrAbilityStorage; public static DB6Storage GarrBuildingStorage; diff --git a/Source/Game/DataStorage/DB2Manager.cs b/Source/Game/DataStorage/DB2Manager.cs index 98c329e89..a61d19462 100644 --- a/Source/Game/DataStorage/DB2Manager.cs +++ b/Source/Game/DataStorage/DB2Manager.cs @@ -255,6 +255,9 @@ namespace Game.DataStorage foreach (ContentTuningXLabelRecord contentTuningXLabel in ContentTuningXLabelStorage.Values) _contentTuningLabels.Add((contentTuningXLabel.ContentTuningID, contentTuningXLabel.LabelID)); + foreach (var (_, creatureLabel) in CreatureLabelStorage) + _creatureLabels.Add(creatureLabel.CreatureDifficultyID, creatureLabel.LabelID); + foreach (CurrencyContainerRecord currencyContainer in CurrencyContainerStorage.Values) _currencyContainers.Add(currencyContainer.CurrencyTypesID, currencyContainer); @@ -282,7 +285,7 @@ namespace Game.DataStorage _factionTeams.Add(faction.ParentFactionID, faction.Id); foreach (FriendshipRepReactionRecord friendshipRepReaction in FriendshipRepReactionStorage.Values) - _friendshipRepReactions.Add((uint)friendshipRepReaction.FriendshipRepID, friendshipRepReaction); + _friendshipRepReactions.Add(friendshipRepReaction.FriendshipRepID, friendshipRepReaction); foreach (var key in _friendshipRepReactions.Keys) _friendshipRepReactions[key].Sort(new FriendshipRepReactionRecordComparer()); @@ -297,6 +300,9 @@ namespace Game.DataStorage Extensions.Swap(ref gameObjectDisplayInfo.GeoBox[5], ref gameObjectDisplayInfo.GeoBox[2]); } + foreach (var (_, gameobjectLabel) in GameObjectLabelStorage) + _gameobjectLabels.Add(gameobjectLabel.GameObjectID, gameobjectLabel.LabelID); + foreach (HeirloomRecord heirloom in HeirloomStorage.Values) _heirlooms[heirloom.ItemID] = heirloom; @@ -1175,6 +1181,11 @@ namespace Game.DataStorage return petFamily.Name[locale][0] != '\0' ? petFamily.Name[locale] : ""; } + public List GetCreatureLabels(int creatureDifficultyId) + { + return _creatureLabels.LookupByKey(creatureDifficultyId); + } + public CurrencyContainerRecord GetCurrencyContainerForCurrencyQuantity(uint currencyId, int quantity) { foreach (var record in _currencyContainers.LookupByKey(currencyId)) @@ -1511,6 +1522,11 @@ namespace Game.DataStorage return _friendshipRepReactions.LookupByKey(friendshipRepID); } + public List GetGameObjectLabels(uint gameobjectId) + { + return _gameobjectLabels.LookupByKey(gameobjectId); + } + public uint GetGlobalCurveId(GlobalCurve globalCurveType) { foreach (var globalCurveEntry in GlobalCurveStorage.Values) @@ -2341,6 +2357,7 @@ namespace Game.DataStorage Dictionary _conditionalChrModelsByChrModelId = new(); Dictionary> _conditionalContentTuning = new(); List<(uint, int)> _contentTuningLabels = new(); + MultiMap _creatureLabels = new(); MultiMap _currencyContainers = new(); MultiMap _curvePoints = new(); Dictionary, EmotesTextSoundRecord> _emoteTextSounds = new(); @@ -2349,6 +2366,7 @@ namespace Game.DataStorage MultiMap _factionTeams = new(); MultiMap _friendshipRepReactions = new(); Dictionary _heirlooms = new(); + MultiMap _gameobjectLabels = new(); MultiMap _glyphBindableSpells = new(); MultiMap _glyphRequiredSpecs = new(); Dictionary _itemChildEquipment = new(); diff --git a/Source/Game/DataStorage/Structs/C_Records.cs b/Source/Game/DataStorage/Structs/C_Records.cs index 8f12d6215..b35f0cdf9 100644 --- a/Source/Game/DataStorage/Structs/C_Records.cs +++ b/Source/Game/DataStorage/Structs/C_Records.cs @@ -507,6 +507,13 @@ namespace Game.DataStorage public short[] SkillLine = new short[2]; } + public sealed class CreatureLabelRecord + { + public uint Id; + public int LabelID; + public uint CreatureDifficultyID; + } + public sealed class CreatureModelDataRecord { public uint Id; diff --git a/Source/Game/DataStorage/Structs/G_Records.cs b/Source/Game/DataStorage/Structs/G_Records.cs index 5904c801d..d1869d3fc 100644 --- a/Source/Game/DataStorage/Structs/G_Records.cs +++ b/Source/Game/DataStorage/Structs/G_Records.cs @@ -38,6 +38,13 @@ namespace Game.DataStorage } } + public sealed class GameObjectLabelRecord + { + public uint Id; + public int LabelID; + public uint GameObjectID; + } + public sealed class GameObjectsRecord { public LocalizedString Name; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 0cf7caa14..1b81073a9 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -3160,6 +3160,16 @@ namespace Game.Entities return base.GetName(locale); } + public bool HasLabel(int cretureLabel) + { + return GetLabels().Contains(cretureLabel); + } + + public List GetLabels() + { + return Global.DB2Mgr.GetCreatureLabels(GetCreatureDifficulty().CreatureDifficultyID); + } + public virtual int GetPetAutoSpellSize() { return SharedConst.MaxSpellCharm; diff --git a/Source/Game/Entities/GameObject/GameObject.cs b/Source/Game/Entities/GameObject/GameObject.cs index 692d269db..62a5d9b39 100644 --- a/Source/Game/Entities/GameObject/GameObject.cs +++ b/Source/Game/Entities/GameObject/GameObject.cs @@ -2752,6 +2752,16 @@ namespace Game.Entities return base.GetName(locale); } + public bool HasLabel(int gameobjectLabel) + { + return GetLabels().Contains(gameobjectLabel); + } + + public List GetLabels() + { + return Global.DB2Mgr.GetGameObjectLabels(GetEntry()); + } + public void UpdatePackedRotation() { const int PACK_YZ = 1 << 20; diff --git a/Source/Game/Entities/Player/Player.Quest.cs b/Source/Game/Entities/Player/Player.Quest.cs index 32414eb78..f7af0001d 100644 --- a/Source/Game/Entities/Player/Player.Quest.cs +++ b/Source/Game/Entities/Player/Player.Quest.cs @@ -2539,6 +2539,9 @@ namespace Game.Entities for (byte i = 0; i < 2; ++i) if (cInfo.KillCredit[i] != 0) KilledMonsterCredit(cInfo.KillCredit[i]); + + foreach (int label in creature.GetLabels()) + UpdateQuestObjectiveProgress(QuestObjectiveType.KillWithLabel, label, 1, creature.GetGUID()); } public void KilledMonsterCredit(uint entry, ObjectGuid guid = default) @@ -3025,6 +3028,7 @@ namespace Game.Entities case QuestObjectiveType.HaveCurrency: case QuestObjectiveType.ObtainCurrency: case QuestObjectiveType.IncreaseReputation: + case QuestObjectiveType.KillWithLabel: if (GetQuestSlotObjectiveData(slot, objective) < objective.Amount) return false; break; diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index b338eb5f8..fe9cb2179 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -7726,6 +7726,7 @@ namespace Game case QuestObjectiveType.Money: case QuestObjectiveType.WinPvpPetBattles: case QuestObjectiveType.ProgressBar: + case QuestObjectiveType.KillWithLabel: break; default: Log.outError(LogFilter.Sql, "Quest {0} objective {1} has unhandled type {2}", qinfo.Id, obj.Id, obj.Type); diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 702d3cb46..9db9e35cd 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -4473,6 +4473,20 @@ namespace Game.Spells unitTarget.ToPlayer().RewardPlayerAndGroupAtEvent((uint)creatureEntry, unitTarget); } + [SpellEffectHandler(SpellEffectName.KillCreditLabel1)] + [SpellEffectHandler(SpellEffectName.KillCreditLabel2)] + void EffectKillCreditLabel() + { + if (effectHandleMode != SpellEffectHandleMode.HitTarget) + return; + + Player playerTarget = unitTarget?.ToPlayer(); + if (playerTarget == null) + return; + + playerTarget.UpdateQuestObjectiveProgress(QuestObjectiveType.KillWithLabel, effectInfo.MiscValue, Math.Max(1, effectInfo.MiscValueB)); + } + [SpellEffectHandler(SpellEffectName.QuestFail)] void EffectQuestFail() {