diff --git a/Source/Framework/Constants/ItemConst.cs b/Source/Framework/Constants/ItemConst.cs index 424e4de7b..7b5aa1258 100644 --- a/Source/Framework/Constants/ItemConst.cs +++ b/Source/Framework/Constants/ItemConst.cs @@ -1509,8 +1509,9 @@ namespace Framework.Constants public enum LootStoreItemType { - Item = 0, - Reference = 1, + Item = 0, + Reference = 1, Currency = 2, + TrackingQuest = 3 } } diff --git a/Source/Framework/Constants/Language.cs b/Source/Framework/Constants/Language.cs index 3b387ee83..775c4d242 100644 --- a/Source/Framework/Constants/Language.cs +++ b/Source/Framework/Constants/Language.cs @@ -369,7 +369,7 @@ namespace Framework.Constants CommandNpcShowlootMoney = 292, CommandNpcShowlootLabel2 = 293, CommandNpcShowlootSublabel = 294, - CommandNpcShowlootEntry2 = 295, + CommandNpcShowlootTrackingQuest = 295, CommandNpcShowLootCurrency = 296, // End diff --git a/Source/Framework/Constants/LootConst.cs b/Source/Framework/Constants/LootConst.cs index f029a2d4e..6883f48f6 100644 --- a/Source/Framework/Constants/LootConst.cs +++ b/Source/Framework/Constants/LootConst.cs @@ -68,7 +68,8 @@ namespace Framework.Constants public enum LootItemType { Item = 0, - Currency = 1 + Currency = 1, + TrackingQuest = 2 } public enum LootError diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index de3aa164f..8180e0f4b 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -524,7 +524,7 @@ namespace Game.Chat if (itemTemplate != null) name = itemTemplate.GetName(handler.GetSessionDbcLocale()); - handler.SendSysMessage(alternateString ? CypherStrings.CommandNpcShowlootEntry2 : CypherStrings.CommandNpcShowlootEntry, + handler.SendSysMessage(CypherStrings.CommandNpcShowlootEntry, alternateString ? 6 : 3 /*number of bytes from following string*/, "\xE2\x94\x80\xE2\x94\x80", itemCount, ItemConst.ItemQualityColors[(int)(itemTemplate != null ? itemTemplate.GetQuality() : ItemQuality.Poor)], itemId, name, itemId); } @@ -539,6 +539,23 @@ namespace Game.Chat count, ItemConst.ItemQualityColors[currency != null ? currency.Quality : (int)ItemQuality.Poor], currencyId, count, name, currencyId); } + static void _ShowLootTrackingQuestCurrencyEntry(CommandHandler handler, uint questId, bool alternateString = false) + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); + string name = "Unknown quest"; + if (quest != null) + { + name = quest.LogTitle; + if (handler.GetSessionDbcLocale() != Locale.enUS) + { + QuestTemplateLocale localeData = Global.ObjectMgr.GetQuestLocale(questId); + if (localeData != null) + ObjectManager.GetLocaleString(localeData.LogTitle, handler.GetSessionDbcLocale(), ref name); + } + } + handler.SendSysMessage(CypherStrings.CommandNpcShowlootTrackingQuest, alternateString ? 6 : 3 /*number of bytes from following string*/, "\xE2\x94\x80\xE2\x94\x80", questId, name, questId); + } + static void _IterateNotNormalLootMap(CommandHandler handler, MultiMap map, List items) { foreach (var key in map.Keys) @@ -564,6 +581,9 @@ namespace Game.Chat case LootItemType.Currency: _ShowLootCurrencyEntry(handler, item.itemid, item.count, true); break; + case LootItemType.TrackingQuest: + _ShowLootTrackingQuestCurrencyEntry(handler, item.itemid, true); + break; } } } @@ -589,6 +609,9 @@ namespace Game.Chat case LootItemType.Currency: _ShowLootCurrencyEntry(handler, item.itemid, item.count); break; + case LootItemType.TrackingQuest: + _ShowLootTrackingQuestCurrencyEntry(handler, item.itemid); + break; } } } @@ -608,6 +631,9 @@ namespace Game.Chat case LootItemType.Currency: _ShowLootCurrencyEntry(handler, item.itemid, item.count); break; + case LootItemType.TrackingQuest: + _ShowLootTrackingQuestCurrencyEntry(handler, item.itemid); + break; } } } diff --git a/Source/Game/Entities/Player/Player.Items.cs b/Source/Game/Entities/Player/Player.Items.cs index 3622a98e8..ea961560c 100644 --- a/Source/Game/Entities/Player/Player.Items.cs +++ b/Source/Game/Entities/Player/Player.Items.cs @@ -6104,6 +6104,9 @@ namespace Game.Entities case LootItemType.Currency: ModifyCurrency(item.itemid, (int)item.count, CurrencyGainSource.Loot); break; + case LootItemType.TrackingQuest: + // nothing to do, already handled + break; } if (ffaItem != null) @@ -6212,7 +6215,7 @@ namespace Game.Entities SendPacket(packet); // add 'this' player as one of the players that are looting 'loot' - loot.OnLootOpened(GetMap(), GetGUID()); + loot.OnLootOpened(GetMap(), this); m_AELootView[loot.GetGUID()] = loot; if (loot.loot_type == LootType.Corpse && !loot.GetOwnerGUID().IsItem()) diff --git a/Source/Game/Loot/Loot.cs b/Source/Game/Loot/Loot.cs index 2b3006572..3ecb0d502 100644 --- a/Source/Game/Loot/Loot.cs +++ b/Source/Game/Loot/Loot.cs @@ -54,6 +54,10 @@ namespace Game.Loots type = LootItemType.Currency; freeforall = true; break; + case LootStoreItemType.TrackingQuest: + type = LootItemType.TrackingQuest; + freeforall = true; + break; default: break; } @@ -73,6 +77,8 @@ namespace Game.Loots return ItemAllowedForPlayer(player, loot, itemid, needs_quest, follow_loot_rules, false, conditions); case LootItemType.Currency: return CurrencyAllowedForPlayer(player, itemid, needs_quest, conditions); + case LootItemType.TrackingQuest: + return TrackingQuestAllowedForPlayer(player, itemid, conditions); default: break; } @@ -89,6 +95,8 @@ namespace Game.Loots strictUsabilityCheck, lootStoreItem.conditions); case LootStoreItemType.Currency: return CurrencyAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.needs_quest, lootStoreItem.conditions); + case LootStoreItemType.TrackingQuest: + return TrackingQuestAllowedForPlayer(player, lootStoreItem.itemid, lootStoreItem.conditions); default: break; } @@ -173,6 +181,18 @@ namespace Game.Loots return true; } + public static bool TrackingQuestAllowedForPlayer(Player player, uint questId, ConditionsReference conditions) + { + // DB conditions check + if (!conditions.Meets(player)) + return false; + + if (player.IsQuestCompletedBitSet(questId)) + return false; + + return true; + } + public void AddAllowedLooter(Player player) { allowedGUIDs.Add(player.GetGUID()); @@ -781,6 +801,14 @@ namespace Game.Loots items.Add(generatedLoot); break; } + case LootStoreItemType.TrackingQuest: + { + LootItem generatedLoot = new(item); + generatedLoot.count = 1; + generatedLoot.LootListId = (uint)items.Count; + items.Add(generatedLoot); + break; + } default: break; } @@ -834,6 +862,11 @@ namespace Game.Loots case LootItemType.Currency: player.ModifyCurrency(lootItem.itemid, (int)lootItem.count, CurrencyGainSource.Loot); break; + case LootItemType.TrackingQuest: + Quest quest = Global.ObjectMgr.GetQuestTemplate(lootItem.itemid); + if (quest != null) + player.RewardQuest(quest, LootItemType.Item, 0, player, false); + break; } if (ffaitem != null) ffaitem.is_looted = true; @@ -847,6 +880,22 @@ namespace Game.Loots return allLooted; } + void AutoStoreTrackingQuests(Player player, List ffaItems) + { + foreach (NotNormalLootItem ffaItem in ffaItems) + { + if (items[ffaItem.LootListId].type != LootItemType.TrackingQuest) + continue; + + --unlootedCount; + ffaItem.is_looted = true; + Quest quest = Global.ObjectMgr.GetQuestTemplate(items[ffaItem.LootListId].itemid); + if (quest != null) + player.RewardQuest(quest, LootItemType.Item, 0, player, false); + } + } + + public void LootMoney() { gold = 0; @@ -965,7 +1014,13 @@ namespace Game.Loots } if (!ffaItems.Empty()) + { + // TODO: flag immediately for loot that is supposed to be mailed if unlooted, otherwise flag when sending SMSG_LOOT_RESPONSE + //if (_mailUnlootedItems) + // AutoStoreTrackingQuests(player, *ffaItems); + PlayerFFAItems[player.GetGUID()] = ffaItems; + } } public void NotifyItemRemoved(byte lootListId, Map map) @@ -999,9 +1054,9 @@ namespace Game.Loots } } - public void OnLootOpened(Map map, ObjectGuid looter) + public void OnLootOpened(Map map, Player looter) { - AddLooter(looter); + AddLooter(looter.GetGUID()); if (!_wasOpened) { _wasOpened = true; @@ -1033,19 +1088,21 @@ namespace Game.Loots } else if (_lootMethod == LootMethod.MasterLoot) { - if (looter == _lootMaster) + if (looter.GetGUID() == _lootMaster) { - Player lootMaster = Global.ObjAccessor.GetPlayer(map, looter); - if (lootMaster != null) - { - MasterLootCandidateList masterLootCandidateList = new(); - masterLootCandidateList.LootObj = GetGUID(); - masterLootCandidateList.Players = _allowedLooters; - lootMaster.SendPacket(masterLootCandidateList); - } + MasterLootCandidateList masterLootCandidateList = new(); + masterLootCandidateList.LootObj = GetGUID(); + masterLootCandidateList.Players = _allowedLooters; + looter.SendPacket(masterLootCandidateList); } } } + + // Flag tracking quests as completed after all items were scanned for this player (some might depend on this quest not being completed) + //if (!_mailUnlootedItems) + var ffaItems = PlayerFFAItems.LookupByKey(looter.GetGUID()); + if (ffaItems != null) + AutoStoreTrackingQuests(looter, ffaItems); } public bool HasAllowedLooter(ObjectGuid looter) @@ -1163,6 +1220,7 @@ namespace Game.Loots LootItemData lootItem = new(); lootItem.LootListID = (byte)item.LootListId; lootItem.UIType = uiType.Value; + lootItem.Type = (byte)item.type; lootItem.Quantity = item.count; lootItem.Loot = new(item); packet.Items.Add(lootItem); diff --git a/Source/Game/Loot/LootManager.cs b/Source/Game/Loot/LootManager.cs index aa4fbe90b..5b830dbb4 100644 --- a/Source/Game/Loot/LootManager.cs +++ b/Source/Game/Loot/LootManager.cs @@ -532,6 +532,8 @@ namespace Game.Loots return RandomHelper.randChance(chance * qualityModifier); } + case LootStoreItemType.TrackingQuest: + return RandomHelper.randChance(chance); default: break; } @@ -611,6 +613,40 @@ namespace Game.Loots } break; } + case LootStoreItemType.TrackingQuest: + { + Quest quest = Global.ObjectMgr.GetQuestTemplate(itemid); + if (quest == null) + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: quest does not exist - skipped"); + return false; + } + + if (!quest.HasFlag(QuestFlags.TrackingEvent)) + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: quest is not a tracking flag - skipped"); + return false; + } + + if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: equal-chanced grouped entry, but group not defined - skipped"); + return false; + } + + if (chance != 0 && chance < 0.0001f) // loot with low chance + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: low chance ({chance}) - skipped"); + return false; + } + + if (mincount != 1 || maxcount != 0) // wrong count + { + Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} ItemType {type} Item {itemid}: tracking quest has count other than 1 (MinCount {maxcount} MaxCount {mincount}) - skipped"); + return false; + } + break; + } default: Log.outError(LogFilter.Sql, $"Table '{store.GetName()}' Entry {entry} Item {itemid}: invalid ItemType {type}, skipped"); return false; @@ -804,6 +840,7 @@ namespace Game.Loots { case LootStoreItemType.Item: case LootStoreItemType.Currency: + case LootStoreItemType.TrackingQuest: // Plain entries (not a reference, not grouped) // Chance is already checked, just add if (personalLooter == null || LootItem.AllowedForPlayer(personalLooter, item, true)) @@ -899,6 +936,7 @@ namespace Game.Loots break; } case LootStoreItemType.Currency: + case LootStoreItemType.TrackingQuest: { // Plain entries (not a reference, not grouped) // Chance is already checked, just add @@ -949,6 +987,8 @@ namespace Game.Loots switch (lootStoreItem.type) { case LootStoreItemType.Item: + case LootStoreItemType.Currency: + case LootStoreItemType.TrackingQuest: if (LootItem.AllowedForPlayer(player, lootStoreItem, strictUsabilityCheck)) return true; // active quest drop found break; @@ -989,6 +1029,10 @@ namespace Game.Loots if (li.type != LootItemType.Currency) continue; break; + case LootStoreItemType.TrackingQuest: + if (li.type != LootItemType.TrackingQuest) + continue; + break; default: break; }